Reputation: 11744
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
ChildViewModelFactory
to Navigation Compose's hiltViewModel
method?ViewModel
? How about creating a lateinit
property and init
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
}
}
Upvotes: 12
Views: 2994
Reputation: 23994
You can do this using EntryPointAccessors
(from Hilt) and a ViewModelProvider.Factory
from View Model library.
In my sample app, BookFormScreen
is using BookFormViewModel
and the view model needs to load a book based on a bookId
passed by the previous screen. This is what I did:
class BookFormViewModel @AssistedInject constructor(
...
@Assisted private val bookId: String?,
) : ViewModel() {
...
@AssistedFactory
interface Factory {
fun create(bookId: String?): BookFormViewModel
}
companion object {
@Suppress("UNCHECKED_CAST")
fun provideFactory(
assistedFactory: Factory, // this is the Factory interface
// declared above
bookId: String?
): ViewModelProvider.Factory = object : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return assistedFactory.create(bookId) as T
}
}
}
}
Notice that I'm not using @HiltViewModel
. The provideFactory
will be use to supply a factory to create this view model.
Then, define the ViewModelFactoryProvider
for the entry point:
@EntryPoint
@InstallIn(ActivityComponent::class)
interface ViewModelFactoryProvider {
fun bookDetailsViewModelFactory(): BookDetailsViewModel.Factory
fun bookFormViewModelFactory(): BookFormViewModel.Factory
}
Now, you need to define a composable function to provide the view model using this factory.
@Composable
fun bookFormViewModel(bookId: String?): BookFormViewModel {
val factory = EntryPointAccessors.fromActivity(
LocalContext.current as Activity,
ViewModelFactoryProvider::class.java
).bookFormViewModelFactory()
return viewModel(factory = BookFormViewModel.provideFactory(factory, bookId))
}
If you're using the navigation library, you can add the ViewModelStoreOwner
parameter in this function and use it in viewModel()
function call. For this parameter, you can pass the NavBackStackEntry
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.
val bookFormViewModel: BookFormViewModel = bookFormViewModel(bookId)
Upvotes: 9