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.
Related
I have some more complex logic for data provided by my ViewModel to the UI, so simply exposing the data via LiveData won't do the job for me. Now I've seen in the Android docs that I can implement Observable on my ViewModel to get the fine-grained control I need.
However in the documentation it also says:
There are situations where you might prefer to use a ViewModel
component that implements the Observable interface over using LiveData
objects, even if you lose the lifecycle management capabilities of
LiveData.
How intelligent is the built-in Android data binding? Will it automatically unregister it's listeners when necessary (e.g. on configuration changes where the View is destroyey) so that I don't have to care about the lost lifecycle capabilities? Or do I have to watch the Lifecycle of the View and unregister it's listeners? (=do manually what LiveData normally does for me).
How intelligent is the built-in Android data binding? Will it automatically unregister it's listeners when necessary (e.g. on configuration changes where the View is destroyey) so that I don't have to care about the lost lifecycle capabilities? Or do I have to watch the Lifecycle of the View and unregister it's listeners? (=do manually what LiveData normally does for me).
So I did some tests. I implemented androidx.databinding.Observable on my ViewModel and did a configuration change with the following log calls:
override fun removeOnPropertyChangedCallback(
callback: androidx.databinding.Observable.OnPropertyChangedCallback?) {
Log.d("APP:EVENTS", "removeOnPropertyChangedCallback " + callback.toString())
}
override fun addOnPropertyChangedCallback(
callback: androidx.databinding.Observable.OnPropertyChangedCallback?) {
Log.d("APP:EVENTS", "addOnPropertyChangedCallback " + callback.toString())
}
I saw that addOnPropertyChangedCallback was invoked for each time my viewmodel was referenced in a layout binding expression. And not once did I see removeOnPropertyChangedCallback invoked. My initial conclusion is that AndroidX databinding is dumb and does not automagically remove the listener.
FYI: the callback type was ViewDataBinding.WeakPropertyListener
However, I took a peek at ViewDataBinding.java source code and found that it is using Weak References to add the listener.
So what this implies, is that upon a configuration change, Android OS should be able to garbage collect your Activity/Fragment because the viewmodel does not have a strong reference.
My advice: Don't add the boilerplate to unregister the listeners. Android will not leak references to your activities and fragments on configuration changes.
Now, if you choose not to use LiveData, consider making your viewmodel implement LifecycleObserver so that you can re-emit the most recent value when your Activity/Fragment goes into the active state. This is the key behavior you lose by not using LiveData. Otherwise, you can emit notifications by using the PropertyChangeRegistry.notifyCallbacks() as mentioned in the documentation you shared at some other time. Unfortunately, I think this can only be used to notify for all properties.
Another thing... while I've not verified the behavior the source code seems to indicate that weak references are used for ObservableField, ObservableList, ObservableMap, etc.
LiveData is different for a couple of reasons:
The documentation for LiveData.observe says that a strong reference is held to both the observer AND the lifecycle owner until the lifecycle owner is destroyed.
LiveData emits differently than ObservableField. LiveData will emit whenever setValue or postValue are called without regard to if the value actually changes. This is not true for ObservableField. For this reason, LiveData can be used to send a somewhat "pseudo-event" by setting the same value more than once. An example of where this can be useful can be found on the Conditional Navigation page where multiple login failures would trigger multiple snackbars.
Nope. ViewModel will not unregister Observable subscription automatically. You can do it manually though. It is pretty easy.
Firstly you create CompositeDisposable
protected var disposables = CompositeDisposable()
Secondly, create your Observable(it may be some request or UI event listener) subscribe to it and assign its result to CompositeDisposable
disposables.add(
someObservable
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ data ->
// update UI or some ObservableFields for view/databinding
}, { exception ->
// handle errors here
})
)
The last thing you should do is to override ViewModel's method onCleared() like this:
override fun onCleared() {
super.onCleared()
disposables.clear()
}
This way all subscription added to your CompositeDisposable will be cleared automatically
Edit
I showed only the example. You may add triggers in onConfigurationChanged or onCreate or onResume to clear subscriptions as well - but it is dependent on specific usecases of an app. I gave just a general one.
Hope it helps.
DataBinding would not do the unregistering for you. Its simply help bind your layout file and the ViewModel. It is the viewModel that will protect you from device's configuration change. You still need to apply onSavedViewState() in your base activity or fragment as viewModel does not cover that. As per unregistering, LiveData does that.
As #Pavio already taught you how to create Observable, that is RxJava working. I would suggest using kotlin's coroutines and viewModel with LiveData to get the best out of your situation. Rx has a learning curve to it, although it does offer hundred of operators for all sorts of operations. If you really want to learn the kotlin way, look into kotlin flows and channels.
If i was in your place, I would solve my problem with ViewModels, LiveData and Coroutines.
I'm implementing an android application in MVP architecture.
I keep a reference to view inside my presenter and do time-consuming tasks such as loading from network inside my model.
My problem is that in each call inside my presenter which I want to call a method of View, it may happen that view is destroyed already and its reference is set to null inside presenter.
So when I received results from model, before each call like mView.updateUISomehow() in need to add if (mView!=null) since when control reaches to this point it may happen that mView is null.
I want to know are there any methods that I skip all null checking and handle all possible exception of presenter class in a class-wide exception handler.
P.S. I know about MVVM, LiveData and Room. I want to resolve this exact problem :)
BasePresenter<View>{
View view
updateUI(){
if(view != null)
callUI()
}
abstract callUI();
}
Your controller would have the knowledge of updateUI(), you may choose how to handle that
YourPresenter<ThatElusiveview> extends BasePresenter<ThatElusiveEview>{
callUI(){
// hoping this is not directly called from the controller!!
}
}
I faced the same problem when I was using MVP with too many UI update calls which will happen in the real scenarios. Well did the refactor to Jet pack. I understand your dilemma.
I believe it is doable if you provide Presenter with listener to view, so if view gets destroyed, Presenter will hold the communication from the controller toward View.
it does sound like checking the view != null, but you can have enumerations of different type of updates going from presenter to View. which you can put in one place to check and then direct them to respective update method depending on the type of enumeration action.
This will also help reading the code regarding different action that presenter is capable of sending to the View
I was wondering whether android binding is compatible with live data on conceptual level.
There is a simple task: call server after button is clicked.
So in my view I have
android:onClick="#{viewmodel::onOrderButtonClick}"
and proper onOrderButtonClick(View) method is defined in ViewModel.
But in order to make server call via LiveData I need my Fragment reference (observe() method needs LifecycleOwner instance as first parameter).
Of course I cannot hold reference to fragment in my ViewModel.
What is the pattern here? Do I really need to implement all event methods in the fragment class and delegate them back into view model class?
After some digging there is a bad news and a good one.
The bad news is that the fragment has to be used anyway (there is always some code in the fragment for each livedata event)
The good one is that it can be done relatively clean:
Call getOrderObservable() from fragment to view model. It returns
MutableLiveData<> created in view model's ctor.
Then call observe() on that observable In view model's onOrderButtonClick()
In onOrderButtonClick() in view model just call setValue()
That solution in my opinion minimalizes amount of code in the fragment. Still it looks not so elegant to separate making the network call and handling the result
Currently I'm working on project which is using RxJava together with RxBinding to observe views' changes. It's working really well for fragments and activities where we have easy access to life-cycle events - as it's recommended we bind to data streams on onResume and unbind on onPause.
Lately we've introduces RecyclerView, which display list of views and all of them can be data stream which we would like to subscribe to. The problem which I faced is passing CompositeSubscription object from activity/fragment through adapter down to view holders when they are created. Of course it doesn't work well ViewHolders won't be recreated when user leaves a screen and comes back (onPause and onResume are being called).
The solution would be to make adapter, layout manager (to access existing view holders) life cycle aware, but it require from us to write extra code to pass those subscriptions reference between logic layers.
However one of my colleagues proposed to use event bus, which would be used to pass Subscription in a event to activity/fragment, where they'll be added to CompositeSubscription and all of them will be unsubscribed all together. Moreover we can inform view holder to subsribe themself when user returns.
What do you think about this approach? Are there any pitfalls which I should be aware of in this approach?
Do not make your Views lifecycle aware. This violates separation of concerns.
Just use clickListeners upon viewBind in the Adapter.
Don't pass the Subscription to the adapter. (The adapter doesn't need to know about it, nor control it's lifecycle) The adapter could just offer an Rx-endpoint that you subscribe to in the (for example) Activity onStart and unsubscribe in onStop. Then Adapter can handle the marshalling of click events on items into an Observable.
Remember: You shouldn't apply RxJava to every problem.
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