19

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?

Steve M
  • 9,296
  • 11
  • 49
  • 98

3 Answers3

22

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.

Asocia
  • 5,935
  • 2
  • 21
  • 46
Mir Milad Hosseiny
  • 2,769
  • 15
  • 19
  • 4
    You don't need to manually set the live data key from `onSaveInstanceState` if you already obtained the LiveData as `savedStateHandle.getLiveData`. – EpicPandaForce Apr 26 '20 at 13:12
  • 1
    @EpicPandaForce I commented `state.set("liveData", liveDate.value)` line and tested with `lifecycle-viewmodel-savedstate` version 2.2.0, but unfortunately it didn't work for me. Would you please provide working sample code? – Mir Milad Hosseiny Apr 26 '20 at 18:22
  • Technically you don't need your `saveState` method entirely if you use `savedStateHandle.getLiveData` – EpicPandaForce Apr 26 '20 at 20:03
  • and it's critical to call `viewModel.saveState()` before `super.onSaveInstanceState(outState)`, otherwise it won't work for some reason that I don't know yet – madim Oct 07 '20 at 14:04
  • I'm sorry but I can't agree with this. (Maybe just a little too late). VM saved state is already lifecycle aware. You don't need to attach the views LifeCycle to the VM they are already there. – Tiago Dávila Nov 23 '21 at 13:03
6

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.

Super Symmetry
  • 2,837
  • 1
  • 6
  • 17
  • If you pass another parameter to the ViewModel (like Api reference), you will still need a `ViewModelFactory` class as: in Activity: `val viewModelFactory = SwapiViewModelFactory(ApiInterface.getInstance())` `val viewModel: TransportationListViewModel by lazy {` `ViewModelProvider(this, viewModelFactory,[TransportationListViewModel::class.java]` `}` [ViewModelFactory with Parameter Example](https://user-images.githubusercontent.com/5157474/193959968-cec5f94d-f732-4911-9bfb-9b42396924fe.png) – RealityExpander Oct 05 '22 at 01:30
  • @RealityExpander A single `SavedStateHandle` parameter is a special case for which you do not need a `ViewModelFactory`. Have a look at the examples in the docs [here](https://developer.android.com/topic/libraries/architecture/viewmodel-savedstate). Also the api has now changed so in the fragment/activity you would use `private val selectedTracksViewModel: SelectedTracksViewModel by viewModels()` – Super Symmetry Oct 09 '22 at 05:16
  • But what if you need to add another parameter to the ViewModel. Is there a way to do that using the `by viewModels()` method? – RealityExpander Oct 09 '22 at 06:16
  • 1
    @RealityExpander Sorry I might have misunderstood your comment: Yes if you have any other parameter then you would need to either use a `ViewModelFactory` or a dependency injection library (e.g. hilt, koin, etc) – Super Symmetry Oct 11 '22 at 18:00
-1

As far as I understand you want to create View model with spec constructor. You can use ViewModelProvider.Factory.

viewModel = ViewModelProvider(this, SavedStateViewModelFactory.create(state)
            .get(SelectedTracksViewModel::class.java)

example of ViewModelFactory

public class SavedStateViewModelFactory {

    public static <E> ViewModelProvider.Factory create(State state){

        return new ViewModelProvider.Factory() {
            @NonNull
            @Override
            @SuppressWarnings("unchecked")
            public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
                if (modelClass.isAssignableFrom(SelectedTracksViewModel.class)) {
                    return (T) new SelectedTracksViewModel<>(state);
                } else {
                    throw new IllegalArgumentException("Unknown ViewModel class");
                }
            }
        };
    }

}
Vahe Gharibyan
  • 5,277
  • 4
  • 36
  • 47