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
でコンストラクター引数として依存関係を提供できるようにするには、独自のファクトリーを実装する必要があります。 - デフォルトのファクトリを使用できますが、Applicationから取得できるコンポーネントを使用して、
ViewModel
依存関係を提供することもできます。 -
ViewModel
正しく取得するには、アクティビティまたはフラグメントにアクセスする必要があり、取得はViewModelProviders
クラスを介して行う必要があります。
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); } }
この方法の利点:
- ダガー2の最小限の知識。
- Dagger 2の基本的な「機能」のみが使用されます(以前のバージョンのライブラリで使用できます)。
-
ViewModel
を提供するための標準ファクトリを使用します。
短所:
- 各
ViewModel
メソッドを挿入します(または、コンポーネントの各依存関係にプロバイダーメソッドが存在します)。 -
ViewModel
内に注入します。
自社工場のオプション
コンストラクターを通じて依存関係を提供するビューモデルをいくつか作成しましょう。
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); } }
この方法の利点:
-
ViewModel
のコンストラクターで@Inject
を使用して依存関係を取得し、ViewModel
を依存関係グラフに追加できます。 ビューモデルごとに注入メソッドを記述する必要はありません。 - すべてのモデル用の1つの単純な工場。
- ファクトリーに新しいビューモデルを簡単に追加できます。
短所:
- 誤って、ファクトリを介してではなく
ViewModelProviders.of()
を介してではなく、@Inject MyViewModel
を使用してViewModelProviders.of()
モデルを要求できます。 ビューモデルを取得するにはあまり便利ではありません。 -
multibinding Provider<>
サポートをmultibinding Provider<>
短剣バージョン。
@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のアーキテクチャコンポーネントが使用されていました。 お気づきかもしれませんが、この記事を書いている時点では、アーキテクチャコンポーネントにはアルファ版があります。 おそらく将来的には、ビューモデルを取得する他の方法があるでしょう。