Zhuinden / realm-monarchy

[ACTIVE-ISH] A wrapper over Realm which exposes it as LiveData, managing Realm lifecycle internally.
Apache License 2.0
88 stars 11 forks source link

Filter "RealmResults" with recycler view animations #22

Closed lucamtudor closed 5 years ago

lucamtudor commented 6 years ago

Is there a way to filter a RealmResults and get "granular animations"? I tried it with both RealmRecyclerViewAdapter and ManagedDogAdapter from the examples.

I just added something like

 private void onQueryChanged(String query) {
        changes = monarchy.findAllManagedWithChanges(realm -> realm.where(RealmDog.class).contains(RealmDogFields.NAME, query, Case.INSENSITIVE));
        changes.observeForever(observer); // detach != destroy in fragments so this is manual
    }

You always get OrderedCollectionChangeSet.State.INITIAL this way.

I can achieve this using the paging components, but I don't want to detach the realm objects. I have a pager with 3 lists that could have each 2-3k elements (contacts, messages), if the user scrolls at the bottom on those lists the memory will be pretty much filled.

Am I missing something? This seems like a very obvious thing, but I'm struggling like an idiot 😢.

Zhuinden commented 6 years ago

1.) you should remove the observer from the previous changes if you use observeForever before re-assigning it to a new value

2.)

Am I missing something? This seems like a very obvious thing

Doesn't look obvious to me. One could say "hey use ListAdapter" except ListAdapter does diffing on background thread, moving RealmObject to background thread would require detaching.

You're getting INITIAL because you are creating a new RealmResults, but afaik you cannot "filter in place" so you can't get Realm to evaluate the diff for you.

public void updateList(List newList) { DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new MyDiffCallback(currentList, newList)); this.currentList = newList; diffResult.dispatchUpdatesTo(this); }

Only thing I can think of is to diff synchronously, but with 2k-3k elements that can be slow, which is clearly not efficient.

The second and much trickier thing I can think of is to re-fetch the data on a background thread (after realm.refresh(); on said background thread) based on the two queries synchronously (assuming non-sync Realm), do the diff, then return the diff before setting the new list. This could work with adequate debouncing on user input and most possibly with RxJava. However in that case you cannot 100% guarantee that UI thread did not receive a new version of data while diffing is in progress (unless you wrap the whole damn thing with RxJava, but even then RealmResults mutates in place over time, so that might fail too.

I was brainstorming a bit and thought of the diffing mechanism used by thorben primke's RealmBasedRecyclerViewAdapter which I generally don't recommend for use, but it has its share of tricks albeit it is not compatible with Realm 3.7.0 and newer. However, upon further inspection it doesn't bring more to the table than regular DiffUtil.

So with all that in mind, I'm actually not sure what the right course of action is! Only thing I can think of is "this works reliably only if you detach the objects from the Realm with PagedList" which is also listed as something you don't want due to its own limitations. Damn!

lucamtudor commented 6 years ago

Thanks a lot for your thoughts! For now, I'll probably use a PagedList (if we manage to make updateQuery work). I made an issue on realm-java & stackoverlow as well.

I always loved Realm & I never had the "threading problem" people usually complain about but this thing is driving me crazy. I'll start looking into alternatives like ObjectBox & see if I can work something out. Room is pretty sweet, but I left sqlite behind when Realm 0.86 was around & it's still giving me the creeps when I see a SQL query 😅.

Anyways, rant over.

@Zhuinden thanks so much for all your effort!

Zhuinden commented 6 years ago

Only Realm provides this sort of lazy-loading mechanism, ObjectBox also has memory-mapped cursors like Realm does but it is opened and closed for the duration of the read transaction which means they do not have the concept of "managed objects".

Its theory is very similar to using detached objects from Realm, except they are faster in raw data access because they don't have long-running read transactions therefore don't have thread checks for data accessors.


I'm also surprised this "filtering in place" never came up, which is interesting because I've been following Realm for 3 years. I guess the "trick" solution was that for duration of async query execution, people show a loading indicator in place of the data, and there are no fine-grained notifications used.

But it is an absolutely valid requirement to have filtering in place and removing filters from the same result set.

lucamtudor commented 6 years ago

It's interesting that this never came up before. Leave a comment/something on the realm-java repo so I don't look I'm all alone here. 😄

Zhuinden commented 5 years ago

@lucamtudor I think the reason is that when I needed this, we could "ditch animations" and that was when fine-grained notifications weren't supported at all.