9

I'm new to RxJava and using this together with MVP architecture.

I've found a few examples on saving observables upon configuration changes using a retained fragment(still not sure if this is the best way to do it). The examples I've found though is handling observables directly on the Activity or Fragment, not from a Presenter.

So I experimented and set up this quick example(using only Reactivex's RxJava and RxAndroid lib) just to test, which seems to work fine. What this example does is:

  1. Initiates an activity with a headless retained fragment.
  2. Push button
  3. Presenter calls a FakeService for a delayed(5seconds) response observable.
  4. Presenter does .cache() on this observable.
  5. Presenter tells the view to retain this observable.
  6. View saves the observable in a retained fragment.
  7. Presenter subscribes to observable.
  8. User does a configuration change(device rotation). User can do this as many times as he wants.
  9. OnPause tells the Presenter's CompositeSubscription to clear and unsubscribes from all current subscriptions.
  10. Activity gets recreated and reuses the existing retained fragment.
  11. Activity's onResume checks if the retained fragment's stored observable is null.
  12. If not null, tells the Presenter to subscribe to it.
  13. The retained observable gets subscribed to, and because .cache was called, it just replays the result to the new subscriber without calling the service again.
  14. When the Presenter shows the final result to the view, it also sets the retained fragment's saved observable to null.

I'm wondering if I'm doing this properly, and if there's a more efficient or elegant way to handle configuration change when the observable's subscription is being handled in a Presenter?


Edit: Thanks for the feedback. Based on this I've reached what I think is a cleaner solution, and I've updated my linked example with the changes.

With the new change; instead of passing the Observable from the Presenter to the Activity to the retainedFragment to be stored incase of a configurationChange event, I rather set the retainedFragment as a second "view" to the Presenter when it's created.

This way when onResume() happens after device rotation, I don't need to make the Activity do the ugly plumbing of passing the Observable from the retainedFragment back to the Presenter.

The Presenter can just interact with this second "view" directly and check for the retained observable itself and resubscribe if needed. The main Activity no longer needs to know about this observable. Suddenly it's a much simpler view layer.

Chad Bingham
  • 32,650
  • 19
  • 86
  • 115
jesobremonte
  • 3,198
  • 2
  • 22
  • 30
  • Hi there, this is very interesting approach indeed. I cloned your project and I'm playing with it. I have followed another approach that handles orientation changes that make use of Loaders. I was curious though into how you handle the state in the presenter. And particularly, when the service finishes and the activity is still in retaining process. Which part handles the presenter state? Could you please explain that part a little bit? Also, does this approach work for activities and fragments? And finally, are there any pitfalls following this approach? Thank you. – Jin Aug 22 '16 at 14:10
  • With this approach, we can replay results from the retained observable, which means that no data is lost, however replaying all the data from onResume() means that the state of the ui is restarted every time there is a configuration change. This is most obvious when adding data received from the observable to a recyclerview and scrolling through the list, and when we have a configuration change, the adapter will be created again and data will be re-added to the adapter, and we will start from the top of the recyclerview again. TL;DR, unable to save scroll position. – desmondtzq Oct 18 '16 at 07:14

3 Answers3

3

Looks good, you can see that example - https://github.com/krpiotrek/RetainFragmentSample

krp
  • 2,247
  • 17
  • 14
  • Thanks for the example. Though I was looking for a way to achieve this with a Presenter, not directly done by Activity. – jesobremonte Jun 02 '16 at 21:21
  • 1
    Yes, but onSaveInstanceState and onStart/onCreate/onResume are Activity/Fragment callbacks so you will have to handle that anyway. You may of course delegate logic to presenter or anywhere else but still Activity/Fragment is the starting point. Here's another example - https://github.com/krpiotrek/MvpState where you can see how to pass state to Presenter. It's done via Dagger but it can be any kind of dependency injection as well. – krp Jun 03 '16 at 06:58
  • Ofcourse. The activity/fragment will tell the Presenter when it's time to unsubscribe/resubscribe depending on the view's state. I want to keep the presenter clean of any android-specific lifecycle methods though. What I did is when the presenter is created, it will also have the retainedFragment's interface as a constructor as a second "view". This way it keeps the presenter from needing know what the activity/fragment state is. That is android specific and should stay on the view layer. – jesobremonte Jun 03 '16 at 10:27
  • What I meant was scenario like that : 1) call presenter.saveState() : State to get state object in onSaveInstanceState method 2) Persist State with retain instance or bundle (depends what State has) 3) pass State object to presenter as constructor param in onCreate/onViewCreated method – krp Jun 05 '16 at 11:25
2

Sounds about right, good job! Some suggestions:

  • You could just use Activity.onRetainNonConfigurationInstance(). I've heard it's getting un-deprecated in Android N. You can continue to use retained fragment if you like it, there's no problem with that, but you don't have to if you preferred not to use fragments.
  • Why only retain the observable and not the whole presenter? It seems maybe a bit wasteful to create a new presenter, maybe you can make it work with same instance that can "attach" and "detach" a view. But then again you have to deal with what to do if your observable emits while you are detached from any views, so maybe that's good enough.
  • Dan Lew recently made a case in his Droidcond SF talk that you shouldn't use cache(). He says replay() gives you greater control over what's happening and replay().autoconnect() works the same as cache(). He convinced me, but see for yourself.
Marcin Koziński
  • 10,835
  • 3
  • 47
  • 61
  • Thanks for the valuable feedback! I may come back to reconsider Activity.onRetainNonConfigurationInstance() if the final N api keeps it undeprecated. For now it feels a little bit too heavy to be the solution in my opinion. I also watched the video you linked, it's very helpful! Convinced me too, and did the change. I've updated my example code to what I think is a better solution, which lets the presenter store the observable on the retainedFragment directly without having to go through the Activity again. – jesobremonte Jun 02 '16 at 20:41
  • I'm glad I helped! :) I'm not sure why `onRetainNonConfigurationInstance()` would feel heavier than a retained fragment. The latter is implemented using the former. Though I agree that it makes sense to wait and see if it's undeprecated in the final release. – Marcin Koziński Jun 02 '16 at 22:00
-1

This library https://github.com/MaksTuev/ferro contains another way for store screens data and managing background tasks.

you scenario will looks like this

  1. Open Activity, create presenter

  2. Push Btn

  3. Presenter calls a FakeService for a delayed(5seconds) response observable.

  4. Configuration changed, presenter isn't destroyed, Observable isn't unsubscrubed, all rx event is frozen

  5. Activity recreated, presenter reused, presenter show on view previously loaded data, all rx event is unfrozen

    I think this help

Community
  • 1
  • 1
Maks Tuev
  • 31
  • 4