I'm trying to migrate an existing project to Hilt (from Dagger), and I've run into an issue that I can't figure out. Since it is an existing project using (a form of) MVC-structure, most guides don't seem to account for my setup, since it doesn't rely on ViewModels.
I've started migrating the activity:
@Module
@InstallIn(ActivityComponent::class)
abstract class ActivityModule {
@Binds
abstract fun bindPresenter(presenter: MainPresenter): MainContract.Presenter
@Binds
abstract fun bindActivity(view: MainActivity): MainContract.View
@Binds
abstract fun provideAccidentPresenter(presenter: AccidentPresenter): AccidentContract.Presenter
@Binds
abstract fun provideAccidentActivity(view: AccidentActivity): AccidentContract.View
}
@InstallIn(ActivityComponent::class)
@Module
object ActivitiesModule {
@Provides
fun provideMainActivity(activity: Activity): MainActivity {
return activity as MainActivity
}
@Provides
fun provideAccidentActivity(activity: Activity): AccidentActivity {
return activity as AccidentActivity
}
}
So far, so good. But when it comes to setting up the bind between the View (Fragment) and the presenter, I have this module:
@Module
@InstallIn(SingletonComponent::class)
abstract class DashboardPresenterModule {
@Binds
abstract fun bindDashboardPresenter(presenter: DashboardPresenter): DashboardContract.Presenter
@Binds
abstract fun bindDashboardView(view: DashboardFragment): DashboardContract.View
}
Both the DashboardPresenter and the DashboardView contains @Inject-annotations that references each-other:
class DashboardPresenter @Inject constructor() : DashboardContract.Presenter, LocationPermissionPresenter() {
@Inject
lateinit var view: DashboardContract.View
@Inject
lateinit var api: Api
@Inject
lateinit var preferences: Preferences
[...]
}
@AndroidEntryPoint
class DashboardFragment : PermissionBaseFragment(), DashboardContract.View, View.OnClickListener {
@Inject
lateinit var preferences: Preferences
@Inject
lateinit var presenter: DashboardContract.Presenter
[...]
}
The code above doesn't compile, and gives the following error
/Users/zippert/git/[** redacted**]/app/build/generated/hilt/component_sources/debug/[** redacted**]/app/App_HiltComponents.java:141: error: [Dagger/MissingBinding] [** redacted**].ui.dashboard_screen.DashboardFragment cannot be provided without an @Inject constructor or an @Provides-annotated method. This type supports members injection but cannot be implicitly provided.
public abstract static class SingletonC implements App_GeneratedInjector,
^
A binding for [** redacted**].ui.dashboard_screen.DashboardFragment exists in [** redacted**].app.App_HiltComponents.FragmentC:
[** redacted**].ui.dashboard_screen.DashboardFragment is injected at
[[** redacted**].app.App_HiltComponents.SingletonC] [** redacted**].di.mvp.DashboardPresenterModule.bindDashboardView(view)
[** redacted**].ui.dashboard_screen.DashboardContract.View is injected at
[[** redacted**].app.App_HiltComponents.SingletonC] [** redacted**].ui.dashboard_screen.DashboardPresenter.view
[** redacted**].ui.dashboard_screen.DashboardPresenter is injected at
[[** redacted**].app.App_HiltComponents.SingletonC] [** redacted**].di.mvp.DashboardPresenterModule.bindDashboardPresenter(presenter)
[** redacted**].ui.dashboard_screen.DashboardContract.Presenter is injected at
[[** redacted**].App_HiltComponents.FragmentC] [** redacted**].ui.dashboard_screen.DashboardFragment.presenter
[** redacted**].ui.dashboard_screen.DashboardFragment is injected at
[[** redacted**].app.App_HiltComponents.FragmentC] [** redacted**].ui.dashboard_screen.DashboardFragment_GeneratedInjector.injectDashboardFragment([** redacted**].ui.dashboard_screen.DashboardFragment) [[** redacted**].app.App_HiltComponents.SingletonC → [** redacted**].app.App_HiltComponents.ActivityRetainedC → [** redacted**].app.App_HiltComponents.ActivityC → [** redacted**].app.App_HiltComponents.FragmentC]
Now, debugging this behaviour, I notice that the error goes away if I either avoid injecting the presenter in the fragment, or if I avoid injecting the view/fragment in the presenter. Technically, I guess I could pass one of the references as a parameter during some part of an init-block but:
A) It negates part of the purpose of the DI
B) I don't want to do this in every fragment throughout the app.
It's probably something super-obvious that I'm missing, and in a world of "oceans of time", I'd want to get rid of the "MVC"-setup completely and replace it with viewmodels, but that is not feasible. The existing codebase worked fine when we had only Dagger.
I'm not a fan of the chosen design pattern, and I'm unsure if the pattern is applicable. Can someone enlighten me on what is incorrect in the config/setup, or if there is some conceptual issue with the mvc-setup that can be fixed?