Search code examples
androidsingletonrealmrealm-mobile-platform

Realm with Singleton Pattern in Android : Correct structure or not?


Currently I have following singleton structure in my code for managing Realm transactions. I need to know the pros and cons of the following singleton structure. With this approach i will be calling updateClockModel() as RealManager.getInstance().updateClockModel(...) from all my activities and fragments.

public class RealmManager {

private static final String TAG = "RealmManager";
private static RealmManager mInstance = null;
private final ThreadLocal<Realm> localRealm = new ThreadLocal<>();

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

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);
    }
}

protected RealmManager() {
}

public void updateClockModel(ClockRLM clockRLM, OnRealmDatabaseListener mRealmListener) {
    Realm mRealm = openLocalInstance();

    mRealm.executeTransactionAsync(realm -> {
        RealmResults<ClockRLM> result = realm.where(ClockRLM.class).equalTo("timeStamp", clockRLM.getTimeStamp()).findAll();
        for (ClockRLM clockRLM1 : result) {
            clockRLM1.setUploadedSuccess(true);
        }
    }, new Realm.Transaction.OnSuccess() {
        @Override
        public void onSuccess() {
            Log.d("Clocke ", "inserted TimeStamp " + clockRLM.getTimeStamp());
            if (mRealmListener != null)
                mRealmListener.isDatabaseOperationSuccess(clockRLM, true);

            closeLocalInstance();
        }
    }, new Realm.Transaction.OnError() {
        @Override
        public void onError(Throwable error) {
            if (mRealmListener != null)
                mRealmListener.isDatabaseOperationSuccess(clockRLM, false);
            closeLocalInstance();
        }
    });
}

public void addClockModel(ClockRLM clockRLM, OnRealmDatabaseListener mRealmListener) {
    Realm mRealm = openLocalInstance();

    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);
            closeLocalInstance();
        }
    }, new Realm.Transaction.OnError() {
        @Override
        public void onError(Throwable error) {
            closeLocalInstance();
        }
    });
  }
}

Solution

  • It would work, except those methods that do writes cannot be executed on background threads - only on ui thread - so I'd add something like following method

    private void executeInTransaction(Realm.Transaction transaction) {
        try {
            Realm realm = openLocalInstance();
            if(!realm.isAutoRefresh()) {
                try {
                    boolean wasInTransaction = realm.isInTransaction();
                    if(!wasInTransaction) {
                        realm.beginTransaction();
                    }
                    transaction.execute(realm);
                    if(!wasInTransaction) {
                        realm.commitTransaction();
                    }
                } catch(Throwable e) {
                    if(realm.isInTransaction()) {
                        realm.cancelTransaction();
                    }
                }
            } else {
                realm.executeTransactionAsync(transaction);
            }
        } finally {
            closeLocalInstance();
        }
    }
    

    This way you can do batch background operations with manual transaction opening + execute async writes from UI thread.

    You need a bit of tweaking to add a "success/failure" listener but the basics are there.