Search code examples
javaandroidrealm

Have I implemented EpicPandaForce's RealmManager correctly?


I am new to Realm (and Android development) and I would like to use a Singleton class to simplify Realm data management so it's easier for my friends to use in our group project.

EpicPandaForce have written a singleton class called RealmManager here, but I couldn't find an example in implementing it, so this is what I have tried:

public class RealmManager {
    private static RealmManager instance;
    private final ThreadLocal<Realm> localRealm = new ThreadLocal<>();

    RealmManager(){}

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

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

    public void storePreferenceDao(int userID, String rank){
        final PreferenceDao preferenceDao = new PreferenceDao();
        preferenceDao.setUserID(userID);
        preferenceDao.setRank(rank);
        openLocalInstance();
        getLocalInstance().executeTransactionAsync(new Realm.Transaction() {
            @Override
            public void execute(Realm realm) {
                realm.copyToRealmOrUpdate(preferenceDao);
            }
        }, new Realm.Transaction.OnSuccess(){
            @Override
            public void onSuccess(){
                System.out.println("Data is stored successfully!");
            }
        }, new Realm.Transaction.OnError(){
            @Override
            public void onError(Throwable error){
                System.out.println("There is an error in storePreferenceDao()");
            }
        });
        closeLocalInstance();
    }

So when my friends want to store some data, they can just use:

RealmManager.getInstance().storePreferenceDao(123, "Alpaca");

Is this how it is supposed to be used or is it redundant? How can I make it more efficient?


Solution

  • Actually in this case, that method can still be called only from UI thread, and the local instance should be closed in the transaction callback (otherwise the onSuccess/onError won't be called)

    You could make a method that is able to execute on bg thread if able, and on current thread if already on a bg thread

    // method in RealmManager
    public final void runTransaction(Realm.Transaction transaction) {
        runTransaction(transaction, null, null);
    }
    
    public final void runTransaction(Realm.Transaction transaction, Realm.Transaction.OnSuccess onSuccess) {
        runTransaction(transaction, onSuccess, null);
    }
    
    public final void runTransaction(Realm.Transaction transaction, Realm.Transaction.OnSuccess onSuccess, Realm.Transaction.OnError onError) {
        Realm realm = openLocalInstance();
        if(realm.isAutoRefresh()) {
            realm.executeTransactionAsync(transaction, new Realm.Transaction.OnSuccess() {
                @Override
                public void onSuccess() {
                    try {
                        if(onSuccess != null) {
                            onSuccess.onSuccess();
                        }
                    } finally {
                        closeLocalInstance();
                    }
                }
            }, new Realm.Transaction.OnError() {
                @Override
                public void onError(Throwable e) {
                    try {
                        if(onError != null) {
                            onError.onError(e);
                        }
                    } finally {
                        closeLocalInstance();
                    }
                }
            });
        } else {
            try {
                realm.executeTransaction(transaction);
                if(onSuccess != null) {
                    onSuccess.onSuccess();
                }
            } catch(Exception e) {
                if(onError != null) {
                    onError.onError(e);
                }
                throw new RuntimeException(e);
            } finally {
                closeLocalInstance();
            }
        }
    }
    

    If you add this method, then you can now execute a transaction that will either be executed on background thread via async transaction method if possible, using synchronous transaction method if not on a looper thread (f.ex. background thread)

    This way you can now do

    public void storePreferenceDao(int userID, String rank) {
        final PreferenceDao preferenceDao = new PreferenceDao();
        preferenceDao.setUserID(userID);
        preferenceDao.setRank(rank);
        runTransaction(new Realm.Transaction() {
            @Override
            public void execute(Realm realm) {
                realm.copyToRealmOrUpdate(preferenceDao);
            }
        }, new Realm.Transaction.OnSuccess(){
            @Override
            public void onSuccess(){
                System.out.println("Data is stored successfully!");
            }
        }, new Realm.Transaction.OnError(){
            @Override
            public void onError(Throwable error){
                System.out.println("There is an error in storePreferenceDao()");
            }
        });
    }
    

    Or just

    public void storePreferenceDao(int userID, String rank) {
        final PreferenceDao preferenceDao = new PreferenceDao();
        preferenceDao.setUserID(userID);
        preferenceDao.setRank(rank);
        runInTransaction(new Realm.Transaction() {
            @Override
            public void execute(Realm realm) {
                realm.copyToRealmOrUpdate(preferenceDao);
            }
        });
    }
    

    You know, I always felt I should add a runTransaction() method to that example. Whether it should default to using executeTransactionAsync by default if able or not is up for debate, though.