Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AndroidViewModel with SavedState

I need to use an AndroidViewModel with application context and a SavedStateHandle. I have it already working with application context, but I fail adding a SavedStateHandle to it.

This is what I have, with only the application context:

// A1. get ViewModel in Fragment
val viewModel = ViewModelProvider(viewLifecycleOwner).get(MyViewModel::class.java)

// A2. MyViewModel derives from my custom BaseAndroidViewModel
class MyViewModel(application: Application) :BaseAndroidViewModel(application)

// A3. BaseAndroidViewModel in turn derives from AndroidViewModel
open class BaseAndroidViewModel(application: Application) : AndroidViewModel(application)

I assume for this question this could likely be reduced to:

// B1. get ViewModel in Fragment
val viewModel = ViewModelProvider(viewLifecycleOwner).get(MyViewModel::class.java)

// B2. BaseAndroidViewModel in turn derives from AndroidViewModel
class MyViewModel(application: Application) : AndroidViewModel(application) 

So, for also having a SavedStateHandle in MyViewModel, how would I have to modify the call in the fragment (line B1 in the example code) ? Do I need an explicit call to the factory SavedStateViewModelFactory? How exactly would that look like? (I am still new to Kotlin/Android, I've never worked with a factory before)

like image 726
stefan.at.wpf Avatar asked Apr 06 '20 15:04

stefan.at.wpf


People also ask

What is the purpose of an AndroidViewModel?

The purpose of the ViewModel is to acquire and keep the information that is necessary for an Activity or a Fragment. The Activity or the Fragment should be able to observe changes in the ViewModel. ViewModels usually expose this information via LiveData or Android Data Binding.

How UI state is managed with ViewModel?

UI state is usually stored or referenced in ViewModel objects and not activities, so using onSaveInstanceState() requires some boilerplate that the saved state module can handle for you. When using this module, ViewModel objects receive a SavedStateHandle object through its constructor.

Does ViewModel survive process death?

ViewModels are automatically destroyed by the system when your user backs out of your activity or fragment or if you call finish() , which means the state will be cleared as the user expects in these scenarios. Unlike saved instance state, ViewModels are destroyed during a system-initiated process death.

What is the difference between AndroidViewModel and ViewModel?

AndroidViewModel inherits ViewModel, so it has all the same functionality. The only added functionality for AndroidViewModel is that it is context aware: when initializing AndroidViewModel you have to pass the context as a parameter. This can be used if you want to show toasts for example.


1 Answers

EDIT: in the final release of AndroidX-Activity 1.2.0 and AndroidX-Fragment 1.1.0, they made SavedStateViewModelFactory the default factory in AppCompatActivity/Fragment, so it is not needed to override the default factory (which is what the second half of this answer does.)

Updating and then using

class MyViewModel(val savedStateHandle: SavedStateHandle): ViewModel()

class MyAndroidViewModel(application: Application, val savedStateHandle: SavedStateHandle): AndroidViewModel(application)

Should just work.


ORIGINAL:

how would I have to modify the call in the fragment (line B1 in the example code) ? Do I need an explicit call to the factory SavedStateViewModelFactory? How exactly would that look like?

In AndroidX-Activity 1.2.0, they added a new method called getDefaultViewModelProviderFactory():

+    @NonNull
+    @Override
+    public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {
+        if (getApplication() == null) {
+            throw new IllegalStateException("Your activity is not yet attached to the "
+                    + "Application instance. You can't request ViewModel before onCreate call.");
+        }
+        return ViewModelProvider.AndroidViewModelFactory.getInstance(getApplication());
+    }
+

Which means if I have a BaseActivity or a BaseFragment, I can swap this out for the SavedStateViewModelFactory from viewmodel-savedstate:

class BaseActivity: AppCompatActivity() {
    override fun getDefaultViewModelProviderFactory(): ViewModelProvider.Factory = 
        SavedStateViewModelFactory(application, this, intent?.extras ?: Bundle())
}

class BaseFragment: Fragment() {
    override fun getDefaultViewModelProviderFactory(): ViewModelProvider.Factory = 
        SavedStateViewModelFactory(requireActivity().application, this, arguments ?: Bundle())
}

Once you have that, you can rely on the automatic instantiation of ViewModel with SavedStateHandle as one of their arguments:

class MyViewModel(val savedStateHandle: SavedStateHandle): ViewModel()

class MyAndroidViewModel(application: Application, val savedStateHandle: SavedStateHandle): AndroidViewModel(application)

Beware that the order application, savedStateHandle is expected by SavedStateViewModelFactory.

However, if you do need custom arguments on top of that, then you'd have to provide a object: AbstractSavedStateViewModelFactory when you invoke the ViewModelProvider(viewModelStoreOwner).get(...) method

like image 165
EpicPandaForce Avatar answered Sep 21 '22 19:09

EpicPandaForce