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

Easy data filtering? #12

Closed lucamtudor closed 6 years ago

lucamtudor commented 6 years ago

This is more of a question, not a library issue per se.

I'm building a prototype to test monarchy & paging lib. Is there a quick pattern I can follow to filter a RealmDataSourceFactory backed paged list? I'm talking about a usual use-case with a long list of results & an edit-text based query.

Thanks for the great work on the library so far!

Zhuinden commented 6 years ago

Same question as for pretty much any LiveData<PagedList<T>>, which means there are two approaches and both should work: https://stackoverflow.com/questions/49192540/paging-library-filter-search

lucamtudor commented 6 years ago

Haha, I didn't notice your stackoverflow answer 👍. I did something similar:

class MessagesViewModel : ViewModel() { 
    private val query = MutableLiveData<String?>()   

    fun dispatchQuery(query: String?) {
        this.query.value = query
    }

    val messagesLiveData = switchMap(query) { queryText: String? ->
        val realmDataSourceFactory = monarchy.createDataSourceFactory { realm ->
            if (queryText.isNullOrEmpty()) {
                realm.where(MessageListRealm::class.java)
            } else {
                realm.where(MessageListRealm::class.java)
                    .beginGroup()
                    .contains("fullName", queryText, Case.INSENSITIVE)
                    .or()
                    .contains("phoneMobile", queryText, Case.INSENSITIVE)
                    .endGroup()
            }
        }

        val dataSourceFactory = realmDataSourceFactory.map { message ->
            repoMessageListToModel(message)
        }

        monarchy.findAllPagedWithChanges(
            realmDataSourceFactory,
            LivePagedListBuilder(dataSourceFactory, PAGE_SIZE).setBoundaryCallback(mobiusBoundaryCallback)
        )
    }!!
}

Even though making a new factory each time doesn't feel right to me... Any feedback?

Zhuinden commented 6 years ago

You should do realm.where<MessageListRealm>() instead to preserve your sanity :p

But the other guy below was right. You could theoretically parameterize the data source factory with the query and it would pass it to the datasource on creation and it could be a mutable field instead of final.

I think it's currently final, but not set in stone. In fact it's quite easy to support.

Zhuinden commented 6 years ago

The tricky part is if you actually use dataSourceFactory.map

Zhuinden commented 6 years ago

I think if I let you redefine the query inside PagedLiveResults and invalidate, then it is as if you had gotten a background thread write. So you get new results.

I should do that. That's a good idea, thanks.

lucamtudor commented 6 years ago

@Zhuinden thanks! I'm looking forward for the update ;)

Zhuinden commented 6 years ago

Should be available as 0.3.1 and I hope that I can call invalidate() from another thread :smile:

Try it out and report back when you can, although technically it should work

lucamtudor commented 6 years ago

@Zhuinden me coding at midnight might be the problem, but I can't seem to make it work. can you crop up a quick sample with it?

Zhuinden commented 6 years ago

AFAIK if you store the RealmDataSourceFactory<T> in a field:

  DataSource.Factory<Integer, RealmDog> realmDataSourceFactory = monarchy.createDataSourceFactory(
            realm -> realm.where(RealmDog.class)); // as a field

Then you can call updateQuery((realm) -> { ... }) on it, which should replace the query in PagedLiveResults, call invalidate on the datasource, and therefore force the DataSource.Factory to re-create the data source and provide new results without having to create a new LivePagedListBuilder and whatever.

Zhuinden commented 6 years ago

I think multithreading foiled me again and I need to use AtomicReference. I'll fix this tomorrow.

Zhuinden commented 6 years ago

Please try with 0.3.2

lucamtudor commented 6 years ago

@Zhuinden I'll give it another try tonight. I've run into this while browsing the source code:

query.get().createQuery(realm).findAll(); // sort/distinct should be handled with new predicate type.

// paged results must be based on synchronous query!

Could you explain that?

Zhuinden commented 6 years ago

You see none of that from the outside world, it just means that the RealmQuery that is executed on the Monarchy looper thread is always executed with findAll(), and you can use Realm 5.x's realm.where<Blah>().sort("doh").distinct("meh") to define the query, so now you don't have to worry about either findAllSortedAsync or findAllSorted or findAllAsync().distinct() (which didn't even work :stuck_out_tongue: )

So the fact that they made sort + distinct be a property of the query itself is actually super-beneficial for the approach of Monarchy.

As for why it uses findAll() instead of findAllAsync(), it's because when I was making the predecessor of Paging integration in this repo, I was getting unexpected troubles with async queries. Can't really specify it more than that, but findAll works and findAllAsync did not work well in some cases sometimes.

Zhuinden commented 6 years ago

Although in retrospect, findAllAsync is needed for using the sync platform.

Zhuinden commented 6 years ago

Anyways, this should work.

lucamtudor commented 6 years ago

It's not working. I tested this with your example, what am I doing wrong?

public class PagedFragment
        extends BaseFragment {
    @BindView(R.id.recycler_view)
    RecyclerView recyclerView;

    @BindView(R.id.search_view)
    SearchView searchView;

    @Inject
    Monarchy monarchy;

    PagedDogAdapter pagedDogAdapter;
    LiveData<PagedList<Dog>> dogs;
    Monarchy.RealmDataSourceFactory<RealmDog> realmDataSourceFactory;
    DataSource.Factory<Integer, Dog> dataSourceFactory;
    Observer<PagedList<Dog>> observer = dogs -> {
        pagedDogAdapter.submitList(dogs);
    };

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        CustomApplication.getInjector(context).inject(this);
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_paged, container, false);
    }

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        ButterKnife.bind(this, view);
        searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
            @Override
            public boolean onQueryTextSubmit(String query) {
                onQueryChanged(query);
                return true;
            }

            @Override
            public boolean onQueryTextChange(String newText) {
                onQueryChanged(newText);
                return true;
            }
        });
        pagedDogAdapter = new PagedDogAdapter();
        recyclerView.setHasFixedSize(true);
        recyclerView.setLayoutManager(new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false));
        recyclerView.setAdapter(pagedDogAdapter);

        realmDataSourceFactory = monarchy.createDataSourceFactory(
                realm -> realm.where(RealmDog.class), true);
        dataSourceFactory = realmDataSourceFactory.map(input -> Dog.create(input.getName()));
        dogs = monarchy.findAllPagedWithChanges(realmDataSourceFactory,
                new LivePagedListBuilder<>(dataSourceFactory, 20));
        dogs.observeForever(observer); // detach != destroy in fragments so this is manual
    }

    private void onQueryChanged(String query) {
        if (realmDataSourceFactory != null) {
            Log.d("tag", "query: " + query);
            realmDataSourceFactory.updateQuery(realm -> realm.where(RealmDog.class).contains(RealmDogFields.NAME, query, Case.INSENSITIVE));
        }
    }

    @Override
    public void onDestroyView() {
        dogs.removeObserver(observer);
        super.onDestroyView();
    }
}
lucamtudor commented 6 years ago

@Zhuinden can you run a quick test yourself?

Zhuinden commented 6 years ago

I will, but only later today.

Zhuinden commented 6 years ago

@lucamtudor Sorry about "today" being such a remarkably vague time interval, but the good news is that I've fixed it and it's in 0.5.1 with https://github.com/Zhuinden/realm-monarchy/pull/24/commits/4737629778c997438404771da55d70f5d1f3bd1b

Now realmDataSourceFactory.updateQuery works as intended.

Zhuinden commented 6 years ago

So yeah, sorry for the delay, totally my mistake :disappointed_relieved: