Android ViewModel recreated on screen rotation

前端 未结 4 1121
滥情空心
滥情空心 2021-02-14 11:01

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

  1. Activity is started and ViewModel in
相关标签:
4条回答
  • 2021-02-14 11:02

    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.

    0 讨论(0)
  • 2021-02-14 11:10

    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)
    }
    
    0 讨论(0)
  • 2021-02-14 11:26

    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)

    0 讨论(0)
  • 2021-02-14 11:27

    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> ViewModel 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> ViewModel 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 = (MyViewModel) obtainViewModel(this, MyViewModel.class);
    }
    

    or in fragment

    private MyViewModel myViewModel;
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (getActivity() != null) {
            myViewModel = (MyViewModel) obtainFragmentViewModel(getActivity(), MyViewModel.class);
        }
    }
    

    It seems to work fine so far. If someone have a better way please let us know!

    0 讨论(0)
提交回复
热议问题