Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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()?

like image 966
Duy Pham Avatar asked Mar 16 '26 22:03

Duy Pham


2 Answers

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();
    }
like image 191
EpicPandaForce Avatar answered Mar 19 '26 12:03

EpicPandaForce


A singleton instance most likely gets you into problems. You should create one for each Activity/Fragment instead.

like image 42
Christian Melchior Avatar answered Mar 19 '26 13:03

Christian Melchior



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!