web-dev-qa-db-fra.com

Dagger2: aucune usine d'injecteurs à destination du fragment

J'essaie de convertir un projet que je construis pour utiliser l'API dagger-Android pour le framework DI, mais je suis dans une impasse avec une exception IllegalArgumentException lorsque j'essaie d'injecter un fragment à l'aide de @ContributesAnroidInjector.

Les modules et composants pertinents sont inclus ci-dessous:

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();
    }
}

mon 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 et LoginActivity ne dépendent que de leurs présentateurs respectifs, et le poignard fonctionne bien dans ces derniers. Mais mon MainActivity peut contenir de nombreux fragments et provoque un crash lors de la tentative d'injection de l'un de ces fragments à l'aide de:

HomeFragment.Java

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

Voici mon logcat pour ce 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)

Je ne sais pas où est le problème dans le code. Si je déplace les liaisons pour HomeFragment vers ActivityBindingModule, l'application fonctionne correctement, mais le plantage revient si je ramène ces liaisons dans MainActivityModule. Qu'est-ce que je fais mal ici?

MODIFIER:

public class MyApp extends DaggerApplication {

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

et mon activité principale:

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
}
9
C. Marr

Lorsque vous injectez à l'aide de AndroidSupportInjection.inject(this) à partir de votre HomeFragment, Dagger parcourt la hiérarchie parent-fragment pour trouver quelqu'un qui implémente HasSupportFragmentInjector. Pour le faire fonctionner, faites votre MainActivity étend DaggerAppCompatActivity qui implémente HasSupportFragmentInjector.

Du doc ​​de AndroidSupportInjection.inject(Fragment fragment):

Injecte {@code fragment} si une implémentation {@link dagger.Android.AndroidInjector} associée peut être trouvée, sinon lance une {@link IllegalArgumentException}.

Utilise l'algorithme suivant pour trouver le {@code AndroidInjector} approprié à utiliser pour injecter {@code fragment}:

  1. Parcourt la hiérarchie du fragment parent pour trouver le fragment qui implémente {@link HasSupportFragmentInjector}, et si aucun ne le fait
  2. Utilise l'activité {@link Fragment # getActivity () de {fragment de code} si elle implémente {@link HasSupportFragmentInjector}, et sinon
  3. Utilise {@link Android.app.Application} s'il implémente {@link HasSupportFragmentInjector}.

Si aucun d'entre eux n'implémente {@link HasSupportFragmentInjector}, une {@link IllegalArgumentException} est levée.

@throws IllegalArgumentException si aucun fragment parent, activité ou application n'implémente {@link HasSupportFragmentInjector}.

Avec cela, Dagger utilisera

@FragmentScope
@ContributesAndroidInjector
abstract HomeFragment provideHomeFragment();

depuis votre MainActivityModule pour injecter à l'intérieur de votre HomeFragment.

24
Benjamin

Il pourrait y avoir d'autres scénarios, où j'ai eu des erreurs similaires:

Cas possible 1:
Lorsque vous avez un DialogFragment affiché à partir d'un Fragment.
Il est important d'utiliser le même FragmentManager.

Par exemple, vous avez un "écran à portée de fragment":

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

Avec un sous-composant

@Module
abstract class HomeInjectors {

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

}

Il est important de noter ici que lorsque vous affichez un fragment de dialogue, vous devez utiliser gestionnaire de fragment enfant et non celui de l'activité.

dans ce cas, si vous affichez la boîte de dialogue du HomeFragment,

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

et

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

ne fonctionnera pas.

Vous devriez plutôt faire:

detailsDialog.show(childFragmentManager, "some tag)

Cas possible 2: Fragment parent avec fragments enfant.

Afin de créer des fragments enfants avec une portée "plus petite" (l'exemple de code est le même que ci-dessus, mais considérez DetailsDialogFragment comme un fragment normal et un enfant du HomeFragment).

Dans mon cas, le fragment enfant n'a pas pu trouver l'injecteur de fragment de Parent.

La raison en était qu'en fournissant un injecteur de fragments pour enfants, j'ai fait par erreur mon BaseFragment implement HasFragmentInjector.
Cependant, comme j'utilise des fragments de support (AndroidX ou autre), j'aurais dû faire BaseFragment implement HasSupportFragmentInjector

Ainsi, le BaseFragment peut ressembler à:

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)
    }
}

Il est utile lorsque, pour certaines raisons, votre "BaseFragment" doit avoir un parent autre que DaggerFragment

1
Leo Droidcoder