Dagger2: no injector factory bound for fragment

23,488

Solution 1

When you inject using AndroidSupportInjection.inject(this) from your HomeFragment, Dagger will walk the parent-fragment hierarchy to find someone that implements HasSupportFragmentInjector. To make it work, make your MainActivity extends DaggerAppCompatActivity which implements HasSupportFragmentInjector.

From the doc of AndroidSupportInjection.inject(Fragment fragment):

Injects {@code fragment} if an associated {@link dagger.android.AndroidInjector} implementation can be found, otherwise throws an {@link IllegalArgumentException}.

Uses the following algorithm to find the appropriate {@code AndroidInjector} to use to inject {@code fragment}:

  1. Walks the parent-fragment hierarchy to find the a fragment that implements {@link HasSupportFragmentInjector}, and if none do
  2. Uses the {@code fragment}'s {@link Fragment#getActivity() activity} if it implements {@link HasSupportFragmentInjector}, and if not
  3. Uses the {@link android.app.Application} if it implements {@link HasSupportFragmentInjector}.

If none of them implement {@link HasSupportFragmentInjector}, a {@link IllegalArgumentException} is thrown.

@throws IllegalArgumentException if no parent fragment, activity, or application implements {@link HasSupportFragmentInjector}.

With this, Dagger will use

@FragmentScope
@ContributesAndroidInjector
abstract HomeFragment provideHomeFragment();

from your MainActivityModule to inject inside your HomeFragment.

Solution 2

There could be other scenarious, where I got similar errors:

Possible case 1:
When you have a DialogFragment shown from a Fragment.
It's important to use the same FragmentManager.

For instance you have a "fragment-scoped screen":

@FragmentScope
@ContributesAndroidInjector(modules = [HomeInjectors::class])
abstract fun provideHomeFragment() HomeFragment

With a subcomponent

@Module
abstract class HomeInjectors {

    @ChildFragmentScope
    @ContributesAndroidInjector(modules = [DetailsModule::class])
    abstract fun provideDetailsFragment(): DetailsDialogFragment

}

Important here to note, that when you show a dialog fragment, you should use child fragment manager not the Activity's one.

in this case, if you show dialog from the HomeFragment,

detailsDialog.show(activity.supportFragmentManager, "some tag)

and

detailsDialog.show(requireFragmentManager(), "some tag)

will not work.

You should do instead:

detailsDialog.show(childFragmentManager, "some tag)

Possible case 2: Parent fragment with child fragments.

In order to make child fragments with "smaller" scope (Sample code is the same as above, but consider DetailsDialogFragment a regular fragment and a child of the HomeFragment).

In my case, the child fragment wasn't able to find Parent's fragment injector.

The reason was that while providing a child fragment injector, I mistakenly made my BaseFragment implement HasFragmentInjector.
However, since I use support fragments (AndroidX or whatever), I should have made BaseFragment implement HasSupportFragmentInjector

So the BaseFragment may look like:

import androidx.fragment.app.Fragment

abstract class BaseFragment : SometFragment(), HasSupportFragmentInjector {

    @Inject lateinit var childFragmentInjector: DispatchingAndroidInjector<Fragment>

    override fun supportFragmentInjector(): AndroidInjector<Fragment> {
        return childFragmentInjector
    }

    override fun onAttach(context: Context) {
        AndroidSupportInjection.inject(this)
        super.onAttach(context)
    }
}

It is useful when by certain reasons your "BaseFragment" must have other than DaggerFragment parent

Share:
23,488
C. Marr
Author by

C. Marr

Updated on June 16, 2020

Comments

  • C. Marr
    C. Marr about 4 years

    I'm trying to convert a project that I'm building to use the dagger-android API for the DI framework, but I'm running into a dead end with an IllegalArgumentException when trying to inject a fragment using @ContributesAnroidInjector.

    The relevant modules and components are included below:

    ApplicationComponent.java

    @Singleton
    @Component(modules = {AndroidSupportInjectionModule.class,
        ApplicationModule.class,
        ActivityBindingModule.class,
        DataManagerModule.class})
    public interface ApplicationComponent extends AndroidInjector<MyApplication> {
    
    DataManagerContract getDataManager();
    
    void inject(MyApplication application);
    
    @Component.Builder
    interface Builder {
    
        @BindsInstance
        ApplicationComponent.Builder application(Application application);
    
        ApplicationComponent build();
        }
    }
    

    my ActivityBindingModule.java:

    @Module
    public abstract class ActivityBindingModule {
    
        @ActivityScope
        @ContributesAndroidInjector(modules = MainActivityModule.class)
        abstract MainActivity bindMainActivity();
    
        @ActivityScope
        @ContributesAndroidInjector(modules = SplashActivityModule.class)
        abstract SplashActivity bindSplashActivity();
    
        @ActivityScope
        @ContributesAndroidInjector(modules = LoginActivityModule.class)
        abstract LoginActivity bindLoginActivity();
    }
    

    MainActivityModule.java

    @Module
    public abstract class MainActivityModule {
    
        @ActivityScope
        @Binds
        abstract MainActivityContract.Presenter provideMainActivityPresenter(MainActivityPresenter presenter);
    
        @FragmentScope
        @ContributesAndroidInjector
        abstract HomeFragment provideHomeFragment();
    
        @FragmentScope
        @Binds
        abstract HomeFragmentContract.Presenter provideHomeFragmentPresenter(HomeFragmentPresenter presenter);
    
        // Inject other fragments and presenters
    }
    

    SplashActivity and LoginActivity only depend on their respective presenters, and dagger works fine in these. But my MainActivity can contain numerous fragments and causes a crash when trying to inject one of those fragments using:

    HomeFragment.java

    public class HomeFragment extends Fragment {
        ....
        @Override
        public void onAttach(Context context) {
            AndroidSupportInjection.inject(this);
            super.onAttach(context);
        }
        ....
    }
    

    Here is my logcat for this crash:

    java.lang.RuntimeException: Unable to start activity ComponentInfo{com.myapp/com.myapp.main.MainActivity}: java.lang.IllegalArgumentException: No injector factory bound for Class<com.myapp.ui.main.Home.HomeFragment>
    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2665)
    at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2726)
    at android.app.ActivityThread.-wrap12(ActivityThread.java)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1477)
    at android.os.Handler.dispatchMessage(Handler.java:102)
    at android.os.Looper.loop(Looper.java:154)
    at android.app.ActivityThread.main(ActivityThread.java:6119)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)
    Caused by: java.lang.IllegalArgumentException: No injector factory bound for Class<com.myapp.ui.main.Home.HomeFragment>
            at dagger.android.DispatchingAndroidInjector.inject(DispatchingAndroidInjector.java:104)
            at dagger.android.support.AndroidSupportInjection.inject(AndroidSupportInjection.java:74)
            at com.myapp.ui.main.Home.HomeFragment.onAttach(HomeFragment.java:65)
            at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1363)
            at android.support.v4.app.FragmentTransition.addToFirstInLastOut(FragmentTransition.java:1109)
            at android.support.v4.app.FragmentTransition.calculateFragments(FragmentTransition.java:996)
            at android.support.v4.app.FragmentTransition.startTransitions(FragmentTransition.java:99)
            at android.support.v4.app.FragmentManagerImpl.executeOpsTogether(FragmentManager.java:2364)
            at android.support.v4.app.FragmentManagerImpl.removeRedundantOperationsAndExecute(FragmentManager.java:2322)
            at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:2229)
            at android.support.v4.app.FragmentManagerImpl.dispatchStateChange(FragmentManager.java:3221)
            at android.support.v4.app.FragmentManagerImpl.dispatchActivityCreated(FragmentManager.java:3171)
            at android.support.v4.app.FragmentController.dispatchActivityCreated(FragmentController.java:192)
            at android.support.v4.app.FragmentActivity.onStart(FragmentActivity.java:560)
            at android.support.v7.app.AppCompatActivity.onStart(AppCompatActivity.java:177)
            at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1248)
            at android.app.Activity.performStart(Activity.java:6696)
            at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2628)
            at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2726) 
            at android.app.ActivityThread.-wrap12(ActivityThread.java) 
            at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1477) 
            at android.os.Handler.dispatchMessage(Handler.java:102) 
            at android.os.Looper.loop(Looper.java:154) 
            at android.app.ActivityThread.main(ActivityThread.java:6119) 
            at java.lang.reflect.Method.invoke(Native Method) 
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886) 
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)
    

    I'm not sure where the problem in the code is. If I move the bindings for HomeFragment to the ActivityBindingModule, the app runs fine, but the crash comes back if I bring those bindings back into the MainActivityModule. What am I doing wrong here?

    EDIT:

    public class MyApp extends DaggerApplication {
    
        @Override
        protected AndroidInjector<? extends DaggerApplication> applicationInjector() {
            return DaggerApplicationComponent.builder().application(this).build();
        }
    }
    

    and my main activity:

    public class MainActivity extends AppCompatActivity
        implements MainActivityContract.View,
        NavigationView.OnNavigationItemSelectedListener {
    
    @Inject
    MainActivityContract.Presenter mPresenter;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        AndroidInjection.inject(this);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    
        // Open home fragment on first start
        if (savedInstanceState == null) {
            // Create new instance of HomeFragment
            HomeFragment homeFragment = HomeFragment.newInstance();
    
            FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
            fragmentTransaction.replace(R.id.content_main, homeFragment)
                    .commit();
        }
    
    // Other logic
    }
    
  • C. Marr
    C. Marr over 6 years
    Thanks, that make a lot of sense. Your work-around of using @Inject HomeFragment homeFragment seems like the obvious solution for an activity which only contains a single fragment, but in a situation where an activity can contain a larger number of fragments, it seems inefficient to inject an instance of every fragment in the MainActivity, no? Is there a way to do something similar but not inject an instance of every fragment, or only inject the fragment when it will be needed?
  • Benjamin
    Benjamin over 6 years
    to inject the fragment only when it will be needed, you can use the lazy injection, see here: google.github.io/dagger/api/2.10/dagger/Lazy.html
  • Benjamin
    Benjamin over 6 years
    @C.Marr see my updated answer to make it work to way you really want it to
  • Wahib Ul Haq
    Wahib Ul Haq almost 6 years
    For those who are using DaggerFragment and faced this problem, what worked for me was that even though I wasn't injecting anything in the related Activity but I had to extend it from DaggerAppCompactActivity and also need to provide it with @ContributesAndroidInjector() like I was already doing for the Fragment.