Android固有のパターンに関する私の問題は、AndroidInjection
クラスを使用する場合、メンバーがActivities
/Fragments
/custom views/adapters以外のオブジェクトを注入する方法がないことです。 、ただしアプリケーションコンポーネントを除く。これは、Subcomponent
/AndroidInjector
の注入に使用されるActivities
(Fragments
)の参照を取得できないためです。これにより、ダイアログが挿入されます(DialogFragments
を使用する場合)。
AndroidInjection
クラスはコアAndroid=タイプのみをサポートしているようです。
以下はあなたの質問に対する答えではありませんが、なぜこの質問をするべきではないのかという説明です。
通常、カスタムViews
への注入は避けてください。この理由は この記事 にリストされています。
この場合にメソッドインジェクション[カスタムビューへのインジェクション]を使用する利点は次のとおりです。
- 依存関係はトップレベルのコンポーネント(アクティビティまたはフラグメント)から伝達する必要があります
- メソッドインジェクションは単一責任原則違反への扉を開かない
- フレームワークに依存しない
- よりよい性能
トップレベルのコンポーネントからの伝播はフィールドに注釈を追加するよりも難しく、定型コードが多く含まれるため、最初の利点は驚くかもしれません。これは確かに悪いことですよね。この場合はありません。実際、このような依存関係の伝播には、2つの優れた側面があります。まず、依存関係はトップレベルのコンポーネントに表示されます。したがって、たとえば、コードの読者は、フラグメントのフィールドを使用すると、このフラグメントが画像を表示することをすぐに理解できます。このような可読性の最適化により、システムは長期的に維持しやすくなります。次に、Viewのサブクラスが追加の依存関係を必要とするユースケースは多くありません。これらの依存関係を提供するために実際に作業する必要があるという事実は、それらを提供することから始めるのが適切な設計上の決定であるかどうかについて考える時間を少し与えます。
2番目の利点は、協調的な構築に関連しています。あなた自身は非常に経験豊富なソフトウェアエンジニアかもしれませんが、おそらく経験の浅いチームメートもいるでしょう。または、いつかプロジェクトを辞めて、引き継ぐ人があなたほど上手ではない可能性もあります。フレームワークを使用して単一の依存関係を注入することにより、基本的には他の注入の扉を開きます。たとえば、SharedPreferencesの一部のデータがカスタムビューで必要になるとします。バグを修正します。経験の浅い開発者の1人は、SharedPreferencesをカスタムビューに直接挿入するのが良い方法であると判断するかもしれません。これを行うことは単一の責任の原則に違反しますが、その開発者はそのような概念にさえ気付かないかもしれません。したがって、長期的には、このような注入の「バックドア」は設計品質を低下させ、デバッグセッションを長くする可能性があります。
カスタムビューでメソッドインジェクションを使用する3つ目の利点は、ビューを依存関係インジェクションフレームワークに結合しないことです。数年後、あなた(または他の貧しい人)がフレームワークを置き換える必要があると想像してみてください。何十ものアクティビティとフラグメントが最初にあるという事実は、あなたの人生を悲惨なものにするでしょう。処理するカスタムビューがさらに数十または数百ある場合は、自殺の気分になる可能性があります。
最後の(ただし重要ではない)利点はパフォーマンスです。 1つの画面には、1つのアクティビティ、複数のフラグメント、および数十のカスタムビューを含めることができます。依存関係注入フレームワークを使用してこの数のクラスをブートストラップすると、アプリケーションのパフォーマンスが低下する可能性があります。これは、リフレクションベースのフレームワークに特に当てはまりますが、Daggerでもパフォーマンスコストがかかります。
さらに、AndroidInjection
クラスを含む新しい注入メソッドを回避することをお勧めします。 このビデオチュートリアル で説明されています。
まず、考え直してください Vasilyの答え 。
しかし、Dagger Androidの前にこれをどのように行ったかを少し考えてみましょう。 Application
クラスから取得したコンポーネントからサブコンポーネントを構築しました。後で、このサブコンポーネントを使用して、たとえばカスタムビューのフィールドを挿入できます。
だから、今はまったく同じことをしようとするでしょう。
私たちの目的は、MyAdapter
クラスをMyButton
に注入することです。
public class MyButton extends AppCompatButton {
@Inject MyAdapter adapter;
public MyButton(Context context) {
super(context);
...
}
}
そして、アダプタがアクティビティに依存するようにしますContext
、notアプリケーションContext
:
public class MyAdapter {
@Inject
public MyAdapter(@Named("activity") Context context) {
}
}
カスタムApplication
クラスから始めましょう。
MyApplication.Java
public class MyApplication extends DaggerApplication {
@Inject
DispatchingAndroidInjector<Activity> dispatchingActivityInjector;
public static MySubcomponent mySubcomponent;
@Override
protected AndroidInjector<? extends DaggerApplication> applicationInjector() {
return DaggerAppComponent.builder()
.create(this);
}
}
AppComponent.Java:
@Component(modules = {AndroidSupportInjectionModule.class, ActivityBindingModule.class, AppModule.class})
@Singleton
public interface AppComponent extends AndroidInjector<MyApplication> {
@Component.Builder
abstract class Builder extends AndroidInjector.Builder<MyApplication> {
}
}
AppModule.Java
@Module
abstract class AppModule {
@Binds
@Singleton
@Named("app")
abstract Context providesContext(Application application);
}
ActivityBindingModule.Java
@Module(subcomponents = MySubcomponent.class)
public abstract class ActivityBindingModule {
@Binds
@IntoMap
@ActivityKey(MainActivity.class)
abstract AndroidInjector.Factory<? extends Activity>
bindMainActivityInjectorFactory(MySubcomponent.Builder builder);
}
AndroidSupportInjectionModule.Java
はダガー自体が付属しています。サポートパッケージのクラスを使用しない場合(つまり、Android.support.v4.app.Fragment
の代わりに Android.app.Fragment
)、次にAndroidInjectionModule.Java
。
MySubcomponent.Java
@ActivityScope
@Subcomponent(modules = {SubcomponentModule.class/*, other modules here, if needed */})
public interface MySubcomponent extends AndroidInjector<MainActivity> {
void inject(MyButton button);
@Subcomponent.Builder
abstract class Builder extends AndroidInjector.Builder<MainActivity> {
public abstract MySubcomponent build();
}
}
SubcomponentModule.Java
@Module
abstract class SubcomponentModule {
@Binds
@ActivityScope
@Named("activity")
abstract Context toContext(MainActivity activity);
}
MainActivity.Java
public class MainActivity extends AppCompatActivity {
@Inject
MySubcomponent subcomponent;
@Override
protected void onCreate(Bundle savedInstanceState) {
// Will inject `subcomponent` field
AndroidInjection.inject(this);
// Saving this component in a static field
// Hereafter you are taking responsibility of mySubcomponent lifetime
MyApplication.mySubcomponent = subcomponent;
super.onCreate(savedInstanceState);
setContentView(new MyButton(this));
}
}
これらすべてが揃ったので、MyButton
は次のようになります。
public class MyButton extends AppCompatButton {
@Inject MyAdapter adapter;
public MyButton(Context context) {
super(context);
MyApplication.mySubcomponent.inject(this);
}
}
私はこれがハッキリに見え、確かに固執するアプローチではないことを認めます。より良いアプローチを見つけてうれしいです。
これは、
Subcomponent
/AndroidInjector
の注入に使用されるActivities
(Fragments
)の参照を取得できないためです。
コンポーネント自体をいつでも注入できます。コンポーネントのフィールドをActivity/Fragmentに追加し、Daggerにそれとともに残りのコンポーネントを挿入させるだけです。
// will be injected
@Inject MainActivitySubcomponent component;
AndroidInjector
のようなdagger-Androidクラスがビュー内のインジェクションをサポートする必要があるかどうかの問題は、次のGithubの問題で説明されています。
https://github.com/google/dagger/issues/72
図書館の作者の一人からの引用:
ここには、哲学的ポイントとロジスティックス/実装ポイントの両方があります。
まず、ビューを挿入することが正しいことであることは完全には明確ではありません。ビューオブジェクトは描画するためのものであり、それ以外のものではありません。 (従来のMVCパターンの)コントローラーは、適切なデータを調整してビューに渡すことができるコントローラーです。ビューを挿入すると、フラグメントとビューの間の線がぼやけます(おそらく、代わりに子フラグメントが実際に適切な構成体ですか?)
実装の観点からは、ビューの親フラグメント(存在する場合)を取得する正規の方法、または親コンポーネントを取得するアクティビティがないという問題もあります。その関係を構築するためのハックが提案されてきましたが、これまでのところ、これを正しく実行できることを示唆するようなハッキングは何も見ていません。 View.getContext()。getApplicationContext()を呼び出してそこから挿入することもできますが、中間レイヤーを何もオプションなしでスキップすると、他のデザインと矛盾し、機能してもユーザーを混乱させる可能性があります。
これは、ヴァシリーの答えで表明された意見を補強します。
さらに追加すると、多くの場合、カスタムビュー内にモデルレイヤーの依存関係を挿入したいようです。これは、ソフトウェア工学の原則 懸念の分離 に反するため、悪い考えです。
ビューとモデルを関連付けるための正しいソリューションは、RecyclerViewとListViewのアダプターのようなアダプターを作成することです。フラグメントレベルまたはプレゼンターレベルでモデルレイヤーの依存関係を挿入し、そこにアダプターを設定できます。