AndroidアプリにMVVMパターンを実装しようとしています。 ViewModelsにはAndroid固有のコード(テストを簡単にするため)を含めないことを読みましたが、さまざまなこと(xmlからリソースを取得する、設定を初期化するなど)にコンテキストを使用する必要があります。これを行う最良の方法は何ですか? AndroidViewModel
にはアプリケーションコンテキストへの参照がありますが、Android固有のコードが含まれているため、ViewModelにあるかどうかはわかりません。また、それらはアクティビティライフサイクルイベントに結び付けられますが、コンポーネントの範囲を管理するために短剣を使用しているため、それがどのように影響するかはわかりません。私はMVVMパターンとDaggerを初めて使用するので、どんな助けでも大歓迎です!
ContextをViewModelに直接持つ代わりにやったこと、必要なリソースを提供するResourceProviderなどのプロバイダークラスを作成し、それらのプロバイダークラスをViewModelに注入しました
Application
によって提供されるAndroidViewModel
コンテキストを使用できます。AndroidViewModel
は、ViewModel
参照を含むApplication
のみを拡張する必要があります。
ViewModelsはテストを簡単にする抽象化であるため、テストを簡単にするためにAndroid固有のコードを含めるべきではありません。
ViewModelsにContextのインスタンス、またはContextを保持するViewやその他のオブジェクトのようなものを含めるべきではない理由は、ActivityおよびFragmentsとは別のライフサイクルを持っているためです。
これが意味することは、アプリで回転の変更を行うとしましょう。これにより、アクティビティとフラグメントが自身を破壊するため、自身が再作成されます。 ViewModelはこの状態の間持続することを意図しているため、破壊されたアクティビティのビューまたはコンテキストを保持している場合、クラッシュやその他の例外が発生する可能性があります。
やりたいことをどのように行うべきかについては、MVVMとViewModelはJetPackのDatabindingコンポーネントと非常にうまく機能します。通常、String、intなどを格納するほとんどの場合、Databindingを使用してビューに直接表示させることができるため、ViewModel内に値を格納する必要はありません。
しかし、データバインディングが必要ない場合は、コンストラクターまたはメソッド内でコンテキストを渡してリソースにアクセスできます。 ViewModel内にそのコンテキストのインスタンスを保持しないでください。
Android Architecture Components View Modelの場合、
アクティビティコンテキストをメモリリークとしてアクティビティのViewModelに渡すことは、良い習慣ではありません。
したがって、ViewModelでコンテキストを取得するには、ViewModelクラスでAndroid View Modelクラスを拡張する必要があります。これにより、以下のコード例に示すようにコンテキストを取得できます。
class ActivityViewModel(application: Application) : AndroidViewModel(application) {
private val context = getApplication<Application>().applicationContext
//... ViewModel methods
}
viewModel内からgetApplication().getApplicationContext()
からアプリケーションコンテキストにアクセスできます。これは、リソース、設定などにアクセスするために必要なものです。
アプリケーションコンテキストへの参照がありますが、Android固有のコードが含まれています
幸いなことに、Mockito.mock(Context.class)
を使用して、テストで必要なものをコンテキストに返すことができます。
したがって、通常どおりViewModel
を使用し、通常どおりViewModelProviders.Factoryを介してApplicationContextを指定します。
MVVMは優れたアーキテクチャであり、間違いなくAndroid開発の未来ですが、まだ環境に優しいものがいくつかあります。 MVVMアーキテクチャのレイヤー通信を例にとると、さまざまな開発者(著名な開発者)がLiveDataを使用してさまざまな方法でさまざまなレイヤーを通信するのを見てきました。それらの一部はLiveDataを使用してViewModelとUIを通信しますが、コールバックインターフェイスを使用してリポジトリと通信するか、Interactors/UseCasesを持ち、LiveDataを使用して通信します。ここでのポイントは、すべてが100%定義ではないということですまだ。
とはいえ、特定の問題に対する私のアプローチは、ViewModelsで使用するアプリケーションのコンテキストをDI経由で使用して、strings.xmlからStringなどを取得することです。
画像の読み込みを扱っている場合は、DatabindingアダプターメソッドからViewオブジェクトを通過させ、Viewのコンテキストを使用して画像を読み込みます。どうして?アプリケーションのコンテキストを使用して画像をロードすると、一部のテクノロジー(Glideなど)で問題が発生する可能性があるためです。
TL; DR:ViewModelのDaggerを介してアプリケーションのコンテキストを注入し、それを使用してリソースをロードします。画像をロードする必要がある場合は、Databindingメソッドの引数を使用してViewインスタンスを渡し、そのViewコンテキストを使用します。
それが役に立てば幸い!
ViewModelを使用する動機はAndroidコードとJavaコードを分離してテストできるようにするため、ViewModelでAndroid関連オブジェクトを使用しないでください。ビジネスロジックを個別に作成し、Androidコンポーネントとビジネスロジックおよびデータの個別のレイヤーを作成します。ViewModelにコンテキストが含まれていると、クラッシュにつながる可能性があります。
短い答え-これをしないでください
なぜ?
ビューモデルの目的全体を無効にする
ビューモデルで実行できるほとんどすべてのことは、LiveDataインスタンスおよびその他のさまざまな推奨アプローチを使用して、アクティビティ/フラグメントで実行できます。
他の人が言及したように、アプリからAndroidViewModel
を取得するために派生できるContext
がありますが、コメントで収集したものから、あなたはViewModel
内から@drawable
sを操作しようとしています。 MVVM全体。
全体として、Context
にViewModel
を含める必要があるということは、ほとんどの場合、View
sとViewModels
の間でロジックを分割する方法を再考することを検討することをお勧めします。
例えば。 ViewModel
でドロアブルを解決してアクティビティ/フラグメントにフィードする代わりに、ViewModel
が所有するデータに基づいて、フラグメント/アクティビティでドロアブルをジャグリングすることを検討してください。たとえば、何らかの種類のオン/オフインジケーターがある場合、(おそらくブール値)状態を保持するのはViewModel
ですが、それに応じて適切なドロアブルを選択するのはView
の仕事です。
Context
のコンストラクターへのビュー(バックエンド要求など)に直接関連しない一部のコンポーネント/サービスにViewModel
が必要な場合(手動/インジェクションによる)-その方法では、Context
への明示的な依存関係がなく、その結果、テストで簡単にモックが作成されます(模擬サービス/コンポーネントをコンストラクターに渡すか、選択したハーネスを提供するだけで、実際のContext
は不要です)
この方法で作成しました:
@Module
public class ContextModule {
@Singleton
@Provides
@Named("AppContext")
public Context provideContext(Application application) {
return application.getApplicationContext();
}
}
次に、AppComponentにContextModule.classを追加しました。
@Component(
modules = {
...
ContextModule.class
}
)
public interface AppComponent extends AndroidInjector<BaseApplication> {
.....
}
そして、ViewModelにコンテキストを注入しました。
@Inject
@Named("AppContext")
Context context;