Search code examples
androidrealm

Global Realm Instance in Android


(I've seen other posts on this but they're a bit outdated, mostly referencing realm 1.x)

Been looking for the best way to institute realm in our app. We're currently all over the place and looking to centralize our realm instance.

In this link (https://medium.com/@Zhuinden/basics-of-realm-a-guide-to-using-realm-1-2-0-634471c0fe8f), I found what I think is a great global implementation but I have 2 questions on it:

1. Does this still apply to current versions of realm (currently at 4.1)?

2. What's the purpose of the retained fragment and can it be done without one?

Thanks in advance!

Here's the relevant section from the link above:

- Making a global Realm instance

I set up a retained fragment in my activity which increments the current active > Activity count. Open the Realm if the counter went from 0 to 1. Close the Realm if the counter went from 1 to 0.

import android.content.Context;
import android.util.Log;

import io.realm.Realm;
import io.realm.RealmConfiguration;

/**
 * Created by Zhuinden on 2016.08.16..
 */
public class RealmManager {
    static Realm realm;

    static RealmConfiguration realmConfiguration;

    public static void initializeRealmConfig(Context appContext) {
        if(realmConfiguration == null) {
            setRealmConfiguration(new RealmConfiguration.Builder(appContext)
                    .deleteRealmIfMigrationNeeded()
                    .build());
        }
    }

    public static void setRealmConfiguration(RealmConfiguration realmConfiguration) {
        RealmManager.realmConfiguration = realmConfiguration;
        Realm.setDefaultConfiguration(realmConfiguration);
    }

    private static int activityCount = 0;

    public static Realm getRealm() {
        return realm;
    }

    public static void incrementCount() {
        if(activityCount == 0) {
            if(realm != null) {
                if(!realm.isClosed()) {
                    realm.close();
                }
            }
            realm = Realm.getDefaultInstance();
        }
        activityCount++;
    }

    public static void decrementCount() {
        activityCount--;
        if(activityCount <= 0) {
            activityCount = 0;
            realm.close();
            Realm.compactRealm(realmConfiguration);
            realm = null;
        }
    }
}

The setRealmConfiguration() method is so that you can replace the default configuration from instrumentation tests with a config for an inMemory() Realm. If you use this, then you can easily count activity references using a retained fragment!

public class BooksScopeListener extends Fragment { // could go to base class
    public BooksScopeListener() {
        setRetainInstance(true);
        RealmManager.incrementCount();
    }

    @Override
    public void onDestroy() {
        RealmManager.decrementCount();
        super.onDestroy();
    }
}

public class BooksActivity extends AppCompatActivity {
    Realm realm;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        RealmManager.initializeRealmConfig(getApplicationContext()); // could go to base class
        super.onCreate(savedInstanceState);
        BooksScopeListener fragment = (BooksScopeListener) getSupportFragmentManager().findFragmentByTag("SCOPE_LISTENER"); // could go to base class
        if(fragment == null) {
            fragment = new BooksScopeListener();
            getSupportFragmentManager().beginTransaction().add(fragment, "SCOPE_LISTENER").commit();
        }
        //get realm instance
        realm = RealmManager.getRealm();

Ta-dah, now you can freely access RealmManager.getRealm() anywhere for the UI thread. Or just @Inject this from a module. (Don’t forget that the scoped approach per activity or fragment works too, I just prefer this one because I used @Singleton component over @Subcomponents).


Solution

  • I wrote that article about a year ago, and while that creates a global Realm for the UI thread, you won't be able to just use it on any thread. And if you open/close the Realm on background threads, you still need to pass Realm as method argument.

    1. Does this still apply to current versions of realm (currently at 4.1)?

    Realm lifecycle management is still a thing that you need to do, and the same principle would apply even for 4.1.0, just like it did in 1.2.0. This is one thing that never really changed :)

    1. What's the purpose of the retained fragment and can it be done without one?

    The retained fragment is added so that you don't close/reopen the Realm just because you rotated the screen; lately you can do this with ViewModel's constructor + onCleared() method as well.

    I just found that retained fragments are the trustworthiest way of listening to the lifecycle without configuration change messing with the lifecycle callbacks.

    The RealmManager shown in the article is responsible solely for the automatic lifecycle management on the UI thread. If used on the UI thread, it still works fine. In fact, that's still where I'd call open/close from in the following example, for the UI thread, anyways.


    If you want a singleton manager class for Realms across threads, a starting out point would be to use following class (a bit proof of concept as I haven't used it in production yet, but hey):

    public class RealmManager {
        private final ThreadLocal<Realm> localRealms = new ThreadLocal<>();
    
    
        /**
         * Opens a reference-counted local Realm instance.
         *
         * @return the open Realm instance
         */
        public Realm open() {
            checkDefaultConfiguration();
            Realm realm = Realm.getDefaultInstance(); 
            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 get() {
            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 close() {
            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);
            }
        }
    }
    

    This way it is actually possible to use RealmManager class as single point of entry provided as @Provides @Singleton RealmManager realmManager(), and detach open() from get() thus allowing you to use an open Realm instance for a given thread (if it is already open), without risk of never being able to close it (because of calling Realm.getDefaultInstance() without a close() pair).

    Then for background thread, you'd use

    try {
        realmManager.open();
        ...
    } finally {
        realmManager.close();
    }
    

    And in other methods inside you can do

    Realm realm = realmManager.get();
    

    As for UI thread, you'd use the retained fragment (or Activity/Fragment onCreate()/onDestroy()) as you normally would, but using open/close as well.

    This way you can manage the Realm lifecycle in a single place, but still pass Realm as thread-local variable, instead of a global one (which would not work, as Realm instances are thread-confined).