Duy Pham Duy Pham - 25 days ago 11
Android Question

Dagger 2 – Should I use a singleton Realm instance?

I'm using Dagger 2 to provide a singleton Realm instance for the whole app (all data access objects use a single realm). However, as far as I know, Realm can have multi-instance using

Realm.getInstance()
and we have to close each instance when we're done with it as presented by the Realm docs:

/**
* Closes the Realm instance and all its resources.
* <p>
* It's important to always remember to close Realm instances when you're done with it in order not to leak memory,
* file descriptors or grow the size of Realm file out of measure.
*
* @throws IllegalStateException if attempting to close from another thread.
*/
@Override
public void close() {
if (this.threadId != Thread.currentThread().getId()) {
throw new IllegalStateException(INCORRECT_THREAD_CLOSE_MESSAGE);
}

if (realmCache != null) {
realmCache.release(this);
} else {
doClose();
}
}


My question is: should I use a singleton Realm instance as I did, or create a realm instance for each Activity / Fragment and close it using
realm.close()
at
onDestroy()
?

Answer Source

Managed RealmObjects (which are lazy-loaded on their access) are accessible only if there is at least 1 open instance of Realm on that given thread, however NOT closing Realm instance on non-looper background thread is very severe problem.

If you provide thread-local singleton Realm from Dagger module then that Realm instance will only be accessible on the thread it was created on. And will cause a crash accessed from anywhere else.

One possibility would be to provide a singleton class of your own that can open Realm instances, like this:

@Singleton
public class RealmManager {
    private final ThreadLocal<Realm> localRealms = new ThreadLocal<>();

    @Inject
    public RealmManager() {
    }

    /**
     * Opens a reference-counted local Realm instance.
     *
     * @return the open Realm instance
     */
    public Realm openLocalInstance() {
        checkDefaultConfiguration();
        Realm realm = Realm.getDefaultInstance(); // <-- maybe this should be configurable
        if(localRealms.get() == null) {
            localRealms.set(realm);
        }
        return realm;
    }

    /**
     * Returns the local Realm instance without adding to the reference count.
     *
     * @return the local Realm instance
     * @throws IllegalStateException when no Realm is open
     */
    public Realm getLocalInstance() {
        Realm realm = localRealms.get();
        if(realm == null) {
            throw new IllegalStateException(
                    "No open Realms were found on this thread.");
        }
        return realm;
    }

    /**
     * Closes local Realm instance, decrementing the reference count.
     *
     * @throws IllegalStateException if there is no open Realm.
     */
    public void closeLocalInstance() {
        checkDefaultConfiguration();
        Realm realm = localRealms.get();
        if(realm == null) {
            throw new IllegalStateException(
                    "Cannot close a Realm that is not open.");
        }
        realm.close();
        // noinspection ConstantConditions
        if(Realm.getLocalInstanceCount(Realm.getDefaultConfiguration()) <= 0) {
            localRealms.set(null);
        }
    }

    private void checkDefaultConfiguration() {
        if(Realm.getDefaultConfiguration() == null) {
            throw new IllegalStateException("No default configuration is set.");
        }
    }
}

But even then, you'd need to manage the local instances for the given threads where you need them.

public class MainActivity
        extends AppCompatActivity {
    RealmManager realmManager;

    ...

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        realmManager = Injector.get().realmManager();
        realmManager.openLocalInstance();
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        ...
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        realmManager.closeLocalInstance();
    }