Search code examples
androidmosby

Mosby MVI: Inconsistent intent binding behavior


I'm using the new Mosby MVI library for a new demo app. When defining intents in a presenter it is inconsistent when the intent is triggered/emitted when the view is attached.

For example: Let's define very simple intent in an activity

public Observable<Boolean> intentLoadData(){
  return Observable.just(true);
}

The presenter binds the intent like:

@Override
protected void bindIntents() {
  Observable<MailListViewState> loadData = intent(ExampleViewContract::intentLoadData).flatMap(interactor::loadData)
            .observeOn(AndroidSchedulers.mainThread());
  subscribeViewState(loadData, ExampleViewContract::render);
}

This intent works just fine. When navigating to a different activity (detail view) and navigating back, bindIntents()is called the the intent is recreated. intentLoadData()doesn't emit a new item and the MviBasePresenter will provide the previous ViewState using the internal BehaviorSubject.

My problem is: When I slightly adjust the Intent (for reloading the data). The intent starts to emit an item when the View is reattached.

So lets change the intent to:

private PublishSubject<Boolean> mReloadDataSubject = PublishSubject.create();

private void reloadData(){
  mReloadDataSubject.onNext(true);
}

public Observable<Boolean> intentLoadData(){
  return mReloadDataSubject.startWith(true);
}

No when navigating to a new activity and back. The intent emits a new item when the view is reattached. In my case this results in a new APU call to the backend to reload the data, rather than reusing the last ViewState. This happens even when reloadData() is never called.

This behavior feels very inconsistent. How can I feel more in control when an intent is triggered during reattaching the view?

Update: For me even more interesting is, how do I avoid the automatic emitting of the intents when reattaching, without completing the Observable. With the introduction of a PublishSubject, the activity will reload the entire data even when just rotating.


Solution

  • To answer my own question and to wrap up the comments, this is my solution:

    At first we have to understand how Mosby3 MVI restores a view, e.g.: after rotation, navigating forth and back to different views. Mosby3 retains the instance of the presenter. When a new instance of the view is created, the presenter will be restored and will be attached to the view. onStart()of the new view, the presenter will update the intents. Hence, the new view creates new intents and the presenter will subscribe to them using PublishSubjects.

    If the intent of the previous view emitted onComplete()the PublishSubjectis completed as well and the stream closes. The (interactor) logic bound to this intent will be unsubscribed. Therefore, this intent can't be triggered by view anymore.

    In the example of the original question. Observable.just(true) closes the stream. Even when the view and it's intents are recreated (after rotation), there is no new item emitted. mReloadDataSubject.startWith(true) instead doesn't emit onComplete() and the stream isnt closed. When the presenter resubscribes to that intent (after rotation), the intent emits thestartsWith(true)`. In the example, this is causing a complete reload of the data on every rotation.

    In order to trigger the intents on a conditional reloading RxNavi can be very helpful.

    public Observable<Boolean> intentReloadData() {
         //check if the data needs a reload in onResume()
         return RxNavi.observe(this, Event.RESUME)
                      .filter(ignored -> mNeedsReload == true)
                      .map(ignored -> true);
    }