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()
Related
I'm using singleton fragment. I thought when I call this singleton fragment, the lifecycle methods onCreateView and onActivityCreated will be called only once. But they aren't even though the fragment is singleton, onCreateView and onActivityCreated are called when I call fragment. But I found something strange. That is, the RecyclerView is holding it's position. If I move A frag(using RecyclerView, position at 20) to B frag and redirect to A frag, the A fragment position is 20. Although onCreateView and onActivityCreated are called again, Why the A fragment position is saved?
ps: I know kotlin support singleton class "Objcet". But I'm more comfortable using singleton constructor than object class.
MainActivity
navigation_view.setNavigationItemSelectedListener {
drawerLayout.closeDrawer(GravityCompat.START)
when(it.itemId){
R.id.scheduleFragment->{
changeFragment(scheduleFragment)
}
R.id.noticeFragment->{
changeFragment(NoticeFragment())
}
}
true
}
}
fragment
companion object {
var scheduleFragment: ScheduleFragment? = null
}
fun getInstance(context: Context): ScheduleFragment {
if (scheduleFragment == null) {
scheduleFragment = ScheduleFragment()
}
return scheduleFragment!!
}
Well, it's behaving exactly as expected. The recycler-view's view, position all are members to the fragment instance. So the values remain same as it has only single instance. But the life cycle methods have nothing to do with the fact that the fragment class is singleton. They get called when the specific event happens. For example, when the activity is created then the onActivityCreated method get called by the system and this method calling has nothing to do about the fragment instance creation. Because the fragment instance creation happens earlier when you make an instance of fragment. Now after the use either you want to keep the instance or destroy it, it's your choice. Hope this will clear your confusion. Let me know if you don't understand anything.
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.
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.
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 have a ViewModel called RecipesViewModel. Usually, I instantiated it this way:
RecipesViewModel viewModel = ViewModelProviders.of(this, new ViewModelProvider.Factory() {
#Override
public <T extends ViewModel> T create(Class<T> modelClass) {
return (T) new RecipesViewModel(recipesRepository);
}
}).get(RecipesViewModel.class);
But now I'm using dagger2 and so I put a #Inject annotation on the constructor of this ViewModel, so I'm able to inject it directly in my fragment, using field injector.
My question is: do I lose something starting the viewmodel this way instead of ViewModelProviders.of way? My ViewModel is already Scoped, so only one instance is create in context.
Other option is to move only the factory instantiation to a dagger2 module, but if there is no problem I prefer the first aproach.
-- EDIT --
Reading the documentation android.arch.lifecycle.ViewModel, I'm a little more afraid. Whe use ViewModelProviders.of to provide a Scope (fragment or activity). If I instantiate it directly what will be the Scope?
ViewModel is a class that is responsible for preparing and managing
the data for an Activity or a Fragment. It also handles the
communication of the Activity / Fragment with the rest of the
application (e.g. calling the business logic classes).
A ViewModel is always created in association with a scope (an fragment
or an activity) and will be retained as long as the scope is alive.
E.g. if it is an Activity, until it is finished.
In other words, this means that a ViewModel will not be destroyed if
its owner is destroyed for a configuration change (e.g. rotation). The
new instance of the owner will just re-connected to the existing
ViewModel.
-- /EDIT --
The RecipesViewModel code is showing below:
#PerActivity
public class RecipesViewModel extends ViewModel {
private static final String TAG = "RecipesViewModel";
private final RecipesRepository recipesRepository;
private LiveData<List<Recipe>> recipes = null;
#Inject
public RecipesViewModel(RecipesRepository recipesRepository) {
this.recipesRepository = recipesRepository;
}
public final void loadAll() {
recipes = recipesRepository.getRecipes();
}
public LiveData<List<Recipe>> getRecipes() {
return recipes;
}
}
For me right now (and I need to research this), but injecting a view model instead of using the ViewModelProviders functionality means you lose some easy activity-fragment communication.
For example from the docs they provide an example of an activity hosting 2 fragments. If one fragment needs to talk to another, the previous method was to maintain an interface via the activity who also had to take care of the lifecycle of that interface. Instead now you can just fetch it from the the ViewModelProviders 'repo' whenever you need.