1

I am working with Firestore and successfully integrated it with Paging Library using ItemKeyedDataSource. Here is a gist:

public class MessageDataSource extends ItemKeyedDataSource<Query, Message> {

    //... private members

    MessageDataSource(Query query) {
        mQuery = query;
    }

    @Override
    public void loadInitial(@NonNull LoadInitialParams<Query> params, @NonNull LoadInitialCallback<Message> callback) {
        mLoadStateObserver.postValue(LoadingState.LOADING);
        mQuery.limit(params.requestedLoadSize).get()
                .addOnCompleteListener(new OnLoadCompleteListener() {
                    @Override
                    protected void onSuccess(QuerySnapshot snapshots) {
                        getLastDocument(snapshots);

                        // I'm able to get the values here
                        List<Message> m = snapshots.toObjects(Message.class);
                        for (Message message : m) {
                            Log.d(TAG, "onSuccess() returned: " + message.getTitle());
                        }

                        callback.onResult(snapshots.toObjects(Message.class));
                    }

                    @Override
                    protected void onError(Exception e) {
                        Log.w(TAG, "loadInitial onError: " + e);
                    }
                });
    }

    @Override
    public void loadAfter(@NonNull LoadParams<Query> params, @NonNull LoadCallback<Message> callback) {
        Log.d(TAG, "LoadingState: loading");
        mLoadStateObserver.postValue(LoadingState.LOADING);
        params.key.limit(params.requestedLoadSize).get()
                .addOnCompleteListener(new OnLoadCompleteListener() {
                    @Override
                    protected void onSuccess(QuerySnapshot snapshots) {
                        getLastDocument(snapshots);
                        callback.onResult(snapshots.toObjects(Message.class));
                    }

                    @Override
                    protected void onError(Exception e) {
                        Log.w(TAG, "loadAfter onError: " + e);
                    }
                });
    }

    private void getLastDocument(QuerySnapshot queryDocumentSnapshots) {
        int lastDocumentPosition = queryDocumentSnapshots.size() - 1;
        if (lastDocumentPosition >= 0) {
            mLastDocument = queryDocumentSnapshots.getDocuments().get(lastDocumentPosition);
        }
    }

    @Override
    public void loadBefore(@NonNull LoadParams<Query> params, @NonNull LoadCallback<Message> callback) {}

    @NonNull
    @Override
    public Query getKey(@NonNull Message item) {
        return mQuery.startAfter(mLastDocument);
    }


    /*
     * Public Getters
     */
    public LiveData<LoadingState> getLoadState() {
        return mLoadStateObserver;
    }

    /* Factory Class */
    public static class Factory extends DataSource.Factory<Query, Message> {

        private final Query mQuery;
        private MutableLiveData<MessageDataSource> mSourceLiveData = new MutableLiveData<>();

        public Factory(Query query) {
            mQuery = query;
        }

        @Override
        public DataSource<Query, Message> create() {
            MessageDataSource itemKeyedDataSource = new MessageDataSource(mQuery);
            mSourceLiveData.postValue(itemKeyedDataSource);
            return itemKeyedDataSource;
        }

        public LiveData<MessageDataSource> getSourceLiveData() {
            return mSourceLiveData;
        }
    }
}

And then within MessageViewModel class's constructor:

MessageViewModel() {
    //... Init collections and query

    // Init Paging
    MessageDataSource.Factory mFactory = new MessageDataSource.Factory(query);
    PagedList.Config config = new PagedList.Config.Builder()
            .setPrefetchDistance(10)
            .setPageSize(10)
            .setEnablePlaceholders(false)
            .build();

    // Build Observables
    mMessageObservable = new LivePagedListBuilder<>(mFactory, config)
            .build();

    mLoadStateObservable = Transformations.switchMap(mMessageObservable, pagedListInput -> {
        // No result here
        Log.d(TAG, "MessageViewModel: " + mMessageObservable.getValue());
        MessageDataSource dataSource = (MessageDataSource) pagedListInput.getDataSource();
        return dataSource.getLoadState();
    });
}

Note the situation:

  • When I'm initializing the viewmodel in MainActivity#oncreate method and observing it, it is working as intended and is able to view it in recyclerview.

  • Later I decided to create a Fragment and refactored it by moving all the logic to the Fragment and when I try to observe the same livedata, no values are returned. Here's how I doing it.

Within Fragment:

public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // ...
    mViewModel = ViewModelProviders.of(getActivity()).get(MessageViewModel.class);
}

public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    //...
    mViewModel.getMessageObserver().observe(this, messages -> {
        Log.d(TAG, "onCreateView() returned: " + messages.size());
    });
    mViewModel.getLoadingStateObserver().observe(this, loadingState -> {
        Log.d(TAG, "onCreateView() returned: " + loadingState.name());
    });

    return view;
}

The interesting part:

  • Within the Fragment the loadstate is returning the values LOADING and SUCCESS
  • Within MessageDataSource, values of the query is successfully returned but while observing the same in the Fragment, I get no values.

What am I doing wrong here?

P.S: I'm learning Android.

Rajarshi
  • 2,419
  • 3
  • 23
  • 36
  • Use *this* instead of getActivity(). So your code will be looks like ==> mViewModel = ViewModelProviders.of(this).get(MessageViewModel.class); – Ram Prakash Bhat Aug 17 '18 at 06:21
  • Tried this, didn't worked, also, I need this as I'll be implementing a `Listener` to communicate with another fragment. – Rajarshi Aug 17 '18 at 07:37
  • @Rajarshi If you need to pagination in real-time, **[this](https://stackoverflow.com/questions/50741958/how-to-paginate-firestore-with-android)** is a recommended way in which you can paginate queries by combining query cursors with the limit() method. I also recommend you take a look at this **[video](https://www.youtube.com/watch?v=KdgKvLll07s)** for a better understanding. – Alex Mamo Aug 17 '18 at 09:15
  • @AlexMamo But it's not in realtime, and I'm getting the results as expected, see in `loadInitial` method. The only thing is that while I can get the results in Activity, I cannot do the same within the fragment. Within Fragment, PagedList of Messages is not returned while network states are returned, strange. And I not so good at debugging this, :(. – Rajarshi Aug 17 '18 at 09:57

2 Answers2

0

With fragments a few problems can occur. Observers are set in onActivityCreated() to ensure the view is created and change 'this' in the observe statement to 'getViewLifecycleOwner()'. This prevent observers from firing more than once after the fragment is popped of the backstack for example. You can read about it here. So change your observer to:

mViewModel.getLoadingStateObserver().observe(getViewLifecycleOwner(), loadingState -> {
    Log.d(TAG, "onCreateView() returned: " + loadingState.name());
});
KvdLingen
  • 1,230
  • 2
  • 14
  • 22
0

The example code showed on Share data between fragments is bare minimal and just looking at it I got the wrong overview until I read this part very carefully:

These fragments can share a ViewModel using their activity scope to handle this communication, as illustrated by the following sample code:

So basically you have to initialize the viewmodel in the Activity: ViewModelProviders.of(this).get(SomeViewModel.class);

And then on the Activity's fragment you can initialize it as:

mViewModel = ViewModelProviders.of(getActivity()).get(SomeViewModel.class);

mViewModel.someMethod().observe(this, ref -> {
   // do things
});

This is what I was doing wrong and now it's fixed.

Rajarshi
  • 2,419
  • 3
  • 23
  • 36