注入 ViewModels 的最佳方式?

best way for injecting ViewModels?

我过去使用过 koin,用 koin 注入 viewModel 是一个单一的衬垫。我需要知道没有它该怎么做! 我们应该在 ViewModelFactory 中为不同的视图模型使用大 switch/case 吗?

class ViewModelFactory: ViewModelProvider.Factory {

    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
       return when(modelClass) {
            is FirstViewModel -> FirstViewModel()
            is SecondViewModel -> SecondViewModel()
                ...
        }
    }
}

但是我必须将所有视图模型的所有依赖项注入工厂。那真是乱七八糟的代码。即使没有 switch/case 本身也是混乱的!我认为您不应该在大型项目中专门这样做。那么有哪些替代方法可以做到这一点?
dagger 如何解决这个问题?

实际上有很好的替代品。

第一个:人们往往会忘记的第一个是每个 ViewModel 都有一个 ViewModelfactory,这比拥有一个大型工厂要好得多。这就是我们所说的单一职责原则

class FirstViewModelFactory(val firstDependency: SomeClass, val secondDependency: SomeOtherClass): ViewModelProvider.Factory {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        FirsViewModel(firstDependency, secondDependency) as T
    }
}

并在 activity 中:

val viewModel: FirstViewModel = ViewModelProvider(this, FirstViewModelFactory(first, second))[FirstViewModel::class.java]

其次:如果你想为所有 ViewModel 只有一个工厂,你可以定义一个带有 lambda 的通用工厂:

class ViewModelFactory<VM: ViewModel>(val provider: () -> VM): ViewModelProvider.Factory {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        return  provider() as T
    }
}

并在 activity 或片段中像这样使用它:

val ViewModel: FirstViewModel = ViewModelProvider(this, ViewModelFactory<FirstViewModel>{
    FirstViewModel(first, second)
})[FirstViewModel::class.java]

这实际上会让您的生活轻松很多。但还是可以改进的

第三:你可以告诉dagger如何为你提供FirstViewModel及其依赖。 如果你不知道如何使用匕首,你可以尝试学习它然后阅读这一部分。 你需要告诉 dagger 你想要获取 FirstViewModle 的实例。这个地方在 AppComponent.

@Component(modules = [AppModule::class])
interface AppComponent {
    val applicationContext: Context
    val firstViewModel: FirstViewModel
    
    ...

    @Component.Factory
    interface Factory {
        fun create(@BindsInstance applicationContext: Context): AppComponent
    }
}

并且在您的 appModule 中,您需要告诉 dagger 如何使用 @Provides 注释提供 FirstViewModel 的依赖项。然后你需要在应用程序class中实例化你的匕首组件,有很多方法可以做到这一点,我只是使用工厂接口:

class MyApplication: Application() {
    val component: AppComponent by lazy {
        DaggerAppComponent.factory().create(applicationContext)
    }
}

别忘了在清单中添加 MyApplication

然后在你的 activity 中,你可以注入 viewModel 而不必担心依赖关系:

val appComponent = (application as MyApplication).appComponent

val viewModel: FirstViewModel = ViewModelProvider(this, ViewModelFactory<FirstViewModel>{
    appComponent.firstViewModel
})[FirstViewModel::class.java]

你可以使用 lazy 和扩展函数使它更漂亮和可读,你最终可以得到这样的东西:

private val viewModel: FirstViewModel by injectVmWith { appInjector.firstViewModel }

第四:你总是可以使用匕首的多重绑定。这样,您将 ViewModelFactory 注入 activity 或片段,然后从中检索 viewModel。有了这个,你告诉匕首把你所有的 viewModels 放在一个地图中,并将它注入 ViewModelFactory。你通过注释帮助 dagger 找到 viewModels。并且您还使用另一个注释告诉 dagger 他们的密钥是什么。您在另一个模块中完成所有这些工作,对于每个视图模型,您都需要在视图模型模块中使用一个函数。然后在你的大型工厂中而不是 switch/case ,你告诉匕首根据其类型从地图中获取所需的视图模型。这是一个服务定位器(反?)模式。这本身就是另一个话题,这个答案已经太长了。参考this or this

总结 : 我想如果你用的是匕首,第三个肯定是赢家。多重绑定也很好,但是每次添加视图模型时,您都需要记住在视图模型模块中也添加一个函数(就像 Koin 一样)。如果你不使用匕首,我认为第二种方式是最好的,但你应该决定什么是最适合你的。
公印也很棒!