Reputation: 3148
I know that generally it shouldn't make a difference that this is using Kotlin, but I've run into odd cases where the @Named
qualifier needed a scope in Kotlin.
I have a ViewHolderFactory
class that allows me to create a simple mapping of view type -> view holder class:
@Singleton
class ViewHolderFactoryImpl @Inject constructor(
private val viewHolderComponentProvider: Provider<ViewHolderSubcomponent.Builder>
): ViewHolderFactory(mapOf(
R.layout.view_error to ErrorViewHolder::class.java,
R.layout.view_soft_error to SoftErrorViewHolder::class.java,
R.layout.view_empty to EmptyViewHolder::class.java,
R.layout.view_loading to LoadingViewHolder::class.java,
R.layout.item_got_it to GotItViewHolder::class.java)) {
override fun createViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val viewHolder = super.createViewHolder(parent, viewType)
if (viewHolder is Injectable) {
viewHolderComponentProvider.get()
.viewHolder(viewHolder)
.build()
.inject(viewHolder)
}
return viewHolder
}
}
The ViewHolderSubcomponent is defined below, the goal is to be able to create one subcomponent for each view holder and inject a few things:
@ViewHolderScope
@Subcomponent(modules = [ViewHolderModule::class])
interface ViewHolderSubcomponent {
fun inject(viewHolder: RecyclerView.ViewHolder)
fun viewHolder(): RecyclerView.ViewHolder
@Subcomponent.Builder
interface Builder {
@BindsInstance
fun viewHolder(viewHolder: RecyclerView.ViewHolder): Builder
fun build(): ViewHolderSubcomponent
}
}
The ViewHolderModule is defined as:
@Module
class ViewHolderModule {
@Provides @ViewHolderScope
fun provideSectionTitleViewHolder(viewHolder: RecyclerView.ViewHolder): SectionTitleViewHolder =
SectionTitleViewHolder(viewHolder.itemView)
}
When I run the app I find that the injection didn't work, my @Inject lateinit var
values are null. Looking at the generated code I can see why:
@Override
public void inject(RecyclerView.ViewHolder viewHolder) {
MembersInjectors.<RecyclerView.ViewHolder>noOp().injectMembers(viewHolder);
}
There's no MembersInjectors<RecyclerView.ViewHolder>
created for this subcomponent. I can't figure out how to get this to work. I know that I should be able to inject into objects not created by dagger, I just can't quite figure out what I'm missing here.
Oh, if it helps any, I did make sure to include the ViewHolderSubcomponent
in my AppModule
's list of subcomponents
Upvotes: 4
Views: 654
Reputation: 34542
inject(viewHolder: RecyclerView.ViewHolder)
will always be a no-op, because framework classes (or most libraries in this case) don't have any @Inject
annotated fields. Dagger will only generate code for the class named in your inject(MyClass instance)
methods, not for any of its sub-types.
So if you have a ErrorViewHolder : RecyclerView.ViewHolder
, then you have to use a component that has a inject(ErrorViewHolder instance)
method to generate code to inject ErrorViewHolder
.
To clarify, because it's generated code—and not dynamic reflection at runtime—calling inject(viewHolder: RecyclerView.ViewHolder)
like you do with an viewHolder : ErrorViewHolder
will still try to inject fields for RecyclerView.ViewHolder
, not ErrorViewHolder
. And RecyclerView.ViewHolder
will always be a no-op as mentioned.
You will have to modify your setup quite a bit so that you can provide a specific subcomponent that can inject a specific viewholder, you can't use one "generic" component for different types. You could create a base class between RecyclerView.ViewHolder
and ErrorViewHolder
, but then again, you could only inject fields declared (and @Inject
annotated) in your base class, not the specific child.
Upvotes: 1
Reputation: 39873
The Kotlin proptery itself is not known to Dagger. What you are trying to achieve could be handled as a field or setter injection. Since a setter is generated with any property I usually go for @set:Inject
.
@set:Inject lateinit var myVar: Type
Alternatively you can consider constructor injections. Like this you can define the properties as val
and private.
Upvotes: 0