Dagger2とアーキテクチャコンポーネント「ViewModel」

ViewModelは、Google I / O 2017で導入されたAndroidアーキテクチャコンポーネントと呼ばれる一連のライブラリのコンポーネントです。ViewModelは、ビューに関連するデータを保存および管理し、アクティビティの再作成(画面の反転など)を「存続させる」機能を備えています。







HabréにはViewModel専用の優れた記事がすでにありました。この記事では、このトピックをさらに詳しく知ることができます。







この記事では、Dagger 2を使用してViewModel



コンポーネントに依存関係を注入(提供)するためのオプションについて説明します。問題は、ViewModelを特別な方法で取得する必要があることです。依存関係としてViewModel



を提供します。 この記事は、マルチバインディングなどのDagger機能の実用化に興味がある人にも興味があるかもしれません。







ViewModel



を取得する特別な方法は次のとおりです。







最初に、アクティビティまたはフラグメントに関連付けられるViewModelProvider



を取得する必要があります。これにより、 ViewModel



有効期間も決定されます。







 ViewModelProvider provider = ViewModelProviders.of(<Activity|Fragment>[, ViewModelProvider.Factory]);
      
      





2番目のパラメーターは、ViewModelインスタンスの作成に使用されるファクトリーを指定するために使用されます。指定しない場合は必要ありません。ファクトリーはデフォルトで使用されます。 デフォルトでは、ファクトリーは、 ViewModel



子孫であるクラス(引数なしのコンストラクターを含む)およびAndroidViewModel



子孫であるAndroidViewModel



(引数が1つのApplication



タイプ)を持つクラスのインスタンスの作成をサポートします。







コンストラクターで独自の引数を使用してViewModel



インスタンスを作成する場合(既定ではファクトリーでサポートされていません)、独自のファクトリーを実装する必要があります。







ViewModelProvider



を取得した後、すでにViewModelProvider



を取得できます。







 ProductViewModel productVM = provider.get(ProductViewModel.class);
      
      





上記の説明から:









ViewModel



依存関係はさまざまな方法で提供でき、各メソッドには長所と短所があるため、いくつかのオプションが考慮されます。







工場出荷時のデフォルトオプション



アクティビティのインジェクションに使用されるモジュールとサブコンポーネントを定義することから始めましょう。







 @Module public class ActivityModule { @Provides public ProductViewModel productViewModel(AppCompatActivity activity) { return ViewModelProviders.of(activity).get(ProductViewModel.class); } }
      
      





 @Subcomponent(modules = {ActivityModule.class}) public interface ActivitySubComponent { @Subcomponent.Builder interface Builder { @BindsInstance Builder with(AppCompatActivity activity); ActivitySubComponent build(); } void inject(MainActivity mainActivity); }
      
      





このようなモジュールとサブコンポーネントの存在により、 ViewModelProviders.of(activity).get(ProductViewModel.class)



内でViewModelProviders.of(activity).get(ProductViewModel.class)



代わりに@Inject



を介して@Inject



モデルをリクエストできます。







デフォルトでファクトリを使用する場合、このファクトリはViewModel



インスタンスの作成を担当し、コンストラクタを介してViewModel



依存関係を要求できないため、コンポーネントを介して依存関係を注入します。 ルートコンポーネントを詰まらせないために、ビューモデル専用のサブコンポーネントを作成します。







 @Subcomponent public interface ViewModelSubComponent { @Subcomponent.Builder interface Builder { ViewModelSubComponent build(); } void inject(ProductViewModel productViewModel); }
      
      





ルートコンポーネントを定義します。







 @Component(modules = {AppModule.class}) @Singleton public interface AppComponent { @Component.Builder interface Builder { @BindsInstance Builder withApplication(Application application); AppComponent build(); } ViewModelSubComponent.Builder viewModelSubComponentBuilder(); ActivitySubComponent.Builder activitySubComponentBuilder(); }
      
      





AppModule



ビューモデルに必要な依存関係が含まれます(たとえば、 ProductDetailsFacade



)。







ビューモデルのルートコンポーネントとサブコンポーネントを含むアプリケーションを作成します。







 public class App extends Application { private AppComponent appComponent; private ViewModelSubComponent viewModelSubComponent; @Override public void onCreate() { super.onCreate(); appComponent = DaggerAppComponent .builder() .withApplication(this) .build(); viewModelSubComponent = appComponent .viewModelSubComponentBuilder() .build(); } public AppComponent getAppComponent() { return appComponent; } public ViewModelSubComponent getViewModelSubComponent() { return viewModelSubComponent; } }
      
      





これで、 ViewModel



依存関係を注入できます。







 public class ProductViewModel extends AndroidViewModel { @Inject ProductFacade productFacade; public ProductViewModel(Application application) { super(application); // ,   . //    ((App)application) .getViewModelSubComponent() .inject(this); } //methods }
      
      





インジェクションの代わりに、コンポーネントで提供メソッドを使用できます。







ViewModel



インジェクションアクティベーション:







 public class MainActivity extends AppCompatActivity { @Inject ProductViewModel productViewModel; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // ,   . //    ((App) getApplication()) .getAppComponent() .activitySubComponentBuilder() .with(this) .build() .inject(this); } }
      
      





この方法の利点:









短所:









自社工場のオプション



コンストラクターを通じて依存関係を提供するビューモデルをいくつか作成しましょう。







 public class UserViewModel extends ViewModel { private UserFacade userFacade; @Inject public UserViewModel(UserFacade userFacade) { this.userFacade = userFacade; } //methods } public class UserGroupViewModel extends ViewModel { private UserGroupFacade userGroupFacade; @Inject public UserGroupViewModel(UserGroupFacade userGroupFacade) { this.userGroupFacade = userGroupFacade; } //methods }
      
      





独自のキーアノテーションを作成します。これを使用して、ビューモデルをコレクションにバインドし、マルチバインドを使用します。 ここで multibindigについて読むことができます







 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @MapKey @interface ViewModelKey { Class<? extends ViewModel> value(); }
      
      





ビューモデルをコレクションにバインドするモジュールを定義します。







 @Module public abstract class ViewModelModule { @Binds @IntoMap @ViewModelKey(UserViewModel.class) abstract ViewModel userViewModel(UserViewModel userViewModel); @Binds @IntoMap @ViewModelKey(UserGroupViewModel.class) abstract ViewModel groupViewModel(UserGroupViewModel groupViewModel); }
      
      





マルチバインディングを使用してViewModel



を提供するためのファクトリーの作成に移りましょう。







 public class DemoViewModelFactory implements ViewModelProvider.Factory { private final Map<Class<? extends ViewModel>, Provider<ViewModel>> viewModels; @Inject public DemoViewModelFactory(Map<Class<? extends ViewModel>, Provider<ViewModel>> viewModels) { this.viewModels = viewModels; } @Override public <T extends ViewModel> T create(Class<T> modelClass) { Provider<ViewModel> viewModelProvider = viewModels.get(modelClass); if (viewModelProvider == null) { throw new IllegalArgumentException("model class " + modelClass + " not found"); } return (T) viewModelProvider.get(); } }
      
      





Provider<ViewModel>



は、ビューモデルの遅延初期化を使用する機会と、モデルビューの新しいインスタンスを毎回取得する機会を提供します。

マルチバインディングがなければ、if / elseから巨大なブロックを取得できます。







Application



クラス:







 @Component(modules = {AppModule.class, ViewModelModule.class}) @Singleton public interface AppComponent { @Component.Builder interface Builder { @BindsInstance Builder withApplication(Application application); AppComponent build(); } void inject(MainActivity mainActivity); }
      
      





アクティビティでViewModel



を提供する:







 public class MainActivity extends AppCompatActivity { @Inject DemoViewModelFactory viewModelFactory; UserViewModel userViewModel; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ((App) getApplication()) .getAppComponent() .inject(this); userViewModel = ViewModelProviders.of(this, viewModelFactory) .get(UserViewModel.class); } }
      
      





この方法の利点:









短所:









@Inject



を介してモデルをリクエストした場合、モデルのビューのインスタンスを取得するだけです(既に依存関係グラフにあるため)。それはアクティビティまたはフラグメントのライフサイクルに接続されず、「生き残る」ことができません。たとえば、画面を反転させるなど、これを機能させるには、工場で作成する必要があります。

モデルをビューグラフに2回追加することはできません。 次のことはできません。







 @Module public class ActivityModule { // , .. UserViewModel     //(@Inject  ) @Provides public UserViewModel productViewModel( DemoViewModelFactory viewModelFactory, AppCompatActivity activity) { return ViewModelProviders .of(activity, viewModelFactory) .get(UserViewModel .class); } }
      
      





この制限を回避するには、モデルのインターフェイスを入力し、インターフェイスでモデルのビューを要求できます。







 @Module public class ActivityModule { @Provides public UserViewModelInterface productViewModel( DemoViewModelFactory viewModelFactory, AppCompatActivity activity) { return ViewModelProviders .of(activity, viewModelFactory) .get(ProductViewModelImplementation.class); } } public class MainActivity extends AppCompatActivity { @Inject UserViewModelInterface userViewModel; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ((App) getApplication()) .getAppComponent() .activitySubComponentBuilder() .with(this) .build() .inject(this); } }
      
      





執筆時点では、ダガー2.11とバージョン1.0.0-alpha9のアーキテクチャコンポーネントが使用されていました。 お気づきかもしれませんが、この記事を書いている時点では、アーキテクチャコンポーネントにはアルファ版があります。 おそらく将来的には、ビューモデルを取得する他の方法があるでしょう。








All Articles