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 アプリケーションを書く際の主な困難は、Activity
や Fragment
など多くの 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 }}
これにはいくつかの問題があります:
-
コードのコピーペーストは、後でリファクタリングするのが難しくなります。 より多くの開発者がそのブロックをコピーペーストするにつれて、それが実際に何を行うかを知る人は少なくなるでしょう。
-
より根本的に、それは注入を要求する型 (
FrombulationActivity
) がその注入器について知ることを要求しています。
dagger.android
dagger.android
のクラスは、上記の問題を単純化する一つのアプローチを提供しています。
別のアプローチとして、通常の Dagger API を使用し、ここにあるようなガイドに従うことです。これは理解するのがより簡単かもしれませんが、追加のボイラープレートを手動で書かなければならないという欠点があります。
Jetpack と Dagger チームは、Android 上の Dagger のための新しいイニシアチブに協力しており、現在の状況からの大きなシフトになることを期待しています。
Injecting Activity objects
-
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. -
まずは
@Subcomponent
でAndroidInjector<YourActivity>
を実装し、@Subcomponent.Factory
でAndroidInjector.Factory<YourActivity>
を拡張することから始めてみてください。@Subcomponent(modules = ...)public interface YourActivitySubcomponent extends AndroidInjector<YourActivity> { @Subcomponent.Factory public interface Factory extends AndroidInjector.Factory<YourActivity> {}}
-
サブコンポーネントを定義したら、サブコンポーネントのファクトリを結合するモジュールを定義し、それを
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();
-
次に、
Application
にHasAndroidInjector
と@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; }}
-
最後に、
Activity.onCreate()
メソッドで、super.onCreate();
を呼ぶ前にAndroidInjection.inject(this)
を呼びます:public class YourActivity extends Activity { public void onCreate(Bundle savedInstanceState) { AndroidInjection.inject(this); super.onCreate(savedInstanceState); }}
-
おめでたいっ!
は、
public class YourActivity extends Activity { public void onCreate(Bundle savedInstanceState) { AndroidInjection.inject(this); super.onCreate(savedInstanceState); }}
- を呼びます。
How did that work?
AndroidInjection.inject()
はApplication
からDispatchingAndroidInjector<Object>
を得て、あなたのアクティビティをinject(Activity)
に渡します。DispatchingAndroidInjector
はAndroidInjector.Factory
を検索して、アクティビティーのクラス (YourActivitySubcomponent.Factory
) を探し、AndroidInjector
(YourActivitySubcomponent
) を作成し、アクティビティーをinject(YourActivity)
に渡します。Injecting Fragment objects
Fragment
を注入することはActivity
を注入することと同様に簡単です。Activity
types のようにonCreate()
に注入するのではなく、Fragment
をonAttach()
に注入します。Activity
s に定義したモジュールとは異なり、Fragment
s のモジュールをどこにインストールするかを選択することが可能です。Fragment
コンポーネントを他のFragment
コンポーネント、Activity
コンポーネント、またはApplication
コンポーネントのサブコンポーネントとすることができます – これらはすべて、Fragment
が他にどのバインディングを必要とするかによります。 コンポーネントの場所を決めたら、対応するtypeimplementをHasAndroidInjector
にします(まだしていない場合)。 たとえば、Fragment
がYourActivitySubcomponent
からのバインディングを必要とする場合、コードは次のようになります: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は、複雑なクラス階層がない場合、DaggerActivity
やDaggerFragment
など、これを行ういくつかのベースタイプを提供します。 これを拡張し、applicationInjector()
メソッドをオーバーライドして、Application
をインジェクトするコンポーネントを返すだけでよいのです。-
DaggerService
およびDaggerIntentService
DaggerBroadcastReceiver
DaggerContentProvider
注意:
DaggerBroadcastReceiver
はBroadcastReceiver
がAndroidManifest.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 avoidNullPointerException
s. メンバ注入が必要な場合 (上記で説明したように)、できるだけ早く注入することが望ましいです。 このため、DaggerActivity
はonCreate()
においてsuper.onCreate()
を呼ぶ前に直ちにAndroidInjection.inject()
を呼び、DaggerFragment
はonAttach()
において同じことを行い、Fragment
が再び接続された場合の不整合も防止する。Activity
のsuper.onCreate()
の前にAndroidInjection.inject()
を呼び出すことは重要です。super
への呼び出しにより、構成変更中に以前のアクティビティ インスタンスからFragment
がアタッチされ、次にFragment
が注入されるからです。Fragment
のインジェクションを成功させるには、Activity
がすでにインジェクションされている必要があります。 ErrorProne のユーザーにとって、super.onCreate()
の後にAndroidInjection.inject()
を呼び出すことはコンパイラー エラーです。FAQ
Scoping AndroidInjector.Factory
AndroidInjector.Factory
はステートレスインターフェースとして意図されていて、実装者は注入されるオブジェクトに関する状態の管理を気にしなくてよくなっています。DispatchingAndroidInjector
がAndroidInjector.Factory
を要求するときは、Provider
を通して行いますので、明示的にファクトリーのインスタンスを保持することはありません。 実装によっては注入されるActivity
/Fragment
などのインスタンスを保持することがあるため、それらを提供するメソッドにスコープを適用することはコンパイル時のエラーとなります。 もしAndroidInjector.Factory
が注入されたオブジェクトのインスタンスを保持していないと確信があるなら、@SuppressWarnings("dagger.android.ScopedInjectorFactory")
をモジュールメソッドに適用することでこのエラーを抑制することができます。