web-dev-qa-db-ja.com

Dagger2:フラグメントにバインドされたインジェクターファクトリがありません

DIフレームワークにdagger-Android APIを使用するように構築しているプロジェクトを変換しようとしていますが、@ ContributesAnroidInjectorを使用してフラグメントを挿入しようとすると、IllegalArgumentExceptionで行き止まりになりました。

関連するモジュールとコンポーネントは次のとおりです。

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

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とLoginActivityはそれぞれのプレゼンターにのみ依存しており、短剣はこれらでうまく機能します。しかし、私のMainActivityには多数のフラグメントが含まれている可能性があり、これらを使用してこれらのフラグメントの1つを挿入しようとするとクラッシュします。

HomeFragment.Java

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

これがこのクラッシュのlogcatです:

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)

コードのどこに問題があるのか​​わかりません。 HomeFragmentのバインディングをActivityBindingModuleに移動すると、アプリは正常に動作しますが、それらのバインディングをMainActivityModuleに戻すとクラッシュが発生します。ここで何が悪いのですか?

編集:

public class MyApp extends DaggerApplication {

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

そして私の主な活動:

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

HomeFragmentからAndroidSupportInjection.inject(this)を使用して注入すると、Daggerは親フラグメント階層をたどって、HasSupportFragmentInjectorを実装する人を見つけます。これを機能させるには、MainActivityを実装してDaggerAppCompatActivityを実装するHasSupportFragmentInjectorを作成します。

AndroidSupportInjection.inject(Fragment fragment)のドキュメントから:

関連する{@link dagger.Android.AndroidInjector}実装が見つかった場合は{@code fragment}を挿入し、それ以外の場合は{@link IllegalArgumentException}をスローします。

次のアルゴリズムを使用して、{@ code fragment}の注入に使用する適切な{@code AndroidInjector}を見つけます。

  1. 親フラグメント階層をたどって、{@ link HasSupportFragmentInjector}を実装するフラグメントを見つけます。
  2. {@link HasSupportFragmentInjector}を実装している場合は{@code fragment}の{@link Fragment#getActivity()アクティビティ}を使用し、実装していない場合は
  3. {@link HasSupportFragmentInjector}を実装している場合は、{@ link Android.app.Application}を使用します。

それらのいずれも{@link HasSupportFragmentInjector}を実装していない場合、{@ link IllegalArgumentException}がスローされます。

親フラグメント、アクティビティ、またはアプリケーションが{@link HasSupportFragmentInjector}を実装していない場合、@ throws IllegalArgumentException。

これにより、ダガーは

@FragmentScope
@ContributesAndroidInjector
abstract HomeFragment provideHomeFragment();

MainActivityModuleからHomeFragmentに注入します。

24
Benjamin

同様のエラーが発生した他のシナリオも考えられます。

考えられるケース1:
DialogFragmentからFragmentが表示されたとき。
同じFragmentManagerを使用することが重要です。

たとえば、「フラグメントスコープの画面」があるとします。

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

サブコンポーネントあり

@Module
abstract class HomeInjectors {

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

}

ここで重要なのは、ダイアログフラグメントを表示する場合は、アクティビティのものではなく子フラグメントマネージャーを使用する必要があることです。

この場合、HomeFragmentからダイアログを表示すると、

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

そして

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

動作しないでしょう。

代わりに行う必要があります:

detailsDialog.show(childFragmentManager, "some tag)

考えられるケース2:子フラグメントを持つ親フラグメント。

「より小さい」スコープで子フラグメントを作成するには(サンプルコードは上記と同じですが、DetailsDialogFragmentを通常のフラグメントとHomeFragmentの子と見なします)。

私の場合、子フラグメントは親のフラグメントインジェクタを見つけることができませんでした。

その理由は、子フラグメントインジェクターを提供しているときに、誤ってBaseFragment implement HasFragmentInjector
ただし、サポートフラグメント(AndroidXなど)を使用しているので、BaseFragment implement HasSupportFragmentInjector

したがって、BaseFragmentは次のようになります。

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

特定の理由で「BaseFragment」にDaggerFragment以外の親が必要な場合に役立ちます

1
Leo Droidcoder