0

I'm working with RxJava + Realm and would like to do something that seems pretty straightforward:

I want to load all RealmObjects that match certain criteria, transform each of the RealmResult objects, then notify the main thread once those transformed objects are ready. The reason I need the transformation to happen in the background thread as well as it's a long running operation if there are a large number of objects in the RealmResults

The problem is, I'm able to load the objects in the background, but unable to do anything with each of the objects while still in the background thread. I'm also trying to do this in a reactive manner.

Code so far:

groupContactSubscription = realm.where(GroupContact.class)
                .equalTo(MODEL_ID, mGroupId)
                .findAllAsync()
                .asObservable()
                .filter(new Func1<RealmResults<GroupContact>, Boolean>() {
                    @Override
                    public Boolean call(RealmResults<GroupContact> groupContacts) {
                        return groupContacts.isLoaded();
                    }
                })
                .first()
                .map(new Func1<RealmResults<GroupContact>, List<Contact>>() {
                    @Override
                    public List<Contact> call(RealmResults<GroupContact> groupContacts) {
                        List<Contact> contacts = new ArrayList<>();

                        //Transform each GroupContact
                        for(int i=0; i<groupContacts.size(); ++i){
                          contacts.add(transformGroupContact(groupContacts.get(0))
                        }
                        return contacts;
                    }
                })
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Action1<List<Contact>>() {
                    @Override
                    public void call(List<Contact> contacts) {
                        // Deal with transformed objects
                    }
                });

The problem is, this isn't happening in the background thread (it still blocks the main thread).

I'm not really sure how this should be done cleanly, can someone point me in the right direction on what's wrong with what I have already? What I believe is happening is that the Realm transaction is happening asynchronously, and the filter->map is also happening on that same background thread. Clearly, it is not, though.

This is my attempt at using RxJava so please be kind

Nithinlal
  • 4,845
  • 1
  • 29
  • 40
rosenthal
  • 775
  • 1
  • 11
  • 28
  • Have you considered *not* transforming the result set, and thus eliminating the need to read *every single element of the result set* from the database, and rather just use Realm's lazy-loading capabilities instead? Just wondering. – EpicPandaForce Dec 18 '16 at 10:38

1 Answers1

1
       groupContactSubscription = Observable.fromCallable(() -> {
                try(Realm realm = Realm.getDefaultInstance()) {
                    RealmRefresh.refreshRealm(realm); // from http://stackoverflow.com/a/38839808/2413303
                    RealmResults<GroupContact> groupContacts = realm.where(GroupContact.class)
                                                                 .equalTo(MODEL_ID, mGroupId)
                                                                 .findAll();
                    List<Contact> contacts = new ArrayList<>();
                    //Transform each GroupContact
                    for(int i = 0; i < groupContacts.size(); ++i) {
                        contacts.add(transformGroupContact(groupContacts.get(i));
                    }
                    return contacts;                        
                }
            })
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(new Action1<List<Contact>>() {
                @Override
                public void call(List<Contact> contacts) {
                    // Deal with transformed objects
                }
            });

Although this doesn't auto-update, so I'm a bit hazy on this one, but I think this would work:

       groupContactSubscription = realm.where(GroupContacts.class).findAll().asObservable()
         .observeOn(Schedulers.io())
         .switchMap(() -> 
             Observable.fromCallable(() -> {
                try(Realm realm = Realm.getDefaultInstance()) {
                    RealmRefresh.refreshRealm(realm); // from http://stackoverflow.com/a/38839808/2413303
                    RealmResults<GroupContact> groupContacts = realm.where(GroupContact.class)
                                                                 .equalTo(MODEL_ID, mGroupId)
                                                                 .findAll();
                    List<Contact> contacts = new ArrayList<>();
                    //Transform each GroupContact
                    for(int i = 0; i < groupContacts.size(); ++i) {
                        contacts.add(transformGroupContact(groupContacts.get(i));
                    }
                    return contacts;                        
                }
            })
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(new Action1<List<Contact>>() {
                @Override
                public void call(List<Contact> contacts) {
                    // Deal with transformed objects
                }
            });

Although with a proper Realm schema, you wouldn't even need to do such transformations (which result in eagerly evaluating the entire database, throwing lazy-loading out the window), you would just do

Subscription contacts = realm.where(Contact.class)
                                      .equalTo("groupContact.modelId", mGroupId)
                                      .findAllAsync()
                                      .asObservable()
                                      .filter(RealmResults::isLoaded)
                                      .filter(RealmResults::isValid)
                                      .subscribe(...);

Or something like that. Maybe just return GroupContact as is.

EpicPandaForce
  • 79,669
  • 27
  • 256
  • 428
  • Hmm, it would appear I was operating blind, and forgotten about Realm's lazy-loading out-of-the-box... The operation to transform the result is rather quick (slow when it's over every RealmResult though..), and maybe best to transform the result in my recycler view's adapter. This way, I can do some level of caching (perhaps a fixed size LRU cache) and only load data when I need to. I think that makes more sense, because as you suggest it seems stupid to load all the data eagerly and transform it.... – rosenthal Dec 18 '16 at 10:56
  • Technically if you can make it so that you receive`RealmResults` in your adapter, then you'll only transform items that are actually showing at the moment. If you use [`RealmRecyclerViewAdapter`](https://github.com/realm/realm-android-adapters), then you can ditch RxJava for this use-case and just give `realm.where(Blah.class)./*...*/.findAllAsync()` to the adapter, and it'll manage loading it. – EpicPandaForce Dec 18 '16 at 11:03
  • Thanks boss -- this all makes sense. Accept the answer because you did provide a solution that would work, but your initial comment reminded me something I had mistakenly forgotten – rosenthal Dec 18 '16 at 19:07