Android Architecture Components: ViewModel keeps getting re-initialised - android

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()
}

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.

Can a coroutine scope leak an activity/fragment?

I need to save a EditText data into a file when to user exits the activity. I know I have plenty of options depending on how important saving that data for me. But for this particular case I'm ok that I can afford losing it so best-afford is fine.
We could maybe create a background service to decouple it entirely from the host activity. However,I was also thinking of creating a coroutine scope that might outlive the activity (i.e.: a singleton NonUIScope) and call the block like;
override fun onSaveInstanceState(bundle: ...) {
...
mInput = et.text.toString()
}
override fun onDestroy() {
NonUIScope.launch {
Downloader.saveData(mInput)
}
}
There might be slightly different implementations to achieve the same goal of course, for instance, using onSavedInstanceSate() instead of writing it into a file but my point is: if I use the ApplicationScope that I've created, could I potentially leak the activity (even there's no UI reference in the coroutine)?
As long as NonUIScope is not related in any way to your activity and also Downloader class doesn't hold an instance of your activity, it will not leak your activity.
E.g. if you use GlobalScope, the life time of the launched coroutine is limited only by the lifetime of the whole application, not by your activity.

Is there a data loss while switching to darkmode

When the Dark mode is enabled using the AppCompatDelegate.MODE_NIGHT_YES in kotlin, the activity or the fragment is recreated. If we are calling an API in the same class it will be recalled. Is there a way to eliminate recalling the API.
The ideal way to handle this is by using ViewModel.
As the official documentation says:
The ViewModel class is designed to store and manage UI-related data in
a lifecycle conscious way. The ViewModel class allows data to survive
configuration changes such as screen rotations.
What it means, is that your ViewModel will survive your UI recreation and there will be no data loss or API double calls.
Create your ViewModel class.
class MyViewModel : ViewModel() {
fun callAPI() {
// call you're API here
}
}
Just obtain your ViewModel once in Fragment's onCreate() lifecycle callback.
val model = ViewModelProviders.of(this)[MyViewModel::class.java]
As you want that your API call will be done onle once per screen, make it in the ViewModel's construcor (for example).
init {
callAPI()
}
Please read more about this here.

onRetainCustomNonConfigurationInstance deprecated in AndroidX

onRetainCustomNonConfigurationInstance was deprecated in AndroidX when the first version got released back in 2018! As the AndroidX Activity library says in the release notes:
onRetainCustomNonConfigurationInstance has been deprecated. Use a ViewModel for storing objects that need to survive configuration changes.
I just want a single object to survive configuration changes, this was the purpose of onRetainCustomNonConfigurationInstance!
For my use case, I want an instance of a Dagger graph to survive configuration changes in an Activity but using AAC ViewModel doesn't feel right to me for that use case. Dagger injects my ViewModels, I don't want to wrap my graph in another ViewModel just for the sake of making it survive configuration changes.
Is there any other way I can make an object survive configuration changes?
Using ViewModel is the most accurate and recommended way to make an object survive configuration changes, you should use it. You could've used onSaveInstanceState but that'd force all the objects needing to support Parcelable, that's not only reasonable, it's sometimes impossible.
To replace onRetainCustomNonConfigurationInstance, you can use the same APIs that support ViewModel in a way that handles all the complexity for you.
You can use this implementation of LongLastingElement API (code here) that uses ViewModel under the hood and removes all the boilerplate code to make an object survive configuration changes.
For your example, to make the Dagger graph survive configuration changes with this API, the code would look like this:
class LoginActivity : AppCompatActivity() {
private lateinit var loginComponent: LoginComponent
...
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
loginComponent = LongLastingElement.getInstance<LoginComponent>(this).getElement {
(applicationContext as MyDaggerApplication).appComponent.loginComponent().create()
}
loginComponent.inject(this)
}
}
LongLastingElement has a static method called getInstance where you specify the type of the object that needs to be stored and you pass a lifecycle owner in. Then, the getElement method is called, it receives a lambda as a parameter that needs to create an instance of the object you want to store.
Since the Lifecycle owner is used to obtain the LongLastingElement instance, the lambda that getElement takes as a parameter will be only called once. It'll create an instance of the object the very first time is called and the same instance will be reused after configuration changes and subsequent calls to getElement. This works for any lifecycle owner such as Activities and Fragments.

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.

Categories

Resources