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.
Related
Let's imagine the following case: we have BottomNavigationView with Navigation Component and MVVM architecture.
In which case in Fragment this line of code will now work
onViewCreated()
viewModel.isActionDone.observe(viewLifecycleOwner) {
doReaction()
}
but this code will?
private val observer = Observer<Boolean> {
doReaction()
}
onViewCreated()
viewModel.isActionDone.observe(this, observer)
In my app, the second code works great, but the first code doesn`t work correctly: when I move to another element in the BottomNavigationView, the observer is called several times.
P.S. this code also calling observer several times.
viewModel.isActionDone.observe(viewLifecycleOwner, observer)
Сould you please help me understand what could be wrong with the first code?
it's because every time onStart() is called on your fragment, the livedata becomes active again and emits the last value again. you should use the Event class as a wrapper or you could use the SingleLiveEvent class which is now considered an anti pattern.
you could read this for more information: https://medium.com/androiddevelopers/livedata-with-snackbar-navigation-and-other-events-the-singleliveevent-case-ac2622673150
It's because When you move to another fragment, NavController destroy the view and stop the fragment and when you back to the first fragment, fragment will be started and view will be created again.
So when you're using "viewLifecycleOwner" for observe, your LiveData emit the last value each time when you back to the first fragment. but when using "this", it does not.
So you should think about your business that you need have emits each time your view created or each time fragment created and use suitable LifecycleOwner.
I home it help you.
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 was reading how to use coroutines here https://developer.android.com/topic/libraries/architecture/coroutines. What makes me confused about is the difference between LiveDataScope and ViewModelScope. It sounds like ViewModelScope takes care of lifecycle automatically and you can do network request in the block. When data received from server, post the value to livedata. but then when I continued to read, there's another topic about LiveDataScope which seems redundant to me since you can already accomplish the same result by using ViewModelScope with livedata. What is the main difference between those two? and when should I choose to use one over the other?
Note: This might be late answer for this topic if Author of OP already has understanding about this, But providing some pointers for the referencing comment of #IgorGanapolsky.
Let's see what is the main difference between viewModelScope & LiveDataScope
1. viewModelScope:
Official doc says that, CoroutineScope tied to this ViewModel. This
scope will be canceled when ViewModel will be cleared, i.e
ViewModel.onCleared is called
Meaning that coroutine scope is tied to ViewModel, and once ViewModel gets cleared this scope gets destroyed by cancelling all child coroutine jobs.
Basically, in MVVM pattern we use ViewModel tied to a particular Activity/Fragment. So once that Activity/Fragment gets destroyed, its ViewModel reaches a cleared state. Thus, it cancels all incomplete jobs started by viewModelScope, throwing CancellationException.
So a usecase of viewModelScope is: inside ViewModel when you've got any suspended function to be called and need a CoroutineScope, inspite of making new one you can directly use this one out of the box from viewodel-ktx library.
class SomeViewModel: ViewModel() {
fun someFunction() {
viewModelScope.launch {
callingSomeSuspendedFun()
callingAnotherSuspendedFun()
}
}
}
Note that you don't need to explicitly override onCleared() method of ViewModel to cancel the scope, it does automatically for you, cheers!
2. LiveDataScope:
Now speaking of LiveDataScope, it's actually an interface provided to build better support for LiveData/CoroutineLiveData that can have CoroutineScope out of the box! use livedata-ktx version
Now imagine a situation that you're having a MVVM pattern and wanted to return LiveData from repository to view model. your repository also contains some suspended functions and some coroutine scope.
In that situation when you do some suspended method calls & return the result as live data, there would be some extra work. you'll need transform your data to particular live data after getting it as result. see the example below:
class SomeRepository {
suspended fun someApiCall() : LiveData<Result> {
val result = MutableLiveData<Result>()
someCoroutineScope.launch {
val someData = someOtherCallToGetResult()
result.postValue(someData)
}
return result
}
}
Imagine you had to write above code block due to LiveData didn't had any support for Coroutines ... but until now!
Now you can directly use liveData { } function that returns you LiveData object giving you scope of LiveDataScope in such a way that you can continue your suspended work and emit the result at the same level rather than getting it messy way like above. So above code block can now optimized by following code or better:
class SomeRepository {
suspended fun someApiCall() : LiveData<Result> {
return liveData<Result> {
val someData = someOtherCallToGetResult()
emit(someData)
}
}
}
So use case of liveData would be at repository level when using MVVM pattern if you expose LiveData to viewmodel from respository rather than creating new inside viewmodel. Please note that there's no thumb rule about liveData method shouldn't be used at viewmodel directly. You can if you want to avoid viewModelScope completely.
TL;DR
Check out the liveData method,
Doc states that, The liveData building block serves as a
structured concurrency primitive between coroutines and LiveData. The code block starts executing when LiveData becomes
active and is automatically canceled after a configurable timeout when
the LiveData becomes inactive. If it is canceled before completion,
it is restarted if the LiveData becomes active again. If it
completed successfully in a previous run, it doesn't restart. Note
that it is restarted only if canceled automatically. If the block is
canceled for any other reason (e.g. throwing a
CancelationException), it is not restarted.
I hope that make sense!
The names imply what they actually are:
A ViewModelScope is defined for each ViewModel in your app. Any
coroutine launched in this scope is automatically canceled if the
ViewModel is cleared.
This means that you can do some tasks(like continuous processing) in a coroutine that is in the scope of the ViewModel. The advantage is that you don't have to care anymore when the ViewModel will be stopped to stop your coroutine (this is a big pain when working with global things like java threads). The lifecycle of the ViewModel is related to when an activity is ended.
The LiveDataScope is used for emitting values in the scope of a LiveData object. This means that as long as the LiveData object is alive and there are subscribers that coroutine will work, however once all the subscribers are out the coroutine will stop. This coroutine also restarts once the LiveData is active again.
Basically these are 2 coroutine contexts each responsible for the lifecycle of its element.
PS:
It sounds like ViewModelScope takes care of lifecycle automatically
and you can do network request in the block.
First of all, network requests cannot be done from the Main thread, you usually do them from IO scope, you can read more here. The second thing is that you should take a look at the lifecycle of the ViewModel compared to Activity if you want to understand why LiveDataScope is usually combined with ViewModelScope, you can read about that here.
The short answer to your question is that you cannot be sure that the view is created from the ViewModelScope so if you want to push some updates to UI you should push them as long as someone is subscribed to LiveData, this is where the LiveDataScope comes into play.
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.
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()
}