Hilt injection in leanback Presenter

Viewed 207

I'm quite new with Hilt injection. I started to migrate my whole project to DI.

It works almost everywhere, but I'm facing an issue when it comes to the leanback presenters. I don't know if it is related to the leanback stuff or juste Hilt

class LiveShowCardPresenter constructor(context: Context, listener: ShowCardViewListener, val hasVariableWidth: Boolean = false) : ShowCardPresenter(context, listener) {
    override fun onCreateViewHolder(parent: ViewGroup): ViewHolder {
        val viewholder = ViewHolder(LiveShowCardView(context, hasVariableWidth))
        viewholder.prepareViewHolderForeground(context, settings.isATV)
        return viewholder
    }
...
}
abstract class ShowCardPresenter constructor(val context: Context, var listener: ShowCardViewListener?) : Presenter() {
    @Inject lateinit var detailsRepository: DetailsRepository
    @Inject lateinit var settings: BackendSettings
... }
@Singleton
class BackendSettings @Inject constructor(@ApplicationContext val context: Context) {
    val isATV = true // TODO

The following error occurs

    kotlin.UninitializedPropertyAccessException: lateinit property settings has not been initialized
    at ch.netplus.tv.ui.presenters.ShowCardPresenter.getSettings(ShowCardPresenter.kt:43)
    at ch.netplus.tv.ui.presenters.LiveShowCardPresenter.onCreateViewHolder(LiveShowCardPresenter.kt:23)

It means it crashes when the settings.isATV is called because the 'settings' var is not initialized at that time. What should I do to have the injection done on time ?

Thanks !

3 Answers

How do you inject dependencies into the LiveShowCardPresenter?

Since your abstract class(ShowCardPresenter) performs field injection, you somehow need to inject these fields when you create LiveShowCardPresenter. To perform those injections, you need to inject LiveShowCardPresenter as well. So, here is how it will look:

class LiveShowCardPresenter @Inject constructor(context: Context) : ShowCardPresenter(context) {
    var hasVariableWidth: Boolean = false

    override fun onCreateViewHolder(parent: ViewGroup): ViewHolder {
        val viewholder = ViewHolder(LiveShowCardView(context, hasVariableWidth))
        viewholder.prepareViewHolderForeground(context, settings.isATV)
        return viewholder
    }
    ...
}
abstract class ShowCardPresenter constructor(val context: Context) : Presenter() {
    var listener: ShowCardViewListener? = null

    @Inject lateinit var detailsRepository: DetailsRepository
    @Inject lateinit var settings: BackendSettings
... }

YourFragment.kt

@AndroidEntryPoint
class YourFragment: BrowseFragment() {
    @Inject
    lateinit var liveShowCardPresenterProvider: Provider<LiveShowCardPresenter>

    ...
    private void setupUIElements() {
        ...
        //new header
        setHeaderPresenterSelector(object : PresenterSelector() {
            override fun getPresenter(o: Any): Presenter {
                // Everytime when [liveShowCardPresenterProvider.get()] is called - new instance will be created
                val presenter = liveShowCardPresenterProvider.get().apply {
                    // You can set your parameters here
                    // hasVariableWidth = true
                    // listener = yourCustomListener
                }
                return presenter;
            }
        });
    }
    ...

If you need a single instance of the LiveShowCardPresenter in your fragment, you can perform a field injection on it without the Provider. Alternativerly, you can inject all of your dependencies in the Fragment and pass them to the LiveShowCardPresenter constructor.

You need to set the inject method in BackendSettings

Like:

class BackendSettings @Inject constructor() {

}

Ok, I walk through your codes step by step: Your LiveShowCardPresenter class should be changed as below:

class LiveShowCardPresenter @Inject constructor(
    @ApplicationContext context: Context,
    listener: ShowCardViewListener
) : ShowCardPresenter(context, listener) {

    var hasVariableWidth = false

    //your codes ...

}

As can be seen, @Inject is added before the constructor and also @ApplicationContext is added before context to provide context through the Hilt. also, hasVariableWidth is set outside of the constructor, if you don't like you can put it inside the constructor and provide it through the module and @Provide annotation. Now we should provide showCardViewListener. as I don't have access to your codes I provide it in a simple way.

 @Module
@InstallIn(SingletonComponent::class)
abstract class ShowCardListenerModule {
    @Binds
    @Singleton
    abstract fun bindShowCardViewListener(showCardViewListenerImpl: ShowCardViewListenerImpl) : ShowCardViewListener
}

ShowCardPresenter class has no changes. Finally, your BackendSettings class is changed like below:

 @Singleton
class BackendSettings @Inject constructor() {
    val isATV = true
    //your codes ...
}

@Inject is added and @Singleton is removed because doesn't need to it. I ran the above codes and it works without any problem.

Related