如何使用Navigation Compose进行辅助注射?

the*_*e64 12 android kotlin android-jetpack-compose dagger-hilt navigation-compose

我有一个名为 的可组合项ParentScreen和一个ViewModel名为ParentViewModel. 在 中ParentViewModel,我正在从我的存储库中收集一个值。

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)
}
Run Code Online (Sandbox Code Playgroud)

在可组合项内部ParentScreen,我有一个ChildWidget可组合项,它有自己的ViewModel命名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
}
Run Code Online (Sandbox Code Playgroud)

现在,我想parentData进入ChildViewModel的构造函数。

问题

  • 我如何提供ChildViewModelFactory给 Navigation Compose 的hiltViewModel方法?
  • 如果这是不可能的,那么将对象从父可组合项注入到子可组合项的最合适的方法是什么ViewModellateinit创建如下所示的属性和方法怎么样init
@HiltViewModel
class ChildViewModel @Inject constructor(
) : ViewModel() {
    lateinit var parentData: Long

    fun init(parentData: Long){
        if(this::parentData.isInitialized) return
        this.parentData = parentData
    }
}
Run Code Online (Sandbox Code Playgroud)

ngl*_*ber 6

您可以使用EntryPointAccessors(来自 Hilt)和ViewModelProvider.Factory视图模型库来完成此操作。

在我的示例应用程序中,BookFormScreen正在使用BookFormViewModel,视图模型需要根据前一个屏幕传递的内容加载一本书bookId。这就是我所做的:

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
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,我没有使用@HiltViewModel. 将provideFactory用于提供工厂来创建此视图模型。

然后,定义ViewModelFactoryProvider入口点:

@EntryPoint
@InstallIn(ActivityComponent::class)
interface ViewModelFactoryProvider {

    fun bookDetailsViewModelFactory(): BookDetailsViewModel.Factory

    fun bookFormViewModelFactory(): BookFormViewModel.Factory
}
Run Code Online (Sandbox Code Playgroud)

现在,您需要定义一个可组合函数来使用此工厂提供视图模型。

@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))
}
Run Code Online (Sandbox Code Playgroud)

如果您使用导航库,您可以ViewModelStoreOwner在此函数中添加参数并在viewModel()函数调用中使用它。对于此参数,您可以传递NavBackStackEntry对象,这样视图模型的作用域将限于该特定的返回堆栈条目。

最后,您可以在可组合项中使用视图模型。

val bookFormViewModel: BookFormViewModel = bookFormViewModel(bookId)
Run Code Online (Sandbox Code Playgroud)