Are ViewModels independent of activity/fragment lifecycles or just their configuration changes. When will they cease to exist and the subsequent onCleared() method called.
Can the viewModel be shared with another Activity ?
A situation:
Activity1+viewModel1--->(rotation)--->Activity1+viewModel1
--->(launch Intent)--->Activity2+viewModel1
is this sharing possible and is it a good practice.
Also, since the app lifecycle callbacks, onPause->onStop->onDestroy is same for both
1.activity rotating and
2.when an Activity ends,
how is a ViewModel figuring out internally the right time to call onCleared and finally end its lifecycle.
Findings:
the ViewModel uses a holderFragment internally to hold an instance of the activity and uses the setRetainInstance method like fragments to account for configuration changes.
Source: dive-inside-of-androids-viewmodel-architecture-components
Are ViewModels independent of activity/fragment lifecycles or just
their configuration changes.
ViewModels (VMs) are independent of configuration changes and are cleared when activity/fragment is destroyed.
Following is the lifecycle of ViewModel from official site:
Can the viewModel be shared with another Activity ?
You shouldn't do that with Activities. However fragments can share a ViewModel using their activity scope to handle communication between them
How is a ViewModel figuring out internally the right time to call onCleared and finally end its lifecycle?
A VM's onCleared is called when the app is put into the background and the app process is killed in order to free up the system's memory.
See the Do ViewModels persist my data? section from this Android Developer's post, ViewModels: Persistence, onSaveInstanceState(), Restoring UI State and Loaders
If you want the user to be able to put the app into the background and then come back three hours later to the exact same state, you should also persist data. This is because as soon as your activity goes into the background, your app process can be stopped if the device is running low on memory.
If the app process and activity are stopped, then the ViewModel will be cleared as well.
Check method onDestroy() in Fragment.java
public void onDestroy() {
this.mCalled = true;
FragmentActivity activity = this.getActivity();
boolean isChangingConfigurations = activity != null && activity.isChangingConfigurations();
if (this.mViewModelStore != null && !isChangingConfigurations) {
this.mViewModelStore.clear();
}
}
The variant isChangingConfigurations is true when the Activity rotates, the viewModelStore method clear() is not called.
When Activity is destroyed, isChangingConfigurations is false, the viewModelStore will be cleared.
Through the source code, we know the ViewModel binds with HolderFragment. you can from the code in class ViewModelProviders to find it.
#MainThread
public static ViewModelProvider of(#NonNull FragmentActivity activity,
#NonNull Factory factory) {
checkApplication(activity);
return new ViewModelProvider(ViewModelStores.of(activity), factory);
}
next, in-class HolderFragment on it's onDestroy() you can find
#Override
public void onDestroy() {
super.onDestroy();
mViewModelStore.clear();
}
Last, open it,
public final void clear() {
for (ViewModel vm : mMap.values()) {
vm.onCleared();
}
mMap.clear();
}
now, maybe you have know it. just like the picture above. When the fragment finished, it cleared; when activity recreate,the fragment's onDestroy() will not be invoked, because
public HolderFragment() {
setRetainInstance(true);
}
hope it can help you.
If you follow the trail (Check super class)
AppCompatActivity --> FragmentActivity --> ComponentActivity
ComponentActivity observe the lifecycle state.
onDestory() calls at configuration change (such as screen rotation) but viewModel doesn't get destroy because of the following condition.
getLifecycle().addObserver(new GenericLifecycleObserver() {
#Override
public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
if (event == Lifecycle.Event.ON_DESTROY) {
if (!isChangingConfigurations()) {
getViewModelStore().clear();
}
}
}
});
I wanted my VM's onClear to be called when the Activity was finishing. I use onPause, because the call to onDestroy is not always immediately executed...it could be a few seconds after onPause:
class SomeActivity : AppCompatActivity() {
override fun onPause() {
super.onPause()
// viewmodel is not always cleared immediately after all views detach from it, which delays
// the vm's cleanup code being called, which lets the resources continue running
// after all UIs detach, which is weird, because I was using timers and media players.
// this makes the VM execute onCleared when its Activity detaches from it.
if (isFinishing) {
viewModelStore.clear()
}
}
}
And here is the order of execution in respect to the GenericLifecycleObserver:
onStateChanged()
onResume()/onDestroy()/etc.
Meaning the observer received the information about the pending state change before it's completed, so for exemple onDestroy() method is finished.
Related
How can viewModel retains data due to configuration changes but not when we try to re instantiate the activity.
ViewModel objects are scoped to the Lifecycle passed to the ViewModelProvider when getting the ViewModel. The ViewModel remains in memory until the Lifecycle it's scoped to goes away permanently
viewModel = ViewModelProviders.of(this)[MainActivityViewModel::class.java]
Here this is the lifecycle owner MainActivity.
Before rotation/ config change:
After rotation/ config change:
We can clearly see here that instance of activity (owner) and lifecycle are changing after rotation. So why its saving the data only in configuration changes. Meanwhile when i tried creating a new instance of same activity manually to re create this scenario the view model is not retaining the data.
What are the deciding parameters for view model to retain the data or not.
And why viewModel retains the data only for config changes and not for something like new instance of same activity.
There is an observer set on the lifecycle of activity/ fragment inside ComponentActivity constructor.
getLifecycle().addObserver(new LifecycleEventObserver() {
#Override
public void onStateChanged(#NonNull LifecycleOwner source,
#NonNull Lifecycle.Event event) {
if (event == Lifecycle.Event.ON_DESTROY) {
// Clear out the available context
mContextAwareHelper.clearAvailableContext();
// And clear the ViewModelStore
if (!isChangingConfigurations()) {
getViewModelStore().clear();
}
}
}
});
ComponentActivity is the parent class of Fragment and AppCompactActivity.It'll be triggered everytime a lifecycle call back is made and if its onDestroy() callback and if its not a configuration change only then it will clear the viewModelStore.
if (!isChangingConfigurations()) {
getViewModelStore().clear();
}
so the deciding parameter is isChangingConfigurations()
This Google sample calls observe on LiveData in a fragment and passes getActivity() as the LifecycleOwner.
mSeekBarViewModel.seekbarValue.observe(getActivity(), new Observer<Integer>() {
#Override
public void onChanged(#Nullable Integer value) {
if (value != null) {
mSeekBar.setProgress(value);
}
}
});
https://github.com/googlecodelabs/android-lifecycles/blob/master/app/src/main/java/com/example/android/lifecycles/step5_solution/Fragment_step5.java
I can't wrap my head around any reasons to do that. I only want updates as long as the fragment is active, so why not scope it to the fragment?
Are there any reasons to ever NOT scope it to the fragment?
In this case, the Fragment is inflated from a <fragment> tag in the Activity's layout, so the lifecycle of the Fragment and the Activity is always the same so it doesn't make any difference.
However, there are two cases where this fails badly:
If you remove() or replace() the Fragment, using getActivity() for your LifecycleOwner will result in leaking the Fragment since the LiveData holds a strong reference to the Observer (and hence, the Fragment since it is a non-static inner class) until the Activity is destroyed
If you detach() and then attach() the Fragment (such as with a FragmentPagerAdapter), then using the Fragment's lifecycle in onCreateView() will result in multiple Observers since onCreateView() is called each time the Fragment's view is recreated upon attach and previous Observers are not destroyed since the Fragment's lifecycle has not been destroyed.
The correct LifecycleOwner to use in onCreateView() is always getViewLifecycleOwner() since this lifecycle is destroyed when the Fragment's View is destroyed:
mSeekBarViewModel.seekbarValue.observe(getViewLifecycleOwner(), new Observer<Integer>() {
#Override
public void onChanged(#Nullable Integer value) {
if (value != null) {
mSeekBar.setProgress(value);
}
}
});
This prevents leaking the Fragment by using a potentially longer lifespan LifecycleOwner (like the Activity) and prevents multiple Observers being registered when using patterns like those employed by FragmentPagerAdapter.
I found a case when architecture components ViewModel isn't retained - in short it goes as follows:
Activity is started and ViewModel instance is created
Activity is put to background
Device screen is rotated
Activity is put back to foreground
ViewModel's onCleared method is called and new object is created
Is it normal behavior of Android that my ViewModel instance is getting destroyed in this case? If so, is there any recommended solution of keeping its state?
One way I can think of is saving it once onCleared is called, however, it would also persist the state whenever activity is actually finishing. Another way could be making use of onRestoreInstanceState but it's fired on every screen rotation (not only if the app is in background).
Any silver bullet to handle such case?
Yes #tomwyr, this was a bug from an android framework. Bug details
The fix is available in 28.0.0-alpha3 and AndroidX 1.0.0-alpha3
But if you don't want to update to above versions now itself, Then you can solve like this (I know this is a bad solution but I didn't see any other good way)
In your activity override onDestroy method and save all the required fields to local variables before calling super.onDestroy. Now call super.onDestroy then Initialize your ViewModel again and assign the required fields back to your new instance of ViewModel
about isFinishing
Below code is in Kotlin:
override fun onDestroy() {
val oldViewModel = obtainViewModel()
if (!isFinishing) { //isFinishing will be false in case of orientation change
val requiredFieldValue = oldViewModel.getRequiredFieldValue()
super.onDestroy
val newViewModel = obtainViewModel()
if (newViewModel != oldViewModel) { //View Model has been destroyed
newViewModel.setRequiredFieldValue(requiredFieldValue)
}
} else {
super.onDestroy
}
}
private fun obtainViewModel(): SampleViewModel {
return ViewModelProviders.of(this).get(SampleViewModel::class.java)
}
AFAIK, ViewModel's only purpose is to survive and keep the data (i.e. "save the state") while its owner goes through different lifecycle events. So you don't have to "save the state" yourself.
We can tell from this that it's "not normal behavior". onCleared() is only called after the activity is finished (and is not getting recreated again).
Are you creating the ViewModel using the ViewModelProvider, or are you creating the instance using the constructor?
In your activity, you should have something like:
// in onCreate() - for example - of your activity
model = ViewModelProviders.of(this).get(MyViewModel.class);
// then use it anywhere in the activity like so
model.someAsyncMethod().observe(this, arg -> {
// do sth...
});
By doing this, you should get the expected effect.
For others that may not be helped by previous answers like me, the problem could be that you haven't set up your ViewModelProvider properly with a factory.
After digging around I solved my similiar problem by adding the following method to my Activities:
protected final <T extends ViewModel> T obtainViewModel(#NonNull AppCompatActivity activity, #NonNull Class<T> modelClass) {
ViewModelProvider.AndroidViewModelFactory factory = ViewModelProvider.AndroidViewModelFactory.getInstance(activity.getApplication());
return new ViewModelProvider(activity, factory).get(modelClass);
}
And then I did this in my Fragments:
protected final <T extends ViewModel> T obtainFragmentViewModel(#NonNull FragmentActivity fragment, #NonNull Class<T> modelClass) {
ViewModelProvider.AndroidViewModelFactory factory = ViewModelProvider.AndroidViewModelFactory.getInstance(fragment.getApplication());
return new ViewModelProvider(fragment, factory).get(modelClass);
}
I already had some abstract super classes for menu purposes so I hid the methods away there so I don't have to repeat it in every activity. That's why they are protected. I believe they could be private if you put them in every activity or fragment that you need them in.
To be as clear as possible I would then call the methods to assign my view model in onCreate() in my activity and it would look something like this
private MyViewModel myViewModel;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
myViewModel = obtainViewModel(this, MyViewModel.class);
}
or in fragment
private MyViewModel myViewModel;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getActivity() != null) {
myViewModel = obtainFragmentViewModel(getActivity(), MyViewModel.class);
}
}
Change support library/compileSDK/targetSDK to 28.
I had similar issue with multi-window. When switching to split screen, my viewModel is recreated. Support library 28 fixed my problem. (My lifecycle version is 1.1.1)
I've started learning architecture components, but can't find one thing.
LifecycleFragment just creates a new LifecycleRegistry object, which does not start observing the fragment's lifecycle.
I guess the LifecycleRegistry object starts listening to the fragment's lifecycle when we, for example, put it into LiveData.observe() as first param, but I haven't found any proof of this in source code.
Question: When and how does a LifecycleRegistry object start to observe a fragment's lifecycle and refresh LifecycleRegistry.mState?
There is a ContentProvider called LifecycleRuntimeTrojanProvider that is merged into the app's AndroidManifest.xml. In its onCreate method it initializes a singleton called LifecycleDispatcher, which is responsible for updating all LifecycleRegistry instances.
LifecycleDispatcher uses the Application.registerActivityLifecycleCallbacks method that has been around since API 14 to get notified when a new activity is created. At this point it injects an instance of ReportFragment into the activity. The ReportFragment uses the Fragment lifecycle callbacks to update the activity's LifecycleRegistry if necessary, like this:
#Override
public void onStop() { // Showing onStop as example
super.onStop();
dispatch(Lifecycle.Event.ON_STOP);
}
private void dispatch(Lifecycle.Event event) {
if (getActivity() instanceof LifecycleRegistryOwner) {
((LifecycleRegistryOwner) getActivity()).getLifecycle().handleLifecycleEvent(event);
}
}
If the new activity is a FragmentActivity, the LifecycleDispatcher calls FragmentManager.registerFragmentLifecycleCallbacks to get notified of the activity's fragments lifecycle events. It relays the onFragmentCreated, onFragmentStarted and onFragmentResumed callbacks to the LifecycleRegistry in case the fragment is a LifecycleRegistryOwner, in the same way as before.
The onFragmentPaused, onFragmentStopped, and onFragmentDestroyed callbacks are called after the corresponding callbacks are called on the fragment, but the LifecycleObserver callbacks must be called before. So whenever a fragment is created, the LifecycleDispatcher injects an instance of LifecycleDispatcher.DestructionReportFragment into it. The DestructionReportFragment's lifecycle callbacks are used to update the registry for the pause, stop and destroy events.
I can't link to the code because it hasn't been released yet, but you can browse it in Android Studio after you add the library to your project.
As Mordag said, as of now, both the LifecycleActivity and LifecycleFragment are not yet implemented. In their documentation Google says:
Any custom fragment or activity can be turned into a LifecycleOwner by implementing the built-in LifecycleRegistryOwner interface (instead of extending LifecycleFragment or LifecycleActivity).
However, that is only half the story, because naturally you are using these Lifecycle Aware components to be able to react to your Activity/Fragment lifecycles and with their code snippet it just doesn't work, because initialising a LifecycleRegistry with the Activity/Fragment like this
LifecycleRegistry lifecycleRegistry = new LifecycleRegistry(this);
only gets you a Lifecycle in the INITIALIZED state.
So, long story short, in order for this to work right now (BEFORE their 1.0-release) it is you who have to implement the Lifecycle of the Activity/Fragment that implements the LifecycleRegistry. So, for each callback of the Activity/Fragment you need to do this:
public class ScoreMasterFragment extends Fragment
implements LifecycleRegistryOwner {
private LifecycleRegistry lifecycle;
#Override
public LifecycleRegistry getLifecycle() {
return lifecycle;
}
public ScoreMasterFragment(){
lifecycle = new LifecycleRegistry(this);
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//more code here
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
}
#Override
public void onStart() {
super.onStart();
//more code here
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_START);
}
#Override
public void onResume() {
super.onResume();
//more code here
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_RESUME);
}
#Override
public void onPause() {
super.onPause();
//more code here
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE);
}
#Override
public void onStop() {
super.onStop();
//more code here
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
}
#Override
public void onDestroy() {
super.onDestroy();
//more code here
_lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY);
}
This will likely be in the code of the future LifecycleActivity and LifecycleFragment, but until then, if you put your Activities/Fragments observing some LifecycleAware object (like LiveData) you will have to do this.
In the case of LiveData, because it will not notify its observers unless they are at least in the STARTED state and in other cases because other LifecycleAware components cannot react to a Lifecycle if its only state is INITIALIZED.
The LifecycleFragment and LifecycleActivity are currently not fully implemented. Those classes will be implemented when the lib is reaching 1.0-release. Currently you can use those LifecycleRegistry to observe LiveData objects. Those objects are based on a future result which could e.g. be an object from your database.
The official documentation can be found here: https://developer.android.com/topic/libraries/architecture/index.html
Official statement regarding the two classes you mentioned:
Lifecycle Fragment and ActivityCompat in the Support Library do not
yet implement LifecycleOwner interface. They will when Architecture
Components reaches 1.0.0 version.
LifecycleActivity ,LifecycleFragment and LifecycleRegistryOwner interface are deprecated in API level 1.0.0. Use android.support.v7.app.AppCompatActivity and android.support.v4.app.Fragment instead of it.
Official documentation here LifecycleActivity LifeCycleFragment
I see that if one instantiates a Dagger 2 Component in an Activity, then it's later nulled in the onDestroy() method like seen here.
public class MyActivity {
private MyActivityComponent component;
//...
public void onCreate() {
component = Dagger_MyActivityComponent.builder()
.myApplicationComponent(App.getComponent())
.build()
.inject(this);
//...
}
public void onDestroy() {
component = null;
}
}
What happens if I don't null that instance and what would happen?
Side note: in comments I've found useful hint why one would set it to null which is pretty convincing: "I don't think it's necessary but it defines scope pretty clear".
What happens if I don't null that instance [...]?
Nothing. After onDestroy gets called the activity object will be garbage collected at some point. If the activity gets recreated it will be a new object. Your dagger component will also be garbage collected then along with your activity. I usually don't null my components in onDestroy because I deem it unnecessary.
This will although not hold true if you keep static references to your activity or have some other sort of memory and activity leaks. But if you have those it will not make much of a difference either if you null your component.