Retrofit - ensuring observable calls always complete without a subscription - android

I have an app that consumes a restful API and I am using Retrofit with RxJava to achieve this. The UI uses the MVVM pattern, leveraging the Android Data Binding library. The view models are what make requests to the API by using a class called DataManager which delegates calls to the appropriate service to return an Observable which the view model then subscribes to and handles the response. So far, it's working absolutely fantastically except for one thing. The subscriptions are essentially bound to the view model which is in turn bound to a Fragment so when the user navigates away from the screen, all subscriptions (which I maintain in a CompositeSubscription) are unsubscribed to prevent memory leaks. Now, by nature of the way that the RxJavaCallAdapterFactory this means that the underlying Call gets cancelled, meaning that the API request is cancelled. Now in most cases this is no problem, if the user navigates away from the page that is still loading, cancelling the call makes sense. (I should note at this point that my Fragments retain their state across configuration changes so the issue only occurs once the fragment is completely destroyed due to the user navigating away from it). However there are certain cases in my app where I want calls to persist regardless of whether or not any subscriptions are subscribed to it.
After some research I see that observables have cache() and replay() methods that allow them to emit their data regardless of whether or not they are subscribed to. This is good, but it only solves half of the problem for me. The observables only exist in the view model, so once it's garbage collected the observables will be too, meaning that the calls die (surely? If I'm wrong about that please let me know). So I need a way of retaining references to cached observables and only removing them once they have fully completed their request.
My idea at the moment is to have a singleton class that would retain a collection of observables to be cached in memory. These observables would then remove themselves from the list when onComplete has been called.
public class ObservableCache {
private static final ObservableCache INSTANCE = new ObservableCache();
private final List<Observable<?>> cache;
public static ObservableCache instance() {
return INSTANCE;
}
public ObservableCache() {
cache = new ArrayList<>();
}
public void cacheObservable(Observable<?> observable) {
cache.add(observable.cache().doOnCompleted(() -> cache.remove(observable)));
}
}
I would just like to get some thoughts on how I should approach solving this issue as well as how viable my suggested approach is. I can't help but feel like there is something wrong with it, but I can't quite figure out what...

Related

Cannot perform this action after onsaveinstancestate after using liveData for fragments transition

this one question has been bothering me for 6 months, it is like a missing peace.. So, I really like LiveData and use it a lot, perhaps too much. Basically, all our fragments adding and removing is managed by LiveData. I have done it for several reasons:
We need to remove fragments in some cases, after onPause has occurred (odd, but a must for our use case).
We have only a single activity with fragments.
I have created a specific navigationViewModel which is shared across all fragments and is created in activity.
I add, remove fragments in this manner:
//ViewModel
...
val addFragmentNr3 = SingleLiveEvent<Boolean>()
//Activity or some fragment calls this:
navigationViewModel.addFragmentNr3.value = true
Then I observe LiveData in Activity and handling transition:
​
navigationViewModel.addFragmentNr3.observe(this, Observer { response ->
if (response != null) {
if (response) {
router.addFragmentNr3(supportFragmentManager)
}
}
})
Then router handles it:
fun addFragmentNr3(supportFragmentManager: FragmentManager) {
val fragmentNr3 = FragmentNr3()
supportFragmentManager.beginTransaction().replace(R.id.root_layout, fragmentNr3, FRAGMENT_NR_3.commit()}
In my honest opinion this should definitely prevent from this crash:java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
However, it does occur in our crash analytics.. It occurs rarely after more complex logic (like updating livedata after onActivityResult), but it does occur...
My main question is: Isn't it is a case, that LiveData handles such scenarios and would emit results only when it safe to perform operations? If not, it means my logic is bad and this approach is complete failure.
P.S. I would like to use navigation library, but as I said we have to manually remove some fragments after user goes to background, or uses split mode and etc.
LiveData does not know whether an action is safe to perform or not.
onSaveInstanceState() is called sometime before onStop() for Android version below P. So there is a small chance that the observer gets notified after onSaveInstanceState() is called.
According to doc, it turned out that onSaveInstanceState() should mark the lifecycle as CREATED and observers are not supposed to be called after onSaveInstanceState().
Suggestion on how to fix it.
One way is to use Android Navigation component and let it handle all of the fragment transaction.
If this is not feasible--like op's case--I suggests just using .commitAllowingStateLoss().
fun addFragmentNr3(supportFragmentManager: FragmentManager) {
val fragmentNr3 = FragmentNr3()
supportFragmentManager.beginTransaction().replace(R.id.root_layout, fragmentNr3, FRAGMENT_NR_3
.commitAllowingStateLoss()}
Now, if you search on the internet there will be dozens of articles warning how using .commitAllowingStateLoss() is bad. I believe these claims are no longer applicable to modern Android development where view restoration does not rely on saved bundles. If you are building an Android application with view models, you hardly need to rely on the Android framework to do the saving. In a proper MVVM application, the view should be designed in a way that it can restore its complete state based on its view models, and view models only.

RxJava unsubscribing in RecyclerView adapter presenter

I've got a presenter that is created for the RecyclerView.Adapter. The presenter has a method to call into the API layer, then change some local data values. This is implemented as an Rx chain:
public void doStuff(Object args) {
mRemote.doStuff(args)
.doOnNext(count -> mLocal.setStuffCount(count))
.firstOrError()
.subscribeOn(mSchedulerProvider.io())
.observeOn(mSchedulerProvider.ui())
.subscribe(...)
<...>
This method might be called several times with different arguments, and therefore generate different subscriptions. As I understand, RxJava will unsubscribe onComplete or onError. However, it is possible that there are, for example, 5 subscriptions created, and before they are resolved, the user moves on from the fragment, and therefore the Adapter is no longer relevant and should be collected. However, as I understand, it won't be collected cause of the active subscriptions, until those are resolved.
Normally I link the presenter with the related view, then store all subscriptions in a CompositeDisposable and clear the disposable when the lifecycle suggests it's time to clear (for example, a view goes to the onPause state). I could implement a similar approach here, however that means I would have to:
1) Establish a relationship between a view and the adapter, so that when the view's lifecycle method is triggered, it will take the adapter and invoke an unsubscribe method on it, which will then take the presenter and invoke clear(). Could I rely on onDetachedFromRecyclerView for that? Or would I have to do that manually?
2) Each time my presenter method to do stuff is called, I'll be creating a new subscription, which means the CompositeDisposable will grow in size until who know's what. Although the call has been executed, the disposable is still referenced in the CompositeDisposable.
I have a feeling there's a simpler solution here so hoping someone might point me at it.
EDIT:
Another solution that comes to mind, would be to create a presenter for each ViewHolder and link subscription to the binding and recycling of the holder. However, that means keeping a few more presenters hanging around - but the approach seems a bit cleaner.
Not sure if this is going to be useful to anyone but this is how I've solved this problem.
I ended up merging my Presenters while keeping different View interfaces for the ViewHolder and the Fragment (will call them ViewHolderView and FragmentView).
I liked the idea of having a Presenter for each ViewHolderView, however, it didn't really solve my problem. I was keeping my CompositeDisposable inside the Presenter, but with that approach, I had to move it out to the Adapter level and keep a reference to the Disposable inside my ViewHolder. Similar to the answer suggested here
I didn't like the idea of manipulating the Adapter from the Fragment lifecycle manually as I was already calling subscribe() and unsubscribe() on FragmentView, and onDetachedFromRecyclerView is not called unless you nullify the adapter on the RecyclerView
The solution was to merge my Adapter presenter with my Fragment presenter while keeping the ViewHolderView interface. So I ended up with a contract for one Presenter, and two separate View interfaces, and I'm passing the Presenter to the Adapter. When FragmentView unsubscribes, it cleans up everything.

RxJava with Presenter and retained fragment for configuration changes

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:
Initiates an activity with a headless retained fragment.
Push button
Presenter calls a FakeService for a delayed(5seconds) response observable.
Presenter does .cache() on this observable.
Presenter tells the view to retain this observable.
View saves the observable in a retained fragment.
Presenter subscribes to observable.
User does a configuration change(device rotation). User can do this as many times as he wants.
OnPause tells the Presenter's CompositeSubscription to clear and unsubscribes from all current subscriptions.
Activity gets recreated and reuses the existing retained fragment.
Activity's onResume checks if the retained fragment's stored observable is null.
If not null, tells the Presenter to subscribe to it.
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.
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.
Looks good, you can see that example - https://github.com/krpiotrek/RetainFragmentSample
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.
This library https://github.com/MaksTuev/ferro contains another way for store screens data and managing background tasks.
you scenario will looks like this
Open Activity, create presenter
Push Btn
Presenter calls a FakeService for a delayed(5seconds) response observable.
Configuration changed, presenter isn't destroyed, Observable isn't unsubscrubed, all rx event is frozen
Activity recreated, presenter reused, presenter show on view previously loaded data, all rx event is unfrozen
I think this help

RxJava resubscribe to event after activity restore

I'm still figuring out RxJava and using it to do some networking stuff with Retrofit 2. Been trying it our for a a couple days and like that the code looks more readable now but have come across an issue that I cant seem to figure a way around.
I am trying to perform a login (which returns an API token) and then use this token to fetch some initial data all in the same chain so that the output of the chain is the token + data. To do this I call my API service with a
apiClient
.login()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.flatMap(token -> getData(token))
.subscribe(new Subscrber<Bundle>() {...});
This seemed fine, but I also wanted to show a progress bar when starting and stopping the chain. So I added a .doOnSubscribe() and a .doOnUnsubscribe() to this as well. However I noticed that after orientation change the fragment that I was trying to hide the progress bar is always null.
So I searched and came across the RxLifecycle lib that seemed like it would help and I now .cache() and unsubscribe from the event chain. But I cant figure out how to subscribe to the same event again in onCreate() after this? I think I'm missing something pretty basic and would appreciate any help with this.
You don't necessarily have to use any architecture pattern to accomplish that. Though any MVP/MVC are nice things for concerns separation, testing etc, making your Controller/Presenter/DAO an application-wide singleton, which keeps up memory through whole application's life is not exactly a good idea.
Here's a sample project using retained fragment instance and RxJava - https://github.com/krpiotrek/RetainFragmentSample
The main idea there is to use Fragment with setRetainInstance(true) called, which protects it from being destroyed on orientation change, and store your Observable in there. Here's how you handle that in Activity/Fragment onCreate
protected void onCreate(Bundle savedInstanceState) {
if (savedInstanceState == null) {
// first run, create observable
mInfoObservable = createInfoObservable();
// set Observable in retained fragment
RetainFragmentHelper.setObject(this, getSupportFragmentManager(), mInfoObservable);
} else {
// following runs, get observable from retained fragment
mInfoObservable = RetainFragmentHelper.getObjectOrNull(this, getSupportFragmentManager());
}
// subscribe
mInfoObservable.subscribe(...);
}
Keep in mind that your Observable has to cache last value, one way is to use cache() operator.
You need to make sure that you subscribe to the same Observable instance that is returned from .cache(). Typically you would store this instance somewhere in a Singleton (like the Application class), a retained fragment or an Android Service.

Save multiple instances states of the same Activity in Android

I am developing an Android app and I would like to avoid reloading similar data when it comes from the same Activity using the same extra.
Specifically, when I launch my Activity 'A' with extra, I use this extra to load remote data from server.
From this Activity, I can relaunch 'A' with different extra and so on.
Example :
A:id1 --> A:id2 --> A:id3
But, it can also be an extra that I already loaded :
A:id1 --> A:id2 --> A:id3 --> A:id1
In this case, I wouldn't request the server again or lose the activities stack.
As I understand, "onSaveInstanceState" allows to save one instance of one Activity, but in my case, it's multiple instances of same Activity (with differents extras).
So, is it the solution to manage a list of saved instance states (bundle) for the same Activity ? Or something else ?
Thanks in advance
The onSaveInstanceState method isn't used in the way you describe. Check this out, it's the documentation for the Activity Class, specifically the Activity Lifecycle section. onSaveInstanceState is a method that gets called when the OS has to kill an Activity for some reason. It allows you to populate a Bundle which will help recreate that specific instance of the Activity where the user left off. Usually this happens because the user switched to a different app and the OS is killing the Activity to reclaim memory, but also happens on screen rotation, so it's a nuance of the platform that is important to at least be aware of.
As for your question, what I would do is use a database to store the information that is retrieved from the server. When you start an Activity, you can first check to see if the data that needs to populate that Activity exists in the database. If it does, load and display it from there, else make the server call.
This is nice, because the data will be persistent over multiple uses of the App. Going further, if the data from the server has the potential to be stale, you can easily extend this to display the data from the database initially, and fire off an asynchronous request for the data that will update both the UI and database when it returns. Your user will almost never be in a state where they're waiting for things to load, which is always a good thing!
Here's a good tutorial on the basics of implementing an sqlite database. This will also give you the added benefit of keeping the data stored over separate runs of your application.
As an alternative, if you don't really need the persistence or other features of the database and don't think that the overhead is worth it, you could create a Singleton class which keeps track of the data as its returned, perhaps implementing it using the Application class. It's important to note (and bringing us full circle) that any in-memory method of storing this data should be managed with onSaveInstanceState to ensure you don't lose any data if the Activity is killed at an unexpected time.
+1 for MattDavis' answer, but I'd also suggest you use what's known as the "singleton pattern".
Essentially, this is a way to cache things in memory. You create a class whose purpose is to hold all the database data you don't want to keep reloading. There's a single global instance of this object, which is initially null. You call a static method that returns that instance. If the static method finds that the instance is null, it creates the instance, populates it from the database, caches it, and returns it to the caller. From that point on, all requests for the instance just return the cached copy. If your app gets killed by the system for lack of resources, and started again later, it transparently re-creates the instance as needed.
This is a very common implementation, and works in a multi-threaded environment:
public class Singleton {
private static volatile Singleton instance = null;
private Singleton() {
// This is the place where you initialize the instance
// from the database.
}
public static Singleton getInstance() {
if (instance == null) {
synchronized(Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
I use this pattern in a lot of places; it's very handy.
To really do it right, you should also implement onLowMemory() or onTrimMemory() in your activities to release the singleton instance when resources are tight.

Categories

Resources