How to inject SavedStateHandle to ViewModel in dynamic feature module?

5.5k views Asked by At

Using @Assisted annotation with savedStateHandle and by viewModels() it's possible to inject SavedStateHandle object to ViewModel in modules that are not dynamic feature modules with dagger hilt as

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    private val viewModel: MainActivityViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
}

class MainActivityViewModel @ViewModelInject constructor(
    @Assisted savedStateHandle: SavedStateHandle
) : ViewModel() {

    val stringData = savedStateHandle.getLiveData<String>("hello_world")
}

but it's not possible for dynamic feature modules to do like this. How is it done with dynamic feature module ViewModels?

2

There are 2 answers

2
Thracian On BEST ANSWER

My ViewModel is

class DashboardViewModel @ViewModelInject constructor(
    @Assisted private val savedStateHandle: SavedStateHandle,
    private val coroutineScope: CoroutineScope,
    private val dashboardStatsUseCase: GetDashboardStatsUseCase,
    private val setPropertyStatsUseCase: SetPropertyStatsUseCase
) : ViewModel() {

}

Creating generic FragmentFactory for SavedStateHandle with

interface ViewModelFactory<out V : ViewModel> {
    fun create(handle: SavedStateHandle): V
}

class GenericSavedStateViewModelFactory<out V : ViewModel>(
    private val viewModelFactory: ViewModelFactory<V>,
    owner: SavedStateRegistryOwner,
    defaultArgs: Bundle? = null
) : AbstractSavedStateViewModelFactory(owner, defaultArgs) {
    @Suppress("UNCHECKED_CAST")
    override fun <T : ViewModel> create(
        key: String,
        modelClass: Class<T>,
        handle: SavedStateHandle
    ): T {
        return viewModelFactory.create(handle) as T
    }
}

/**
 * Convenience function to use with `by viewModels` that creates an instance of
 * [AbstractSavedStateViewModelFactory] that enables us to pass [SavedStateHandle]
 * to the [ViewModel]'s constructor.
 *
 * @param factory instance of [ViewModelFactory] that will be used to construct the [ViewModel]
 * @param owner instance of Fragment or Activity that owns the [ViewModel]
 * @param defaultArgs Bundle with default values to populate the [SavedStateHandle]
 *
 * @see ViewModelFactory
 */
@MainThread
inline fun <reified VM : ViewModel> SavedStateRegistryOwner.withFactory(
    factory: ViewModelFactory<VM>,
    defaultArgs: Bundle? = null
) = GenericSavedStateViewModelFactory(factory, this, defaultArgs)

ViewModel factory for ViewModel

class DashboardViewModelFactory @Inject constructor(
    private val coroutineScope: CoroutineScope,
    private val dashboardStatsUseCase: GetDashboardStatsUseCase,
    private val setPropertyStatsUseCase: SetPropertyStatsUseCase
) : ViewModelFactory<DashboardViewModel> {

    override fun create(handle: SavedStateHandle): DashboardViewModel {
        return DashboardViewModel(
            handle,
            coroutineScope,
            dashboardStatsUseCase,
            setPropertyStatsUseCase
        )
    }
}

And creating ViewModel using the DashBoardViewModelFactory in Fragment as

@Inject
lateinit var dashboardViewModelFactory: DashboardViewModelFactory

private val viewModel: DashboardViewModel
by viewModels { withFactory(dashboardViewModelFactory) }

Here you can see the full implementation in action. I wasn't able to find the source i used to implement this solution, if you can comment the link, i would like to give credit to author.

0
Arman On

Dagger2 Assisted documentation

class MyViewModel @AssistedInject constructor(
    @Assisted val savedStateHandle: SavedStateHandle
) : ViewModel() {
    val data: Int = savedStateHandle["key"]!!
}
@AssistedFactory
interface ViewModelAssistedFactory {
    fun create(savedStateHandle: SavedStateHandle): MyViewModel
}
class ViewModelFactory @Inject constructor(
    private val viewModelAssistedFactory: ViewModelAssistedFactory
) : AbstractSavedStateViewModelFactory() {

    @Suppress("UNCHECKED_CAST")
    override fun <T : ViewModel> create(
        key: String,
        modelClass: Class<T>,
        handle: SavedStateHandle
    ): T = viewModelAssistedFactory.create(handle) as T
}

Fragment

init {
    arguments = bundleOf("key" to 1)
}

@Inject
lateinit var viewModelFactory: ViewModelFactory

private val myViewModel: MyViewModel by viewModels { viewModelFactory }

Hilt documentation he knows how to deliver SavedStateHandle

@HiltViewModel
class MyViewModel @Inject constructor(
    val savedStateHandle: SavedStateHandle
) : ViewModel()