Dagger2: no injector factory bound for fragment
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}:
- Walks the parent-fragment hierarchy to find the a fragment that implements {@link HasSupportFragmentInjector}, and if none do
- Uses the {@code fragment}'s {@link Fragment#getActivity() activity} if it implements {@link HasSupportFragmentInjector}, and if not
- 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
C. Marr
Updated on June 16, 2020Comments
-
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 over 6 yearsThanks, 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 theMainActivity
, 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 over 6 yearsto 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 over 6 years@C.Marr see my updated answer to make it work to way you really want it to
-
Wahib Ul Haq almost 6 yearsFor 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 fromDaggerAppCompactActivity
and also need to provide it with@ContributesAndroidInjector()
like I was already doing for the Fragment.