Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I use the new Saved State Module of ViewModel

I'm using lifecycle version 2.2.0-rc03 and the official docs and articles found don't even list the correct class name or constructor arguments. I think I have to get the ViewModel instance through something like this

viewModel = ViewModelProvider(this, SavedStateViewModelFactory(requireActivity().application, savedStateRegistryOwner))
            .get(SelectedTracksViewModel::class.java)

but I can't figure out the SavedStateRegistryOwner.

Can someone give a simple example of how to create the saved state ViewModel instance and the correct way to save and restore a value in the ViewModel?

like image 827
Steve M Avatar asked Jan 11 '20 20:01

Steve M


People also ask

How do you use a state 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.

How do I use savedInstanceState on Android?

The onSaveInstanceState() callback stores data needed to reload the state of a UI controller, such as an activity or a fragment, if the system destroys and later recreates that controller. To learn how to implement saved instance state, see Saving and restoring activity state in the Activity Lifecycle guide.

How does a ViewModel retain itself?

ViewModel objects are automatically retained during configuration changes so that data they hold is immediately available to the next activity or fragment instance. FYI: You can use ViewModel to preserve UI state only during a configuration change, nothing else as explained perfectly in this official doc.

What allows you to properly restore a user state when an activity is restarted?

The savedInstanceState is a reference to a Bundle object that is passed into the onCreate method of every Android Activity. Activities have the ability, under special circumstances, to restore themselves to a previous state using the data stored in this bundle.


2 Answers

For using Saved State module for View Model you have to add the androidx.lifecycle:lifecycle-viewmodel-savedstate dependency to your project. This example has been written based on version 1.0.0-rc03.

Please add the following line to your project Gradle file:
implementation 'androidx.lifecycle:lifecycle-viewmodel-savedstate:1.0.0-rc03'

ViewModel implementation:

class SavedStateViewModel(private val state: SavedStateHandle) : ViewModel() {

    val liveData = state.getLiveData("liveData", Random.nextInt().toString())

    fun saveState() {
        state.set("liveData", liveData.value)
    }
}

Activity implementation:

class SavedStateActivity : AppCompatActivity() {

    lateinit var viewModel: SavedStateViewModel;

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding: ActivityStateBinding = DataBindingUtil.setContentView(this, R.layout.activity_state)

        viewModel = ViewModelProvider(this, SavedStateViewModelFactory(this.application, this)).get(SavedStateViewModel::class.java)

        binding.viewModel = viewModel
        binding.lifecycleOwner = this
    }

    override fun onSaveInstanceState(outState: Bundle) {
        if(::viewModel.isInitialized)
            viewModel.saveState()

        super.onSaveInstanceState(outState)
    }
}

I have tested this code and it works fine.

like image 184
Mir Milad Hosseiny Avatar answered Oct 05 '22 03:10

Mir Milad Hosseiny


I am adding an answer to this old post just in case someone might find it useful.

I managed to do it as follows:

  • Add the following dependency to your "build.gradle (Module: app)" file
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
  • Add savedState: SavedStateHandle property to the constructor of the ViewModel
class SelectedTracksViewModel(private val savedState: SavedStateHandle) : ViewModel() {

    companion object {
        private const val SAVED_TRACK_INDEX = "savedTrackIndex"
    }

    private var trackIndex: Int
      set(value) {
        field = value
        // Simply update the savedState every time your saved property changes
        savedState.set(SAVED_TRACK_INDEX, value)
      }

    init {
        trackIndex = savedState.get<Int>(SAVED_TRACK_INDEX) ?: 0
    }

    fun moveToNextTrack() {
        trackIndex++ 
        // Initially I was updating savedState here - now moved to setter
        
        // Some more code here
    }    
}

Finally in the activity/fragment

    private val selectedTracksViewModel: SelectedTracksViewModel by lazy {
        ViewModelProvider(this).get(SelectedTracksViewModel::class.java)
    }

And that's it. No need for SavedStateViewModelFactory, simply add the savedState property to your ViewModel constructor and update it when tracked properties change. Everything else works as if you're not using savedState: SavedStateHandle and this way is very similar to the traditional onSaveInstanceState(Bundle) in activities/fragments.

Update: Initially I was updating savedState after changing trackIndex. This means one has to update savedState every time saved properties are changed. This is a huge potential future bug if one forgets to add that line. A better and more robust pattern is to update the savedState in the setter of the property.

like image 35
Super Symmetry Avatar answered Oct 05 '22 02:10

Super Symmetry