[Feature Discussion]: Introducing MviBaseIOPresenter
See original GitHub issueMaybe we should add another Presenter for MVI that comes closer to the idea what Jake Wharton presented here:
http://jakewharton.com/the-state-of-managing-state-with-rxjava/
The idea is to have an Input / Output Presenter
something like:
/**
* @param <I> The type of Input events (type of intents)
* @param <O> The type of "Output" (type of ViewState)
*/
abstract class MviBaseIOPresenter<I , O> {
PublishRelay input();
BehaviorRelay output();
abstract void bindIntent(); // called only once the view is attached the very first time
void unbindIntent(); // called only once the view is destroyed permanently
}
Usage:
public class MyPresenter <InputEvent, MyViewState> {
@Inject BackendApi backend; // i.e. some Retrofit interface
@Override
public void bindIntent(){
input()
.switchMap( event -> {
if (event instanceof InputEvent.LoadingEvent)
return backend.loadFoo()
.map(MyViewState::content)
.startWith(MyViewState.loading())
.onErrorReturn(MyViewState::error);
else if (event instanceof InputEvent.PullToRefreshEvent)
return backend.loadFoo()
.map(MyViewState::content)
.startWith(MyViewState.pullToRefreshLoading())
.onErrorReturn(MyViewState::pullToRefreshError);
})
.subscribe(output);
}
}
public class MyActivity extends MviActivity<MyPresenter> {
private Disposable inputDisposable;
private Disposable outputDisposable;
@Inject MyPresenter presenter;
@Override
public MyPresenter createPresenter() {
return presenter;
}
@Override
public void onCreate(Bundle bundle){
super.onCreate(bundle);
// findViewById stuff and other view setup things
...
Observable <InputEvent> loadingEvent = Observable.just(new InputEvents.Loading()); // triggers loading immediately
Observable<InputEvent> pullToRefreshEvent = RxSwipeRefreshLayout(swiperefreshlayout)
.map( ignored -> new InputEvent.PullToRefreshEvent() )
.subscribe(presenter.input());
Observable<InputEvents> inputs = Observable.merge(loadingEvent, pullToRefreshEvent);
inputDisposable = inputs.subscribe(presenter.input());
outputDisposable = presenter.output.observeOn(AndroidSchedulers.main())
.subscribe(this::render);
}
private void render(MyViewState state) {
...
}
@Override
public void onDestroy(){
super.onDestroy();
inputDisposable.dispose()
outputDisposable.dispose();
}
}
This issue is just for brain storming and idea sharing.
Basically, Mosby will just be used to keep presenter through screen orientation changes. Since presenter.output
is a BehaviorRelay latest “ViewState” will be automatically resubmitted after screen orientation change.
Lifecycle Management of Disposables must be done manually.
We could provide such a Presenter, although I personally like more the current approach of MviBasePresenter
but you know … Jake Wharton is a smart guy … Maybe he (or someone else using Mosby) sees some advantages with this implementation … it’s definitely a little less “black magic” compared to MviBasePresenter
.
Any feedback is very welcome!
Issue Analytics
- State:
- Created 6 years ago
- Reactions:2
- Comments:7 (4 by maintainers)
Top GitHub Comments
I find it very strange if observables (like the above) are created on Activity/Fragment/View.
I can’t think how you can write tests if you have observables with operators like
.scan()
on activities. Current approach seems more testable to me.One of the most interesting part of the talk is the part with the Actions, transformers etc which can be useful for LCE. Τhis can lead Mosby for a new Redux module.
Thanks for your feedback!
Not sure if I have understood that correctly but right now you can do the same:
Then you would have just one Observable<Intent> where you pipe all your view Intent’s through. Nobody said you have to define an “intent-method” for each intent.
Not sure if that will solve that problem because
MviBaseIOPresenter
internally must have aBehaviorSubject
for the latest state. Otherwise you can’t keep the rx stream to your “business logic” alive if the view unsubscribes and resubscribes to presenter (i.e. during screen orientation changes). So this is not really solving your problem. To solve this problem you will have to tell the Presenter when to destroy the presenter and it’s internal stream permanently. You can do that in Mosby by providing your own MviDelegate like ActivityMviDelegate. For example the default one that uses Mosby out of the box is ActivityMviDelegateImpl which does that inonStart() <--> onStop()
. If you want to have something that operatesonStart() <--> onPause() <--> onResume() <--> onStop()
then you have to implement your own ActivityMviDelegate as this delegate is the component that is responsible to coordinate lifecycle of view and presenter. That is totally possible withMviBasePresenter
right now but as you see, you would have to write the a Delegate forMviBaseIOPresenter
too. So your described problem is independent from the Presenter implementation. It’s all about coordinating lifecylce of both, view and presenter, and that is the responsibility of the MviDelegate.Yes, that is correct.
Overall, yes, I will start to work on that soon.