//I want to share with you my way of restoring view model state.

//Could be useful for some of you or inspire you to write something similar. I would like to hear your thoughts.

// create all these classes in a separate Android library module

/**

* 1) create a base view model class that does NOT extend from [ViewModel]l. More on that later

*/

abstract class BaseViewModel {

internal fun onClearedInternal ( ) {

onCleared ( )

}

protected open fun onCleared ( ) {

}

open fun onSaveInstanceState ( ) : Serializable ? {

return null

}

open fun onRestoreInstanceState ( state : Serializable ) {

}

}

/**

* 2) create a view model map which will delegate the BaseViewModel calls

* [savedState] is of type Serializable but you can modify that to [android.os.Parcelable] if you want

*/

class ViewModelMap ( savedState : Serializable ? ) {

private val savedStateMap = savedState as? SavedStateMap

private val viewModelMap = mutableMapOf < Class < out BaseViewModel > , BaseViewModel > ( )

@Suppress ( "UNCHECKED_CAST" )

fun < T : BaseViewModel > create ( klass : Class < T > , provider : ( ) -> T ) : T {

var cached = viewModelMap [ klass ]

if ( cached == null ) {

cached = provider ( ) . also { newViewModel ->

viewModelMap [ klass ] = newViewModel

savedStateMap ? . get ( klass. name ) ? . let {

newViewModel. onRestoreInstanceState ( it )

}

}

}

return cached as T

}

/**

* store the saved instance state for each view model by their class name in a hash map

*/

private class SavedStateMap : Serializable {

private val map = mutableMapOf < String , Serializable > ( )

operator fun set ( key : String , value : Serializable ) {

map [ key ] = value

}

operator fun get ( key : String ) = map [ key ]

}

fun onSaveInstanceState ( ) : Serializable {

val savedStateMap = SavedStateMap ( )

viewModelMap. forEach { ( key, value ) ->

value. onSaveInstanceState ( ) ? . let {

savedStateMap [ key. name ] = it

}

}

return savedStateMap

}

fun onCleared ( ) {

viewModelMap. values . forEach {

it. onClearedInternal ( )

}

viewModelMap. clear ( )

}

}

/**

* 3) create a view model container which will be the only [ViewModel] you will use with a newly created [ViewModelMap]

* put a [SavedStateHandle] in the constructor to restore the state from (androidx.lifecycle:lifecycle-viewmodel-savedstate:1.0.0-alpha05)

*

*/

class ViewModelContainer ( private val savedStateHandle : SavedStateHandle ) : ViewModel ( ) {

private val viewModelMap = ViewModelMap ( savedStateHandle. get < Bundle > ( KEY_BUNDLE ) ? . getSerializable ( KEY_SERIALIZABLE ) )

fun < T : BaseViewModel > create ( klass : Class < T > , provider : ( ) -> T ) : T {

return viewModelMap. create ( klass, provider )

}

override fun onCleared ( ) {

super . onCleared ( )

viewModelMap. onCleared ( )

}

/**

* should be called in each [androidx.savedstate.SavedStateRegistryOwner] onSaveInstanceState call

*/

fun onSaveInstanceState ( ) {

val bundle = Bundle ( )

bundle. putSerializable ( KEY_SERIALIZABLE, viewModelMap. onSaveInstanceState ( ) )

savedStateHandle. set ( KEY_BUNDLE, bundle )

}

companion object {

private const val KEY_BUNDLE = "viewmodelcontainer.bundle"

private const val KEY_SERIALIZABLE = "viewmodelcontainer.serializable"

}

}

/**

* 4) Create a custom view model factory with a view model [javax.inject.Provider] for use with Dagger

*/

class ViewModelFactory < VM : BaseViewModel > @Inject constructor (

val viewModel : Provider < VM >

)

/**

* 5) write two extension functions for your [ViewModelFactory]. One for activity and one for fragments

*/

inline fun < reified T : BaseViewModel > ViewModelFactory < T > . get ( fragment : Fragment ) : T {

return ViewModelProvider ( fragment, SavedStateViewModelFactory ( fragment. activity !! . application , fragment ) ) . get ( ViewModelContainer :: class . java ) . create ( T :: class . java ) {

viewModel. get ( )

}

}

inline fun < reified T : BaseViewModel > ViewModelFactory < T > . get ( activity : FragmentActivity ) : T {

return ViewModelProvider ( activity, SavedStateViewModelFactory ( activity. application , activity ) ) . get ( ViewModelContainer :: class . java ) . create ( T :: class . java ) {

viewModel. get ( )

}

}

/**

* 6) Create an abstract BaseDialogFragment, BaseFragment and BaseActivity where you define a [ViewModelContainer] property

* and call [ViewModelContainer.onSaveInstanceState] in [FragmentActivity.onSaveInstanceState]

*

* here's the activity version

*/

abstract class BaseActivity : AppCompatActivity ( ) {

private val viewModelContainer : com. rvdsoft . vgcollectiontracker . library . presentation . mvi . viewmodel . ViewModelContainer by viewModels (

factoryProducer = { SavedStateViewModelFactory ( application, this ) }

)

override fun onSaveInstanceState ( outState : Bundle ) {

super . onSaveInstanceState ( outState )

viewModelContainer. onSaveInstanceState ( )

}

}

//******* USAGE **********

class SomeViewModel : BaseViewModel ( ) {

val _string = MutableLiveData < String > ( )

val string : LiveData < String > = _string

}

class SomeActivity : BaseActivity ( ) {

@Inject

lateinit var vmFactory : ViewModelFactory < SomeViewModel >

lateinit var viewModel : SomeViewModel

override fun onCreate ( savedInstanceState : Bundle ? ) {

super . onCreate ( savedInstanceState )

AndroidInjection. inject ( this )

viewModel = vmFactory. get ( this )

}