Android ViewModel recreated on screen rotation

16,167

Solution 1

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

Solution 2

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.

Solution 3

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);
    }
}
Share:
16,167

Related videos on Youtube

tomwyr
Author by

tomwyr

Log.i("GREETING","Hello Stack!")

Updated on September 15, 2022

Comments

  • tomwyr
    tomwyr almost 2 years

    I found a case when architecture components ViewModel isn't retained - in short it goes as follows:

    1. Activity is started and ViewModel instance is created
    2. Activity is put to background
    3. Device screen is rotated
    4. Activity is put back to foreground
    5. 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?

  • tomwyr
    tomwyr about 6 years
    Yeah, I'm creating it exactly as in your example, i.e. by referencing it through ViewModelProviders. Also, I added a comment to my question if you're interested.
  • Ace
    Ace about 6 years
    I see.. Sorry then, this was AFAIK. If it's a known issue, then I hope they fix it ASAP. If not, then hopefully someone more experienced can enlighten us
  • Blcknx
    Blcknx about 6 years
    I think the purpose is different. It survives the data even in cases, when the lifecycle of the activity finishes like in the typical case of a rotation.
  • Ace
    Ace about 6 years
    @Blcknx hence, the "(and is not getting recreated)" in my answer
  • Surendar Dharavath
    Surendar Dharavath almost 6 years
    Same problem for me also, Till API level 26 it working fine, but i faced this issue when i updated my API level to 27
  • tomwyr
    tomwyr almost 6 years
    I found some other way to deal with the issue (made some code changes so the bug doesn't affect my app), but your solution seems totally fine as a workaround.