web-dev-qa-db-ja.com

依存関係の注入にdagger2を使用するときにスーパークラスを注入することはできますか?

私はAndroidアプリケーションでDIにDagger2を使用します。@ Injectフィールドを使用するすべてのクラスに対してインジェクトメソッドを作成する必要があることがわかりました。すべてのサブクラスでinjectを呼び出す必要はありませんか?たとえば、Activityを使用します。すべてのActivityが拡張されるBaseActivityがあります。コンポーネントにinjectメソッドを作成する方法はBaseActivityとBaseActivityのonCreateでinjectを呼び出すだけで、サブアクティビティの@injectフィールドは自動的に挿入されますか?

43
Chris.Zou

今はできません。グレゴリーキックによる説明( here から):

メンバーインジェクションメソッドの仕組みは次のとおりです。

  1. クラス階層のどこにでも@Injectがある型のメンバー注入メソッドを作成できます。そうでない場合は、エラーが発生します。
  2. 型階層全体のすべての@Injectedメンバー(引数の型とすべてのスーパータイプ)が注入されます。
  3. 引数型のサブタイプのメンバーは@Injectedされません。

この問題は here および here で議論され、更新のためにこれらをフォローアップします。しかし、すぐに変更される可能性は低いため、Dagger 2は リリース間近 です。

29

私は同じ状況に遭遇しました。すべてのアクティビティの共通コンポーネントからの注入を少し楽にする1つの方法は次のとおりです。

1)Applicationクラスを拡張して、共通コンポーネントを作成し、それへの参照を保持できるようにします。

_public class ApplicationDagger extends Application {

    private ApplicationComponent component;

    @Override
    public void onCreate(){
        super.onCreate();
        component = DaggerApplicationComponent.builder().applicationModule(new ApplicationModule(this)).build();
    }

    public ApplicationComponent getComponent(){
            return component;
    }
}
_

2)Applicationから共通コンポーネントを取得し、抽象メソッドinjectActivityを呼び出して、引数としてコンポーネントを指定する抽象DaggerActivityを作成します。このような:

_public abstract class DaggerActivity extends Activity {

    @Override
    public void onCreate(Bundle saved){
        super.onCreate(saved);
        ApplicationComponent component = ((ApplicationDagger) getApplication()).getComponent();
        injectActivity(component);
    }

    public abstract void injectActivity(ApplicationComponent component);
}
_

3)最後に、各Activity拡張DaggerActivityを実際に注入する必要があります。しかし、abstractメソッドを実装する必要があるため、これは今では少ない労力で実行できます。そうしないと、コンパイルエラーが発生します。さあ:

_public class FirstActivity extends DaggerActivity {

    @Inject
    ClassToInject object;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //initialize your Activity
    }

    @Override
    public void injectActivity(ApplicationComponent component) {
        component.inject(this);
    }
}
_

もちろん、コンポーネントで各アクティビティを明示的に宣言する必要があります。

UPDATE:@ActivityScopeオブジェクトをフラグメントに挿入する

ある時点で、オブジェクトをActivityライフサイクルにバインドするために カスタムスコープ を使用する必要がありました。一部の人々を助けるかもしれないので、私はこの投稿を拡張することにしました。

@ ModuleクラスActivityModule@ SubcomponentインターフェースActivityComponentがあるとします。

DaggerActivityを変更する必要があります。 Activities拡張DaggerActivityは、新しいメソッド(署名の変更)を実装する必要があります。

_public abstract class ActivityDagger extends AppCompatActivity {

    ActivityComponent component;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        component = ((ApplicationDagger) getApplication()).getComponent().plus(new ActivityModule(this));
        injectActivity(component);
        super.onCreate(savedInstanceState);
    }

    ActivityComponent getComponent() {
        return component;
    }

    public abstract void injectActivity(ActivityComponent component);
}
_

次に、FragmentDaggerを拡張するクラスFragmentを次のように作成できます。

_public abstract class FragmentDagger extends Fragment {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityDagger activityDagger = (ActivityDagger) getActivity();
        ActivityComponent component = activityDagger.getComponent();
        injectFragment(component);
    }

    public abstract void injectFragment(ActivityComponent component);

}
_

Activitiesに関しては、Fragments拡張FragmentDaggerには実装するメソッドが1つしかありません。

_public abstract void injectFragment(ActivityComponent component);
_

Fragmentsはどこでも再利用できるはずです。 ActivityDaggersuper.onCreated()メソッドは、コンポーネントのインスタンス化後に呼び出す必要があることに注意してください。それ以外の場合、Activityのメソッドsuper.onCreate()が呼び出されるため、Fragment状態が再作成されるときに NullPointerException が返されます。

41
Gordak

リフレクションを使用して少しハックすることができます:

_public class UiInjector {

    private static final String METHOD_NAME = "inject";

    private final UIComponent component;

    public UiInjector(final UIComponent component) {
        this.component = component;
    }

    public void inject(final Object subject) {
        try {
            component.getClass()
                    .getMethod(METHOD_NAME, subject.getClass())
                    .invoke(component, subject);
        } catch (final NoSuchMethodException exception) {
            throwNoInjectMethodForType(component, subject.getClass());
        } catch (final Exception exception) {
            throwUnknownInjectionError(exception);
        }
    }

    private void throwNoInjectMethodForType(final Object component, final Class subjectType) {
        throw new RuntimeException(component.getClass().getSimpleName() +
                " doesn't have inject method with parameter type : " + subjectType);
    }

    private void throwUnknownInjectionError(final Exception cause) {
        throw new RuntimeException("Unknown injection error", cause);
    }
}_

この場合、コンポーネントにinjectメソッドを記述する必要がありますが、各アクティビティ、フラグメント、ビューなどに「inject」メソッドは必要ありません。

なぜ機能するのですか?インジェクションサブジェクトでgetClass()を使用すると、ベースではなく子孫クラスが取得されます。

あぶない! Proguardを使用する場合は、次の_-keep class <ComponentClass> { *; }_をルールに追加して、コンポーネントにインジェクトメソッドをそのまま保持する必要があります。

2