I've a composable called ParentScreen
and a ViewModel
named ParentViewModel
. Inside the ParentViewModel
, I am collecting a value from my repo.
class MyRepo @Inject constructor() {
fun getParentData() = System.currentTimeMillis().toString() // some dummy value
}
@HiltViewModel
class ParentViewModel @Inject constructor(
myRepo: MyRepo
) : ViewModel() {
private val _parentData = MutableStateFlow("")
val parentData = _parentData.asStateFlow()
init {
val realData = myRepo.getParentData()
_parentData.value = realData
}
}
@Composable
fun ParentScreen(
parentViewModel: ParentViewModel = hiltViewModel()
) {
val parentData by parentViewModel.parentData.collectAsState()
ChildWidget(parentData = parentData)
}
Inside the ParentScreen
composable, I have a ChildWidget
composable and it has its own ViewModel
named ChildViewModel
.
@HiltViewModel
class ChildViewModel @AssistedInject constructor(
@Assisted val parentData: String
) : ViewModel() {
@AssistedFactory
interface ChildViewModelFactory {
fun create(parentData: String): ChildViewModel
}
init {
Timber.d("Child says data is $parentData ")
}
}
@Composable
fun ChildWidget(
parentData: String,
childViewModel: ChildViewModel = hiltViewModel() // How do I supply assisted injection factory here?
) {
// Code omitted
}
Now, I want to get parentData
inside ChildViewModel
's constructor.
Questions
- How do I supply
ChildViewModelFactory
to Navigation Compose'shiltViewModel
method? - If that's not possible, what would be the most suitable method to inject an object from the parent composable to the child composable's
ViewModel
? How about creating alateinit
property andinit
method like below?
@HiltViewModel
class ChildViewModel @Inject constructor(
) : ViewModel() {
lateinit var parentData: Long
fun init(parentData: Long){
if(this::parentData.isInitialized) return
this.parentData = parentData
}
}
You can do this using
EntryPointAccessors
(from Hilt) and aViewModelProvider.Factory
from View Model library.In my sample app,
BookFormScreen
is usingBookFormViewModel
and the view model needs to load a book based on abookId
passed by the previous screen. This is what I did:Notice that I'm not using
@HiltViewModel
. TheprovideFactory
will be use to supply a factory to create this view model.Then, define the
ViewModelFactoryProvider
for the entry point:Now, you need to define a composable function to provide the view model using this factory.
If you're using the navigation library, you can add the
ViewModelStoreOwner
parameter in this function and use it inviewModel()
function call. For this parameter, you can pass theNavBackStackEntry
object, with this, the view model will be scoped to that particular back stack entry.Finally, you can use your view model in your composable.