web-dev-qa-db-ja.com

Android MVVM ViewModelでコンテキストを取得する方法

AndroidアプリにMVVMパターンを実装しようとしています。 ViewModelsにはAndroid固有のコード(テストを簡単にするため)を含めないことを読みましたが、さまざまなこと(xmlからリソースを取得する、設定を初期化するなど)にコンテキストを使用する必要があります。これを行う最良の方法は何ですか? AndroidViewModelにはアプリケーションコンテキストへの参照がありますが、Android固有のコードが含まれているため、ViewModelにあるかどうかはわかりません。また、それらはアクティビティライフサイクルイベントに結び付けられますが、コンポーネントの範囲を管理するために短剣を使用しているため、それがどのように影響するかはわかりません。私はMVVMパターンとDaggerを初めて使用するので、どんな助けでも大歓迎です!

32

ContextをViewModelに直接持つ代わりにやったこと、必要なリソースを提供するResourceProviderなどのプロバイダークラスを作成し、それらのプロバイダークラスをViewModelに注入しました

12

Applicationによって提供されるAndroidViewModelコンテキストを使用できます。AndroidViewModelは、ViewModel参照を含むApplicationのみを拡張する必要があります。

25
Jay

ViewModelsはテストを簡単にする抽象化であるため、テストを簡単にするためにAndroid固有のコードを含めるべきではありません。

ViewModelsにContextのインスタンス、またはContextを保持するViewやその他のオブジェクトのようなものを含めるべきではない理由は、ActivityおよびFragmentsとは別のライフサイクルを持っているためです。

これが意味することは、アプリで回転の変更を行うとしましょう。これにより、アクティビティとフラグメントが自身を破壊するため、自身が再作成されます。 ViewModelはこの状態の間持続することを意図しているため、破壊されたアクティビティのビューまたはコンテキストを保持している場合、クラッシュやその他の例外が発生する可能性があります。

やりたいことをどのように行うべきかについては、MVVMとViewModelはJetPackのDatabindingコンポーネントと非常にうまく機能します。通常、String、intなどを格納するほとんどの場合、Databindingを使用してビューに直接表示させることができるため、ViewModel内に値を格納する必要はありません。

しかし、データバインディングが必要ない場合は、コンストラクターまたはメソッド内でコンテキストを渡してリソースにアクセスできます。 ViewModel内にそのコンテキストのインスタンスを保持しないでください。

18
Jackey

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 

}
7
devDeejay

viewModel内からgetApplication().getApplicationContext()からアプリケーションコンテキストにアクセスできます。これは、リソース、設定などにアクセスするために必要なものです。

7

アプリケーションコンテキストへの参照がありますが、Android固有のコードが含まれています

幸いなことに、Mockito.mock(Context.class)を使用して、テストで必要なものをコンテキストに返すことができます。

したがって、通常どおりViewModelを使用し、通常どおりViewModelProviders.Factoryを介してApplicationContextを指定します。

4
EpicPandaForce

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コンテキストを使用します。

それが役に立てば幸い!

3
4gus71n

ViewModelを使用する動機はAndroidコードとJavaコードを分離してテストできるようにするため、ViewModelでAndroid関連オブジェクトを使用しないでください。ビジネスロジックを個別に作成し、Androidコンポーネントとビジネスロジックおよびデータの個別のレイヤーを作成します。ViewModelにコンテキストが含まれていると、クラッシュにつながる可能性があります。

3
Rohit Sharma

短い答え-これをしないでください

なぜ?

ビューモデルの目的全体を無効にする

ビューモデルで実行できるほとんどすべてのことは、LiveDataインスタンスおよびその他のさまざまな推奨アプローチを使用して、アクティビティ/フラグメントで実行できます。

2
humble_wolf

他の人が言及したように、アプリからAndroidViewModelを取得するために派生できるContextがありますが、コメントで収集したものから、あなたはViewModel内から@drawablesを操作しようとしています。 MVVM全体。

全体として、ContextViewModelを含める必要があるということは、ほとんどの場合、ViewsとViewModelsの間でロジックを分割する方法を再考することを検討することをお勧めします。

例えば。 ViewModelでドロアブルを解決してアクティビティ/フラグメントにフィードする代わりに、ViewModelが所有するデータに基づいて、フラグメント/アクティビティでドロアブルをジャグリングすることを検討してください。たとえば、何らかの種類のオン/オフインジケーターがある場合、(おそらくブール値)状態を保持するのはViewModelですが、それに応じて適切なドロアブルを選択するのはViewの仕事です。

Contextのコンストラクターへのビュー(バックエンド要求など)に直接関連しない一部のコンポーネント/サービスにViewModelが必要な場合(手動/インジェクションによる)-その方法では、Contextへの明示的な依存関係がなく、その結果、テストで簡単にモックが作成されます(模擬サービス/コンポーネントをコンストラクターに渡すか、選択したハーネスを提供するだけで、実際のContextは不要です)

2
Ivan Bartsov

この方法で作成しました:

@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;
0
loopidio