Search code examples
androidsingletonrealmrealm-mobile-platform

Realm with Singleton Pattern in Android: Pros and Cons


Hi I have gone through the best practices in Realm and according to it the best way to handle Realm instances is to open a realm instance in onCreate() method of an Activity using relam = Realm.getDefaultInstance() and close it in onDestroy() using realm.close().

But currently I have following singleton structure in my code. I need to know the pros and cons of the following singleton structure over the best practice suggested in realm docs.

Here is my code: Approach 1

public class RealmManager {
private static final String TAG = "RealmManager";

private RealmAsyncTask transactionManager;
private static RealmManager mInstance = null;

public static RealmManager getInstance() {
    if (mInstance == null)
        mInstance = new RealmManager();
    return mInstance;
}

private Realm mRealm;

protected RealmManager() {
    mRealm = Realm.getDefaultInstance();
}

public void saveOrUpdateChatChannel(ChatChannel channel) {
    mRealm = Realm.getDefaultInstance();
    mRealm.executeTransactionAsync(new Realm.Transaction() {
        @Override
        public void execute(@NonNull Realm bgRealm) {
            bgRealm.copyToRealmOrUpdate(channel);
        }
    }, new Realm.Transaction.OnError() {
        @Override
        public void onError(Throwable error) {
            Log.e(TAG,"Failed to update Channel");
        }
    });
}

public void deleteChatChannel(String channelID, OnRealmDatabaseListener mRealmListener) {
    mRealm = Realm.getDefaultInstance();

    mRealm.executeTransactionAsync(new Realm.Transaction() {
        @Override
        public void execute(@NonNull Realm realm) {
            RealmResults<ChatChannel> result = realm.where(ChatChannel.class).equalTo("channelId", channelID).findAll();
            result.deleteAllFromRealm();
        }
    }, new Realm.Transaction.OnSuccess() {
        @Override
        public void onSuccess() {
            if (mRealmListener != null)
                mRealmListener.isDatabaseOperationSuccess(channelID, true);
        }
    }, new Realm.Transaction.OnError() {
        @Override
        public void onError(@NonNull Throwable error) {

        }
    });
}

public void closeRealm() {
    if (mRealm != null) {
        mRealm.close();
    }
    if (transactionManager != null) {
        transactionManager.cancel();
    }
  }

}

So in the Approach 1, I will be creating realm instances in my activities, services, intent services using RealmManager.getInstance() and then continue to do transactions. And in all my Activity onDestroy() methods i am closing the realm using RealmManager.closeRealm(). So my question is, if the RealmManager.closeRealm() which is called in Activity onDestroy(), will affect any transactions which are executing in Service?

Here is my code: Approach 2

public class RealmManager {

private static RealmManager mInstance = null;

public static RealmManager getInstance() {
    if (mInstance == null)
        mInstance = new RealmManager();
    return mInstance;
}

private Realm mRealm;

protected RealmManager(){
    mRealm = Realm.getDefaultInstance();
}

public void addClockModel(ClockRLM clockRLM,OnRealmDatabaseListener mRealmListener){

    RealmAsyncTask transactionManager = mRealm.executeTransactionAsync(realm -> realm.copyToRealm(clockRLM), new Realm.Transaction.OnSuccess() {
        @Override
        public void onSuccess() {
            Log.d("Clocke ", "Inserted TimeStamp " + clockRLM.getTimeStamp());
            if (mRealmListener != null)
                mRealmListener.isDatabaseOperationSuccess(clockRLM,true);
            if (transactionManager != null)
                transactionManager.cancel();
        }
    }, new Realm.Transaction.OnError() {
        @Override
        public void onError(Throwable error) {
            if (transactionManager != null)
                transactionManager.cancel();
        }
    });
  }
}

So in the Approach 2, I will be creating realm instances in my activities, services, intent services using RealmManager.getInstance() and then continue to do transactions. I am not sure where to close realm if i use Approach 2. What if i do not close it anywhere and only when the app closes, the RealmManager gets destroyed and realm instance will be destroyed. OR i need to close the realm instance in the application level (I am not sure whether we can close instance in the application level).

Which one is better among Approach 1 and Approach 2. Or is it better to open a realm instance in onCreate() method of an Activity using relam = Realm.getDefaultInstance() and close it in onDestroy() using realm.close().


Solution

  • Realm is hard to use under a "singleton manager" because Realm.getDefaultInstance() might seem like you're getting something that's a "singleton", but it's really not. Instances of Realm are thread-local and reference-counted, each call to getInstance() increments a ref count while close() decrements it.

    I've said a few times that open() would have been a better name, but I came to this conclusion far too late :)

    First, the reason why your Singleton approach is not good is because:

    • Calls to the methods can only be done from the thread that first calls RealmManager.getInstance(), which is expected to the UI thread (but not guaranteed)

    • You hard-code that you want to execute each 1 operation in 1 async transaction, so you can't use this thing on a background thread too much

    In order to make a realm manager that can be invoked on any threads, and UI thread uses async transaction while background thread uses sync transaction, you'd need to use a method like this.

    And also, you'd need to track the open Realm instance for that given thread, so that you can access it wherever you want, without incrementing the ref count.

    public class RealmManager {
        private final ThreadLocal<Realm> localRealm = new ThreadLocal<>();
    
        public Realm openLocalInstance() {
            Realm realm = Realm.getDefaultInstance();
            if(localRealm.get() == null) {
                localRealm.set(realm);
            }
            return realm;
        }
    
        public Realm getLocalInstance() {
            Realm realm = localRealm.get();
            if(realm == null) {
                throw new IllegalStateException("No open Realms were found on this thread.");
            }
            return realm;
        }
    
        public void closeLocalInstance() {
            Realm realm = localRealm.get();
            if(realm == null) {
                throw new IllegalStateException(
                    "Cannot close a Realm that is not open.");
            }
            realm.close();
            if(Realm.getLocalInstanceCount(Realm.getDefaultConfiguration()) <= 0) {
                localRealm.set(null);
            }
        }
    

    With a class like this, you could do:

    try {
        realmManager.openLocalInstance();
        // Realm is open and can be accessed on this thread with `realmManager.getLocalInstance()`
        // also all your methods in the RealmManager can use `getLocalInstance()`
    } finally {
        realmManager.closeLocalInstance();
    }
    


    I also created a library a while ago that wraps Realm in such a way that it eliminates the need for manual reference counting, but it never really caught on. Check out the source if curious.