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

ix0qys7i  于 2023-03-30  发布在  Kotlin
关注(0)|答案(1)|浏览(78)

我有一个名为ParentScreen的可组合对象和一个名为ParentViewModelViewModel。在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)
}

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
}

现在,我想在ChildViewModel的构造函数中获取parentData

问题

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

    fun init(parentData: Long){
        if(this::parentData.isInitialized) return
        this.parentData = parentData
    }
}
ukdjmx9f

ukdjmx9f1#

您可以使用EntryPointAccessors(来自Hilt)和View Model库中的ViewModelProvider.Factory来完成此操作。
在我的sample app中,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
            }
        }
    }
}

注意,我没有使用@HiltViewModelprovideFactory将用于提供一个工厂来创建这个视图模型。
然后,为入口点定义ViewModelFactoryProvider

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

    fun bookDetailsViewModelFactory(): BookDetailsViewModel.Factory

    fun bookFormViewModelFactory(): BookFormViewModel.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))
}

如果你使用的是导航库,你可以在这个函数中添加ViewModelStoreOwner参数,并在viewModel()函数调用中使用它。对于这个参数,你可以传递NavBackStackEntry对象,这样,视图模型的作用域就会被限制到那个特定的回栈条目。
最后,您可以在组合中使用视图模型。

val bookFormViewModel: BookFormViewModel = bookFormViewModel(bookId)

相关问题