Observers added unexpectedly in LiveData - android

I am trying to implement Room and Livedata in my application.
I use LiveData to be updated on changes.
I have a ViewModel which returns a LiveData object (returned by Room via a Dao class) and I observe this LiveData in my Views.
I have made breakpoints in each place where I add an observer like that :
mSessionViewModel.sessionsList.observe(mActivity, Observer<List<Session>> { list ->
setSessionList(list!!)
})
However, when I am in debug and I check the mObservers variable of the LiveData object, I see that mIterators increase without the breakpoints where I have the creation of the observers reached (like above) :
Can anyone explain me this behavior? It seems that observers are added even if I don't create new ones...
It could eventually be the number of times a change in the LiveData has been detected but I wouldn't expect the list of observers to increase.

I experienced something similar causing multiple messages to pop up upon navigating back to the same fragment. I fixed it by making sure the binding takes place in onCreate rather than onCreateView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
initObservables()
}

Related

Will data binding unregister listeners from a ViewModel implementing Observable?

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.

ViewModel not getting cleared in Navigation navigate and live data in viewmodel stays alive

So, I have implemented single activity with multiple fragments pattern using Navigation. I used viewmodel for each fragment for non-ui operatios.
The problem is when you navigate using findNavController().navigate(), the fragment is not actually destroyed. Only the onDestroyView is called. So, the fragment's onDestroy never gets called and subsequently the viewmodel doesn't get cleared and so the LiveData observer also remains alive and when i come back to the fragment the observer is created again and so live data is observed twice. Once with the old data that it holds and second with the new data from some operations.
For example,
I have fragmentA and fragmentB
A shows a list and B you can add something which will be shown in the list. maybe fetching new data from api in fragment B to be shown in A.
So, when i go back from fragment B to A the observer gets called twice first with old data then second with the updated data. In the end the list shows correct data but I don't want two observers happening.
I have followed this article
https://medium.com/#BladeCoder/architecture-components-pitfalls-part-1-9300dd969808
and tried to use viewLifeCycleOwner instead of this but that does not help and issue still exists.
I also tried removing observer before observing :
vm.ld.removeObservers(this)
vm.ld.observe(viewLifeCyclerOwner, observer)
still the issue remains.
(I tried removing observer in onDestroyView also, still the issue remains.)
The only work around i found is manually calling the viewmodel onCleared in onDestroyView and clearing the livedata.
In fragment onDestroyView
vm.clear()
In viewmodel
fun clear() = onCleared()
override fun onCleared() {
//do stuff
}
Now, this solves my issue. But i feel this is not a solid solution and there can be a better way to do this. I would be glad if anybody can shed a light on this one. Thanks.
I wasted a couple of days troubleshooting a similar issue. Double check how your VM is initialized:
val myVm: MyViewModel by activityViewModels()
vs.
val myVm: MyViewModel by viewModels()
If you use the by activityViewModels() delegate, you are instructing Android to tie the lifetime of the VM to the host activity rather than the current Fragment. Thus, your VM won't be cleared even when the fragment is destroyed. I learned this the hard way. Switching back to the by viewModels() delegate leaves the VM scoped to the lifetime of the Fragment. When the fragment is destroyed, if it's the only observer, the VM will clear.
I was totally confused by the this vs. viewLifecycleOwner target when observing. Apparently, the choice of target has only to do with whether you intend to manually control presenting the dialog of a DialogFragment. Another gem of confusion.
In your case, if you're switching between fragments and onDestroy isn't being called, it could also be because fragments are being retained. For example, ViewPager2 has offscreenPageLimit, which instructs Android to keep hidden fragments in memory as you switch pages, adding even further to this mess of having to know absolutely everything about everything just to use the SDK.
You can use viewModelStore.clear() in your fragment, and then override onCleared() in ViewModel for dispose what your need
You can set the value of your livedata to null in onDestroyView.
example :
override fun onDestroyView() {
super.onDestroyView()
vm.ld.value = null
}
or
in ViewModel
fun resetLiveData() {
_ld.value = null
}
in Fragment
override fun onDestroyView() {
super.onDestroyView()
vm.resetLiveData()
}

Observer has null object, even though the object is instantiated right above

I have this function in my app:
fun bindUI() = launch(Dispatchers.Main){
val locationResults = locationViewModel.locationResponse
val owner = viewLifecycleOwner
locationResults.observe(owner, Observer {
if (it == null) return#Observer
// TODO: set loading icon to GONE
initRecyclerView(it.features.toLocationSearchResultListItem())
})
}
This function is triggered when a certain button is pressed in the app. When debugging the code, the locationResults field is set to a RoomTrackingLiveData object, but then when it gets to the if condition inside the observer, it is null, and it returns out of the function. In this case it is of type LocationSearchResponse! and locationViewModel.locationResponse is of type LiveData<out LocationSearchResponse>
Why would it be null right after results are correctly retrieved from that variable just a few lines before?
Dispatcher.Main may be the UI thread, but the UI thread by itself does not have a view lifecycle like Activities and Fragments.
You have to bind your viewmodel within a fragment or activity or viewLifecycleOwner is going to be null.
When you think about it it makes sense that this cannot work, because you could have say 5 fragments on screen at the same time. All of them run on the UI thread with seperate lifecycles. Witch of them do you propose viewLifecycleOwner should be referring to?
Since you said this runs on a button click. Just remove the coroutine and it should probably work.
There also is a lifecycle aware coroutine scope 'viewLifecycleOwner.lifecycleScope.launch' if you wish to use a coroutine and make it lifecycle aware. But I dont really see the point here. It is possible though and maybe there is a point and somebody can enlighten me. ^^
Seems like that's not the problem like #ianhanniballake suggested your LiveData's data might be null. You can get the data with the locationResults.value or when you debug your locationResults should have a field mData. That's is later your it.

How can I get SAM Interface's object in Kotlin

Suppose I am Observing a LiveData in a Fragment and I want to remove observer after I received the data.
eg:
val testLiveData = MutableLiveData<String>()
and Observing as:
testLiveData.observe(this, Observer<String> {
//TODO://Remove this Observer from here
//testLiveData.removeObserver(this)
})
How can I do that? Calling "this" is giving me an instance of the Fragment instead of the current Observer.
However, I can do like this.
testLiveData.observe(this, object : Observer<String>{
override fun onChanged(t: String?) {
testLiveData.removeObserver(this)
}
})
Is there any way to do the same in SAM?
In the first case you cannot access this since it is not guaranteed that every invocation of observe creates a new instance of Observer<String>.
If the lambda does not access any variable within the function where it is defined, the corresponding anonymous class instance is reused between calls (i.e., a singleton Observer is created that is used for every observe call).
Thus, for implementing listeners, the second variant (object : Observer<String>) should be used. This enforces that a new Observer is created every time observe is called, which in turn can then be accessed as this within its implemented methods.

Android Architecture Components: ViewModel keeps getting re-initialised

I have an activity that uses the ViewModel architecture component:
class RandomIdViewModel : ViewModel() {
var currentId : MutableLiveData<String?> = MutableLiveData()
init {
currentId.value = UUID.randomUUID().toString()
}
}
And then in my Activity I have this in the onCreate() method:
viewModel = ViewModelProviders.of(this).get(RandomIdViewModel::class.java)
viewModel.currentId.observe(this, idObserver)
Every time I rotate my phone the Id changes. So I am fairly confused as to why init is being called when I set the viewModel object.
EDIT
I have been looking at the saving state UI guidelines and it definitely appears that the ViewModel should maintain it's data throughout simple configuration changes:
ViewModel is ideal for storing and managing UI-related data while the user is actively using the application. It allows quick access to UI data and helps you avoid refetching data from network or disk across rotation, window resizing, and other commonly occurring configuration changes. ...
ViewModel is ideal for storing and managing UI-related data while the user is actively using the application. It allows quick access to UI data and helps you avoid refetching data from network or disk across rotation, window resizing, and other commonly occurring configuration changes
It appears that having a global variable in the activity that is stores a reference to the ViewModel as a once off causes the issue. All the examples seem to use the VM in a local variable, which doesn't work for me (I don't want my observers to be declared inline as it starts making the code quite messy1). The local variable seems to get a new instance every time a config change occurs. However if I create a method:
private fun viewModel() = ViewModelProviders.of(this).get(RandomIdViewModel::class.java)
and I call this whenever I need the VM. I think this is a bug that will most likely be resolved in the future.
1As a side note I also need to point out that I also had to remove my observers when the activity was not using them. This was another reason why I couldn't just inline the definition of the observers as they happen in different lifecycle events:
override fun onResume() {
super.onResume()
viewModel().currentId.observe(this, idObserver)
}
override fun onPause() {
viewModel().currentId.removeObserver(idObserver)
super.onPause()
}

Categories

Resources