Dagger と Android

Dagger 2 の他の依存性注入フレームワークに対する主な利点の 1 つは、厳密な生成実装(リフレクションなし)により、Android アプリケーションで使用できることです。

哲学

Android 用に書かれたコードは Java ソースですが、スタイルの面でかなり異なっていることがよくあります。 典型的には、そのような違いは、モバイル プラットフォームのユニークなパフォーマンスの考慮事項に対応するために存在します。

But the many of the patterns commonly applied to code intended for Android arecontrary to those applied to other Java code. Effective Java のアドバイスの多くでさえ、Android には不適切と考えられています。

熟語と移植可能なコードの両方の目標を達成するために、Dagger は ProGuard に依存して、コンパイルしたバイトコードを後処理します。 これにより、Daggerto は、サーバーと Android の両方で自然に見えるソースを生成し、異なるツールチェーンを使用して、両方の環境で効率的に実行されるバイトコードを生成します。 さらに、Dagger は、生成する Java ソースが一貫して ProGuard 最適化と互換性があることを保証するという明確な目標があります。

tl;dr

Dagger は、Android 上のユーザーが R8 または ProGuard を使用すると仮定します。

Why Dagger on Android is hard

Dagger を使用して Android アプリケーションを書く際の主な困難は、ActivityFragment など多くの Android フレームワーククラスは OS 自身によりインスタンスが生成されるが、インジェクトしたオブジェクトをすべて生成できれば Dagger は最高に機能するということです。 その代わり、ライフサイクルメソッドでメンバーインジェクションを実行する必要があります。

public class FrombulationActivity extends Activity { @Inject Frombulator frombulator; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // DO THIS FIRST. Otherwise frombulator might be null! ((SomeApplicationBaseType) getContext().getApplicationContext()) .getApplicationComponent() .newActivityComponentBuilder() .activity(this) .build() .inject(this); // ... now you can write the exciting code }}

これにはいくつかの問題があります:

  1. コードのコピーペーストは、後でリファクタリングするのが難しくなります。 より多くの開発者がそのブロックをコピーペーストするにつれて、それが実際に何を行うかを知る人は少なくなるでしょう。

  2. より根本的に、それは注入を要求する型 (FrombulationActivity) がその注入器について知ることを要求しています。

dagger.android

dagger.android のクラスは、上記の問題を単純化する一つのアプローチを提供しています。

別のアプローチとして、通常の Dagger API を使用し、ここにあるようなガイドに従うことです。これは理解するのがより簡単かもしれませんが、追加のボイラープレートを手動で書かなければならないという欠点があります。

Jetpack と Dagger チームは、Android 上の Dagger のための新しいイニシアチブに協力しており、現在の状況からの大きなシフトになることを期待しています。

Injecting Activity objects

  1. Install AndroidInjectionModule in your application component to ensure that all bindings necessary for these base types are available.If Jetpack and Dagger teams are working together for Dagger on Android on the new initiative for Jetpack and Dagger has been a current statusquo.

  2. まずは@SubcomponentAndroidInjector<YourActivity>を実装し、@Subcomponent.FactoryAndroidInjector.Factory<YourActivity>を拡張することから始めてみてください。

    @Subcomponent(modules = ...)public interface YourActivitySubcomponent extends AndroidInjector<YourActivity> { @Subcomponent.Factory public interface Factory extends AndroidInjector.Factory<YourActivity> {}}
  3. サブコンポーネントを定義したら、サブコンポーネントのファクトリを結合するモジュールを定義し、それを Application を注入するコンポーネントに追加して、コンポーネント階層に追加してください。

    @Module(subcomponents = YourActivitySubcomponent.class)abstract class YourActivityModule { @Binds @IntoMap @ClassKey(YourActivity.class) abstract AndroidInjector.Factory<?> bindYourAndroidInjectorFactory(YourActivitySubcomponent.Factory factory);}@Component(modules = {..., YourActivityModule.class})interface YourApplicationComponent { void inject(YourApplication application);}

    プロヒント: サブコンポーネントとそのファクトリーが手順 2 で述べた以外のメソッドまたはスーパータイプを持たない場合、@ContributesAndroidInjector を使用してそれらを生成することができます。 ステップ2と3の代わりに、アクティビティを返すabstractモジュールメソッドを追加し、@ContributesAndroidInjectorでアノテートし、サブコンポーネントにインストールしたいモジュールを指定します。 サブコンポーネントにスコープが必要な場合は、メソッドにスコープ アノテーションも適用します。

    @ActivityScope@ContributesAndroidInjector(modules = { /* modules to install into the subcomponent */ })abstract YourActivity contributeYourAndroidInjector();
  4. 次に、ApplicationHasAndroidInjector@Injectを実装し、androidInjector()メソッドからDispatchingAndroidInjector<Object>を返すようにする。

    public class YourApplication extends Application implements HasAndroidInjector { @Inject DispatchingAndroidInjector<Object> dispatchingAndroidInjector; @Override public void onCreate() { super.onCreate(); DaggerYourApplicationComponent.create() .inject(this); } @Override public AndroidInjector<Object> androidInjector() { return dispatchingAndroidInjector; }}
  5. 最後に、Activity.onCreate()メソッドで、super.onCreate();を呼ぶ前にAndroidInjection.inject(this)を呼びます:

    public class YourActivity extends Activity { public void onCreate(Bundle savedInstanceState) { AndroidInjection.inject(this); super.onCreate(savedInstanceState); }}
  6. おめでたいっ!

    は、

    public class YourActivity extends Activity { public void onCreate(Bundle savedInstanceState) { AndroidInjection.inject(this); super.onCreate(savedInstanceState); }}

  7. を呼びます。
  8. How did that work?

    AndroidInjection.inject()Application から DispatchingAndroidInjector<Object> を得て、あなたのアクティビティを inject(Activity) に渡します。 DispatchingAndroidInjectorAndroidInjector.Factory を検索して、アクティビティーのクラス (YourActivitySubcomponent.Factory) を探し、AndroidInjector (YourActivitySubcomponent) を作成し、アクティビティーを inject(YourActivity) に渡します。

    Injecting Fragment objects

    Fragment を注入することは Activity を注入することと同様に簡単です。

    Activitytypes のように onCreate() に注入するのではなく、FragmentonAttach() に注入します。

    Activitys に定義したモジュールとは異なり、Fragments のモジュールをどこにインストールするかを選択することが可能です。 Fragment コンポーネントを他の Fragment コンポーネント、Activity コンポーネント、または Application コンポーネントのサブコンポーネントとすることができます – これらはすべて、Fragment が他にどのバインディングを必要とするかによります。 コンポーネントの場所を決めたら、対応するtypeimplementをHasAndroidInjectorにします(まだしていない場合)。 たとえば、FragmentYourActivitySubcomponent からのバインディングを必要とする場合、コードは次のようになります:

    public class YourActivity extends Activity implements HasAndroidInjector { @Inject DispatchingAndroidInjector<Object> androidInjector; @Override public void onCreate(Bundle savedInstanceState) { AndroidInjection.inject(this); super.onCreate(savedInstanceState); // ... } @Override public AndroidInjector<Object> androidInjector() { return androidInjector; }}public class YourFragment extends Fragment { @Inject SomeDependency someDep; @Override public void onAttach(Activity activity) { AndroidInjection.inject(this); super.onAttach(activity); // ... }}@Subcomponent(modules = ...)public interface YourFragmentSubcomponent extends AndroidInjector<YourFragment> { @Subcomponent.Factory public interface Factory extends AndroidInjector.Factory<YourFragment> {}}@Module(subcomponents = YourFragmentSubcomponent.class)abstract class YourFragmentModule { @Binds @IntoMap @ClassKey(YourFragment.class) abstract AndroidInjector.Factory<?> bindYourFragmentInjectorFactory(YourFragmentSubcomponent.Factory factory);}@Subcomponent(modules = { YourFragmentModule.class, ... }public interface YourActivityOrYourApplicationComponent { ... }

    Base Framework Types

    DispatchingAndroidInjector は実行時にクラスによって適切な AndroidInjector.Factory を検索するので、ベース クラスは AndroidInjection.inject() を呼ぶだけでなく HasAndroidInjector を実装することができます。 各サブクラスが行うべきことは、対応する@Subcomponentをバインドすることだけです。 Daggerは、複雑なクラス階層がない場合、DaggerActivityDaggerFragmentなど、これを行ういくつかのベースタイプを提供します。 これを拡張し、applicationInjector()メソッドをオーバーライドして、Applicationをインジェクトするコンポーネントを返すだけでよいのです。

    • DaggerService および DaggerIntentService
    • DaggerBroadcastReceiver
    • DaggerContentProvider

    注意: DaggerBroadcastReceiverBroadcastReceiverAndroidManifest.xml に登録されているときだけ使用する必要がある。

    サポートライブラリ

    Androidサポートライブラリのユーザーのために、dagger.android.supportパッケージに並列型があります。

    TODO(ronshapiro): androidxパッケージによってこれを分割し始めるべき

    どうしたら手に入りますか?

    以下を build.gradle に追加します:

    dependencies { implementation 'com.google.dagger:dagger-android:2.x' implementation 'com.google.dagger:dagger-android-support:2.x' // if you use the support libraries annotationProcessor 'com.google.dagger:dagger-android-processor:2.x' annotationProcessor 'com.google.dagger:dagger-compiler:2.x'}

    いつ注入するか

    Constructor injection is preferred whenever possible because javac will ensure that no field is referenced before it been set, which helps avoidNullPointerExceptions. メンバ注入が必要な場合 (上記で説明したように)、できるだけ早く注入することが望ましいです。 このため、DaggerActivityonCreate() において super.onCreate() を呼ぶ前に直ちに AndroidInjection.inject() を呼び、DaggerFragmentonAttach() において同じことを行い、Fragment が再び接続された場合の不整合も防止する。

    Activitysuper.onCreate() の前に AndroidInjection.inject() を呼び出すことは重要です。super への呼び出しにより、構成変更中に以前のアクティビティ インスタンスから Fragment がアタッチされ、次に Fragment が注入されるからです。 Fragment のインジェクションを成功させるには、Activity がすでにインジェクションされている必要があります。 ErrorProne のユーザーにとって、super.onCreate() の後に AndroidInjection.inject() を呼び出すことはコンパイラー エラーです。

    FAQ

    Scoping AndroidInjector.Factory

    AndroidInjector.Factory はステートレスインターフェースとして意図されていて、実装者は注入されるオブジェクトに関する状態の管理を気にしなくてよくなっています。 DispatchingAndroidInjectorAndroidInjector.Factory を要求するときは、Provider を通して行いますので、明示的にファクトリーのインスタンスを保持することはありません。 実装によっては注入される Activity/Fragment などのインスタンスを保持することがあるため、それらを提供するメソッドにスコープを適用することはコンパイル時のエラーとなります。 もし AndroidInjector.Factory が注入されたオブジェクトのインスタンスを保持していないと確信があるなら、 @SuppressWarnings("dagger.android.ScopedInjectorFactory") をモジュールメソッドに適用することでこのエラーを抑制することができます。