0

I have followed Android Architecture Blueprints Dagger2 for dependency injection: URL

Now I want to inject Adapter to my Fragment class:

@ActivityScoped
class MainFragment @Inject
constructor(): DaggerFragment(), ArtistClickCallback {

    @Inject lateinit var adapter : ArtistAdapter 
}

Main Module class:

@Module
abstract class MainModule {
    @FragmentScoped
    @ContributesAndroidInjector(modules = [MainFragmentModule::class])
    internal abstract fun mainFragment(): MainFragment

    @Binds
    internal abstract fun bindArtistClickCallback(mainFragment: MainFragment) : ArtistClickCallback
}

MainFragmentModule:

@Module
class MainFragmentModule {

    @Provides
    fun provideArtistAdapter() = ArtistAdapter()
}

And this is my adapter class:

 class ArtistAdapter @Inject constructor(
  private val artistClickCallback : ArtistClickCallback
) : PagedListAdapter<LastFmArtist, RecyclerView.ViewHolder>(POST_COMPARATOR)

When I build the project I get following Kotlin compiler error:

error: [Dagger/DependencyCycle] Found a dependency cycle:
public abstract interface AppComponent extends dagger.android.AndroidInjector<com.sample.android.lastfm.LastFmApp> {
                ^
      com.sample.android.lastfm.ui.main.MainFragment is injected at


com.sample.android.lastfm.ui.main.MainModule.bindArtistClickCallback$app_debug(mainFragment)
  com.sample.android.lastfm.ArtistClickCallback is injected at
      com.sample.android.lastfm.ui.main.ArtistAdapter.artistClickCallback
  com.sample.android.lastfm.ui.main.ArtistAdapter is injected at
      com.sample.android.lastfm.ui.main.MainFragment.adapter
  com.sample.android.lastfm.ui.main.MainFragment is injected at
      com.sample.android.lastfm.ui.main.MainActivity.mainFragment
  com.sample.android.lastfm.ui.main.MainActivity is injected at
      dagger.android.AndroidInjector.inject(T) [com.sample.android.lastfm.di.AppComponent → com.sample.android.lastfm.di.ActivityBindingModule_MainActivity$app_debug.MainActivitySubcomponent]

Can you suggest me how to solve this problem?

Codes can be found at URL

Tanveer Munir
  • 1,956
  • 1
  • 12
  • 27
Ali
  • 9,800
  • 19
  • 72
  • 152

1 Answers1

0

Your fragment should probably not have @ActivityScoped as a scope. Further do not use constructor injection with fragments (or any other framework type)! The Android framework will create those objects in some cases, and you will end up with the wrong reference in your classes. Add the fragment to the corresponding component via its builder.

Also you're using a provides annotated method as well as constructor injection (@Inject constructor()). Pick one. Since you also use field injection within the ArtistAdapter the next "error" you would encounter would be a null callback because you don't inject the adapter anywhere. You just create the object.
Constructor injection should usually be favored, which will also inject fields. Remove the following completely, keep the annotation on the construcor:

@Provides
fun provideArtistAdapter() = ArtistAdapter()

Moving on, your error originates in MainActivitySubcomponent (last line) and seems to be because your MainFragment is bound as an ArtistClickCallback, but requires a ArtistAdapter which requires a ArtistClickCallback...hence your dependency cycle.

This issue should resolve itself once you fix the problems mentioned (@Inject constructor on the fragment in this case) above, since it originates through the MainFragment being constructed by Dagger within the MainActivitySubcomponent, which is the wrong place anyways since your fragment should have a lower scope than the Activity.

Further you need to move your binding (@Binds fun bindArtistClickCallback) into the MainFragmentModule, since there is no fragment to bind in the Activity component (where you add the binding currently)

When you fix all those issues, you will bind your fragment to the correct FragmentSubcomponent, where you will bind it as a Callback, with which you can then create the Adapter and it should work.

I recommend you have a more thorough look on Dagger and make sure to understand all the issues / fixes pointed out.


This is how it should look

@FragmentScoped
class MainFragment(): DaggerFragment(), ArtistClickCallback {

  @Inject lateinit var adapter : ArtistAdapter 
}

@Module
abstract class MainModule {
  @FragmentScoped
  @ContributesAndroidInjector(modules = [MainFragmentModule::class])
  internal abstract fun mainFragment(): MainFragment
}

@Module
class MainFragmentModule {

  @Binds
  internal abstract fun bindArtistClickCallback(mainFragment: MainFragment) : ArtistClickCallback
}

class ArtistAdapter @Inject constructor(
  private val artistClickCallback : ArtistClickCallback
) : PagedListAdapter<LastFmArtist, RecyclerView.ViewHolder>(POST_COMPARATOR)
David Medenjak
  • 33,993
  • 14
  • 106
  • 134
  • Thanks. I receive following error : `MainFragment cannot be provided without an @Inject constructor or an @Provides-annotated method. This type supports members injection but cannot be implicitly provided.` – Ali Feb 27 '19 at 19:53
  • `MainFragmentModule` should be abstract in this case since the method inside is abstract as well. – Ali Feb 27 '19 at 19:54
  • In google sample they have used @ActivityScoped as well as inject constructor : https://github.com/googlesamples/android-architecture/blob/todo-mvp-dagger/todoapp/app/src/main/java/com/example/android/architecture/blueprints/todoapp/tasks/TasksFragment.java – Ali Feb 27 '19 at 19:58
  • @Ali You're right, seems like they do. Seems like they create the fragment via constructor injection to use within the `@ActivityScope` aka the Activity...but they still have a subcomponent for the fragment...which would end up with the fragment being injected twice (?)...This seems also like it would break once you add any `@FragmentScoped` dependency within the fragment since the higher scope could not provide it anymore – David Medenjak Feb 27 '19 at 20:16
  • By using injected constructor for fragment, I receive following error : `ArtistClickCallback cannot be provided without an @Provides-annotated method.` Can you suggest how to solve that? – Ali Feb 27 '19 at 21:01
  • @Ali Please see [here for reference](https://stackoverflow.com/q/44912080/1837367) I'd assume it's a scoping issue, but hard to tell without the full error and code – David Medenjak Feb 27 '19 at 21:20
  • I created a branch for that : https://github.com/Ali-Rezaei/Last.fm-MVI/tree/inject_adapter. If you had time please take a look. – Ali Feb 28 '19 at 09:12