[英]Dagger2, adding a binding for ViewModelProvider.Factory in a dependant component
When attempting to add a ViewModel
bind into the multibinding for an inherited ViewModelFactory
(created with no scope) within a lower scope ( @FragmentScope
), I keep running into this error:当尝试将ViewModel
绑定添加到较低范围 ( @FragmentScope
) 内继承的ViewModelFactory
(创建时没有范围) 的@FragmentScope
,我不断@FragmentScope
此错误:
java.lang.IllegalArgumentException: unknown model class com.example.app.MyFragmentVM java.lang.IllegalArgumentException:未知模型类 com.example.app.MyFragmentVM
(note: the below is not by any means an exhaustive list, but are two good examples of resources and the kinds of advice I've perused) (注意:以下绝不是一个详尽的清单,而是我所阅读的资源和建议种类的两个很好的例子)
I'm relatively new to working with Dagger so I had to do a lot of Googling to try and understand what has been going on, but I've reached a point where, to my understanding, something should be working(?)...我对使用 Dagger 比较陌生,所以我不得不做很多谷歌搜索来尝试了解发生了什么,但我已经达到了一个点,据我所知,有些东西应该起作用(?).. .
From sources similar to [1], I removed the @Singleton
scope on ViewModelFactory
, but I still get the aforementioned crash saying there is no model class found in the mapping.从类似于 [1] 的来源中,我删除了ViewModelFactory
上的@Singleton
范围,但我仍然遇到上述崩溃,说在映射中找不到模型类。
From sources similar to [2] I tried to reinforce my understanding of how dependencies worked and how items are exposed to dependant components.从类似于 [2] 的来源,我试图加强我对依赖项如何工作以及项目如何暴露给依赖组件的理解。 I know and understand how ViewModelProvider.Factory
is available to my MyFragmentComponent
and it's related Modules.我知道并理解ViewModelProvider.Factory
如何可用于我的MyFragmentComponent
及其相关模块。
However I do not understand why the @Binds @IntoMap
isn't working for the MyFragmentVM
.但是我不明白为什么@Binds @IntoMap
不适用于MyFragmentVM
。
Let me first go through the setup of the stuff that already exists in the application -- almost none of it was scoped for specific cases让我首先介绍一下应用程序中已经存在的东西的设置——几乎没有一个是针对特定情况的
// AppComponent
@Component(modules=[AppModule::class, ViewModelModule::class])
interface AppComponent {
fun viewModelFactory(): ViewModelProvider.Factory
fun inject(activity: MainActivity)
// ... and other injections
}
// AppModule
@Module
class AppModule {
@Provides
@Singleton
fun providesSomething(): Something
// a bunch of other providers for the various injection sites, all @Singleton scoped
}
// ViewModelModule
@Module
abstract class ViewModelModule {
@Binds
abstract fun bindsViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
@Binds
@IntoMap
@ViewModelKey(MainActivityVM::class)
abstract fun bindsMainActivityVM(vm: MainActivityVM): ViewModel
}
// VMFactory
class ViewModelFactory @Inject constructor(
private val creators: Map<Class<out ViewModel>, @JvmSuppressWildcards Provider<ViewModel>>
): ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
val creator = creators[modelClass] ?: creators.entries.firstOrNull {
modelClass.isAssignableFrom(it.key)
}?.value ?: throw IllegalArgumentException("unknown model class $modelClass")
try {
@Suppress("UNCHECKED_CAST")
return creator.get() as T
} catch (e: Exception) {
throw RuntimeException(e)
}
}
}
And the following is how I am trying to add and utilize my @FragmentScope:以下是我尝试添加和利用我的 @FragmentScope 的方式:
// MyFragmentComponent
@FragmentScope
@Component(
dependencies = [AppComponent::class],
modules = [MyFragmentModule::class, MyFragmentVMModule::class]
)
interface MyFragmentComponent {
fun inject(fragment: MyFragment)
}
// MyFragmentModule
@Module
class MyFragmentModule {
@Provides
@FragmentScope
fun providesVMDependency(): VMDependency {
// ...
}
}
// MyFragmentVMModule
@Module
abstract class MyFragmentVMModule {
@Binds
@IntoMap
@ViewModelKey(MyFragmentVM::class)
abstract fun bindsMyFragmentVM(vm: MyFragmentVM): ViewModel
}
// MyFragment
class MyFragment : Fragment() {
@set:Inject
internal lateinit var vmFactory: ViewModelProvider.Factory
private lateinit var viewModel: MyFragmentVM
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
DaggerMyFragmentComponent.builder()
.appComponent(MyApplication.instance.component)
.build()
.inject(this)
viewModel = ViewModelProvider(this, vmFactory).get(MyFragmentVM::class.java)
}
}
What's interesting here to note is that MyFragmentModule
itself does NOT end up providing any unique injections for MyFragment
(those all come from AppComponent as it is right now).这里需要注意的是, MyFragmentModule
本身并没有最终为MyFragment
提供任何独特的注入(这些都来自 AppComponent,就像现在一样)。 It DOES however, provide unique injections for the ViewModel
that MyFragment
uses.但是,它确实为MyFragment
使用的ViewModel
提供了独特的注入。
The root of this problem is the difference between subcomponents and component dependencies .这个问题的根源在于子组件和组件依赖之间的差异。
When working with subcomponents, the parent component knows everything about its subcomponents.当使用子组件时,父组件知道关于它的子组件的一切。 As such, when a subcomponent requests a multibinding, the parent component can combine its contributions with those of the subcomponent.因此,当子组件请求多重绑定时,父组件可以将其贡献与子组件的贡献结合起来。 This even works transitively: if the subcomponent requests an unscoped ViewModelProvider.Factory
, the injected map will include bindings from the subcomponent.这甚至可以传递:如果子组件请求无作用域的ViewModelProvider.Factory
,则注入的映射将包含来自子组件的绑定。 (The same is true of a @Reusable
binding, but not a @Singleton
.) (同样适用于@Reusable
绑定,但不适用于@Singleton
。)
If you change your components with dependencies into subcomponents, everything will just work.如果您将具有依赖项的组件更改为子组件,一切都会正常进行。 However, this might not fit your desired architecture.但是,这可能不适合您想要的架构。 In particular, this is impossible if MyFragmentComponent
is in an Instant App module.特别是,如果MyFragmentComponent
位于 Instant App 模块中,则这是不可能的。
When working with component dependencies, the main component merely exposes objects through provision methods, and it does not know about any components that might depend on it .在处理组件依赖时,主组件只是通过提供方法暴露对象,它不知道任何可能依赖它的组件。 This time, when asked for a ViewModelProvider.Factory
, the main component does not have access to any @ViewModelKey
bindings except its own, and so the Factory
it returns will not include the MyFragmentVM
binding.这一次,当请求一个ViewModelProvider.Factory
,主组件除了它自己的之外不能访问任何@ViewModelKey
绑定,因此它返回的Factory
将不包括MyFragmentVM
绑定。
If MyFragmentComponent
does not require any ViewModel
bindings from AppComponent
, you can extract bindsViewModelFactory
into its own module and include it in both components.如果MyFragmentComponent
不需要来自AppComponent
任何ViewModel
绑定,您可以将bindsViewModelFactory
提取到它自己的模块中并将其包含在两个组件中。 That way, both components can create their own Factory
independently.这样,两个组件都可以独立创建自己的Factory
。
If you do need some ViewModel
bindings from AppComponent
, hopefully you can add those binding modules to MyFragmentComponent
as well.如果您确实需要AppComponent
一些ViewModel
绑定,希望您也可以将这些绑定模块添加到MyFragmentComponent
中。 If not, you would have to expose the map in AppComponent
, and then somehow combine those entries with your new bindings.如果没有,您将不得不在AppComponent
公开地图,然后以某种方式将这些条目与您的新绑定结合起来。 Dagger does not provide a good way to do this. Dagger 没有提供一个很好的方法来做到这一点。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.