Search code examples
androidrealm

What is the best practice to chain realm queries in Android?


So, I have an Android app that uses realm.io. I have to run queries asynchronously like this :

public static void getProductsByCategoryId(Realm realm, 
     String categoryId,
     OrderedRealmCollectionChangeListener<RealmResults<Product>> callback) {

    RealmResults<Product> result = realm.where(Product.class)
            .equalTo(CATEGORY, categoryId)
            .findAllAsync();
    result.addChangeListener(callback);
}

The callback will process this response, but then I need to run another query in sequence. So, you'll have queryA => process response => queryB => process response. So, the callback may have code like this

.....
getProductsByCategoryId(app.getRealmInstance(), "ABC123", firstCallback);
.....
private OrderedRealmCollectionChangeListener<RealmResults<Product>> firstCallback = new OrderedRealmCollectionChangeListener<RealmResults<Product>>() {

        @Override
        public void onChange(RealmResults<Product> realmProducts, OrderedCollectionChangeSet changeSet) {

                mProdList.addAll(mRealm.copyFromRealm(realmProducts));

                // get more product info (2nd call)
                MainApplication.getMoreProductInfo(mRealm, mCatId, false, secondCallback);
        }
    };

Currently, my understanding is that you would run queryB in the callback of queryA ? Looking at the requirements for the app, I will end up with chains of 3 or 4 queries. Is this an appropriate approach, or is there a specific pattern I should be using ? I haven't found any guidance yet in the Realm documentation.


Solution

  • It's generally an indication of bad schema design if you need to do multiple queries in order to retrieve your result set, because the way Realm works is that if you can define your query results with one query (and you don't use realm.copyFromRealm() which you generally don't need to use anyways), then its elements and the results itself are all lazy-loaded.

    If you cannot accomplish that, then even then, generally you probably shouldn't chain find*Async calls, because any RealmResults that you don't store as a field variable has a chance of being consumed by GC, and its change listener won't be called when isLoaded() is true (because said RealmResults no longer exists).

    So what you really seem to want to do is just execute multiple queries on a background thread then return copied results to the main thread, in which case it'd just look like this

    Executor executor = Executors.newSingleThreadedPool(); // or some other pool
    Handler handler = new Handler(Looper.getMainLooper());
    
    public void getQueryResults(DataLoadedCallback callback) {
        executor.execute(() -> {
            try(Realm realm = Realm.getDefaultInstance()) {
                realm.refresh(); // <-- might not be necessary
                RealmResults<XYZ> results1 = realm.where(XYZ.class)./*...*/.findAll();
                RealmResults<ZXY> results2 = realm.where(ZXY.class)./*...*/.findAll();
                RealmResults<YZX> results3 = realm.where(YZX.class)./*...*/.findAll();
                List<Something> someList = new LinkedList<>(); 
                for/*do magic transform things*/
                    someList.add(blah /* blah is not a managed RealmObject */);
                }
                handler.post(() -> {
                    callback.onDataLoaded(Collections.unmodifiableList(new ArrayList<>(someList)));
                });
            }
        });
    }