Android
 Computer >> コンピューター >  >> システム >> Android

プロのように Android で Model-View-ViewModel を使用する方法

この記事での私の目標は、GUI アーキテクチャのプレゼンテーション ロジックに関して、Model-View-ViewModel アーキテクチャ パターンが、状況によっては非常にぎこちない関心事の分離を提示する理由を説明することです。

MVVM の 2 つのバリアントを調べます (ない プロジェクトの要件に基づいて、あるバリアントを別のバリアントよりも優先する理由を説明します。

MVVM vs MVP/MVC?

日曜日のライブ Q&A セッションで聞かれる最も一般的な質問は、次のようなものである可能性が非常に高いです:

MVVM vs MVP/MVC?

この質問をされるたびに、すべての状況で優れた機能を発揮する単一の GUI アーキテクチャは存在しないという考えを強調します。

なぜ、あなたは尋ねるかもしれませんか?特定のアプリケーションに最適なアーキテクチャ (または少なくとも適切な選択) は、当面の要件に大きく依存します。

要件という言葉について簡単に考えてみましょう。 実際の意味:

  • UI はどのくらい複雑ですか? 通常、単純な UI を調整するために複雑なロジックは必要ありませんが、複雑な UI では、スムーズに動作するために広範なロジックときめ細かい制御が必要になる場合があります。
  • テストについてどの程度気にしますか? 一般的に言えば、フレームワークと OS に密接に結合されているクラス (特にユーザー インターフェース) ) テストには余分な作業が必要です。
  • 再利用性と抽象化をどの程度推進したいと考えていますか? アプリケーションのバックエンド、ドメイン、さらにはプレゼンテーション ロジックを異なるプラットフォーム間で共有したい場合はどうでしょうか?
  • 生まれつき現実的ですか 、完璧主義者怠け者 、または上記のすべてをさまざまなタイミングでさまざまな状況で?

上記の要件と懸念事項に関して MVVM がどのように機能するかについて詳しく説明する記事を書きたいと思います。残念ながら、MVVM を実行する方法は 1 つしかないと思い込んでいる人もいるでしょう。

代わりに、MVVM の一般的な考え方に対する 2 つの異なるアプローチについて説明します。これらのアプローチには、非常に明確な利点と欠点があります。しかし、まず、一般的な考え方から始めましょう。

ビュークラスを参照してはならない

古英語が読めない友達へ:ビュー クラスを参照することはできません ."

ViewModel という名前を使用する以外に (クラスが ロジック でいっぱいの場合、それ自体が混乱を招きます) )、MVVM アーキテクチャの 1 つの鉄則は、ViewModel からビューを参照してはならないということです。

ここで、混乱の最初の領域は、この「参照」という言葉から生じる可能性があります。これを、いくつかの異なるレベルの専門用語を使用して言い直します。

  • ViewModel は、View への参照 (メンバー変数、プロパティ、可変/不変フィールド) を持たない場合があります
  • ViewModel は、どのビューにも依存していない可能性があります
  • ViewModel は View と直接対話しない可能性があります

さて、Android プラットフォームでは、このルールの理由は単に、ソフトウェア アーキテクチャを知っていると思われる誰かがそれは悪いと言ったので、それを破ることが悪いということではありません。

アーキテクチャ コンポーネントの ViewModel クラスを使用する場合 (インスタンスが永続化されるように設計されています) Fragment/Activity ライフサイクルよりも長い 適切な場合 )、ビューを参照すると、深刻なメモリ リークが発生します .

MVVM が一般にそのような参照を許可しない理由については、目標は仮定的に View と ViewModel の両方を簡単にテストして記述できるようにします。

ViewModel の再利用が促進されると指摘する人もいるかもしれませんが、これはまさにこのパターンで問題が発生する場所です。 .

コードを見る前に、私は個人的に LiveData を使用していないことに注意してください。 私自身の生産コードで。私は最近、独自のパブリッシャー-サブスクライバー パターンを作成することを好みますが、以下で述べることは、ViewModel からビューへの PubSub/Observer パターン リンクを可能にするすべてのライブラリに適用されます。

この記事には、同じアイデアの多くをカバーするビデオ チュートリアルが付属しています:

ViewLogic + ViewModel または View + ViewModelController?

前のセクションで「崩壊」と言ったとき、パターンが文字通り崩壊すると言っているわけではありません。つまり、それは (少なくとも) 2 つの異なるアプローチに分かれており、それらは非常に明確な外観、利点、および結果を持っています。

これら 2 つのアプローチについて考えてみましょう。また、どちらかを優先したい場合も考えてみましょう。

プロのように Android で Model-View-ViewModel を使用する方法
Boromir は、MVVM はアプリケーションのプレゼンテーション ロジックを消滅させる魔法の杖ではないと説明しています。

最初のアプローチ:再利用可能な ViewModel を優先する

私が知る限り、MVVM を実装するほとんどの人は、ビューモデルの再利用を促進することを目標にしています。 異なるビューの数 (多対 1 の比率)。

簡単に言うと、この再利用性を実現するには 2 つの方法があります。

  • 特定のビューを参照しない。現時点では、これがニュースではないことを願っています。
  • 知ることで UI の詳細についてはできるだけ触れないでください。 一般的に

2 番目の点は、あいまいで直感に反するように聞こえるかもしれません (参照していないものについてどうやって知ることができるでしょうか?) ので、コードを見てみましょう:

class NoteViewModel(val repo: NoteRepo): ViewModel(){
    //Note: you may also publish data to the View via Databinding, RxJava Observables, and other approaches. Although I do not like to use LiveData in back end classes, it works great with Android front end with AAC
    val noteState: MutableLiveData<Note>()
    //...
    fun handleEvent(event: NoteEvent) {
        when (event) {
            is NoteEvent.OnStart -> getNote(event.noteId)
            //...
        }
    }
    private fun getNote(noteId: String){
        noteState.value = repo.getNote(noteId)
    }
}

これは非常に単純化された例ですが、要点は、この特定の ViewModel が公に公開する唯一のもの (handleEvent 関数以外) は単純な Note オブジェクトです:

data class Note(val creationDate:String,
                val contents:String,
                val imageUrl: String,
                val creator: User?)

この特定のアプローチにより、ViewModel は特定のビューだけでなく、詳細、ひいてはプレゼンテーション ロジックからも完全に分離されます。 特定のビューの。

私が言っていることがまだ漠然としているように見える場合は、別のアプローチを説明すれば明確になることを約束します.

以前の見出し「ViewLogic + ViewModel…」ですが、 つまり、非常に分離された再利用可能な ViewModel を使用することで、この Note オブジェクトを画面上でレンダリング/バインドする方法を理解する作業を View 自体に依存するようになりました。

View クラスに Logic を埋め込むのが嫌いな人もいます。

これは、物事が非常に混乱し、プロジェクトの要件に依存する場所です。 . View クラスを次のようなロジックで満たすと言っているわけではありません:

private fun observeViewModel() {
    viewModel.notes.observe(
        viewLifecycleOwner,
        Observer { notes: List<Note> ->
            if (notes.isEmpty()) showEmptyState()
            else showNoteList(notes)
        }
    )
   //..
}

常に 悪いことですが、プラットフォームに密結合されたクラス (フラグメントなど) はテストが難しく、ロジックを含むクラスはテストするのに最も重要なクラスです!

一言で言えば、優れたアーキテクチャの黄金原則であると私が考えるものを適用できていないということです:関心の分離 .

私の個人的な意見では、関心の分離を非常に高度に適用する価値があるということです。しかし、それが何を意味するのか、まったく見当もつかない人々によって、多くのキャッシュ カウ アプリケーションが作成されていることを間違えないでください。

いずれにせよ、次に説明するアプローチですが、独自の副作用があります。 、再びビューからプレゼンテーション ロジックを削除します。

とにかく、そのほとんどは。

2 番目のアプローチ:Humble View、Control-Freak ViewModel

ビューをきめ細かく制御できない場合があります (これは、ビューモデルの再利用性を優先した結果です)、実際には最悪です.

以前のアプローチを無差別に適用することへの熱意をさらに低下させるために、私はしばしば しない ViewModel を再利用する必要がある .

皮肉なことに、「抽象化が多すぎる」というのは、MVVM よりも MVP に対する一般的な批判です。

そうは言っても、ビューに対するこのきめの細かい制御を取り戻すために、ビューモデルに参照を単純に追加し直すことはできません。これは基本的に MVP + メモリ リークにすぎません (まだ AAC の ViewModel を使用していると仮定します)。

別の方法として、動作のほぼすべてを含むように ViewModel を構築します。 、状態プレゼンテーション ロジック 特定のビューの。もちろん、View は引き続き ViewModel にバインドする必要がありますが、View に関する十分な詳細が ViewModel に存在するため、View の機能は 1 つのライナーにまとめられます (わずかな例外はあります)。

Martin Fowler の命名規則では、これは Passive View/Screen として知られています。このアプローチのより一般的な名前は、Humble Object Pattern です。 .

これを実現するには、基本的に、ビューに存在するすべてのコントロールまたはウィジェットに対して、ViewModel に監視可能なフィールドを持たせる必要があります (ただし、データ バインディング、Rx、LiveData など)。

class UserViewModel(
    val repo: IUserRepository,
){

    //The actual data model is kept private to avoid unwanted tampering
    private val userState = MutableLiveData<User>()

    //Control Logic
    internal val authAttemptState = MutableLiveData<Unit>()
    internal val startAnimation = MutableLiveData<Unit>()

    //UI Binding
    internal val signInStatusText = MutableLiveData<String>()
    internal val authButtonText = MutableLiveData<String>()
    internal val satelliteDrawable = MutableLiveData<String>()

    private fun showErrorState() {
        signInStatusText.value = LOGIN_ERROR
        authButtonText.value = SIGN_IN
        satelliteDrawable.value = ANTENNA_EMPTY
    }
    //...
}

その後、View 自体を ViewModel に接続する必要がありますが、そのために必要な関数は簡単に記述できます。

class LoginView : Fragment() {

    private lateinit var viewModel: UserViewModel
    //...
    
    //Create and bind to ViewModel
    override fun onStart() {
        super.onStart()
        viewModel = ViewModelProviders.of(
        //...   
        ).get(UserViewModel::class.java)

        //start background anim
        (root_fragment_login.background as AnimationDrawable).startWithFade()

        setUpClickListeners()
        observeViewModel()

        viewModel.handleEvent(LoginEvent.OnStart)
    }

    private fun setUpClickListeners() {
      //...
    }

    private fun observeViewModel() {
        viewModel.signInStatusText.observe(
            viewLifecycleOwner,
            Observer {
                //"it" is the value of the MutableLiveData object, which is inferred to be a String automatically
                lbl_login_status_display.text = it
            }
        )

        viewModel.authButtonText.observe(
            viewLifecycleOwner,
            Observer {
                btn_auth_attempt.text = it
            }
        )

        viewModel.startAnimation.observe(
            viewLifecycleOwner,
            Observer {
                imv_antenna_animation.setImageResource(
                    resources.getIdentifier(ANTENNA_LOOP, "drawable", activity?.packageName)
                )
                (imv_antenna_animation.drawable as AnimationDrawable).start()
            }
        )

        viewModel.authAttemptState.observe(
            viewLifecycleOwner,
            Observer { startSignInFlow() }
        )

        viewModel.satelliteDrawable.observe(
            viewLifecycleOwner,
            Observer {
                imv_antenna_animation.setImageResource(
                    resources.getIdentifier(it, "drawable", activity?.packageName)
                )
            }
        )
    }

この例の完全なコードはこちらにあります。

お気づきかもしれませんが、この ViewModel を他の場所で再利用することはおそらくないでしょう。 .また、私たちのビューは十分に謙虚になり (コード カバレッジの基準と好みに応じて)、非常に簡単に記述できるようになりました。

プレゼンテーション ロジックの分布の間で、何らかの中途半端なものを見つけなければならない状況に出くわすことがあります。 これは、これらのアプローチのいずれにも厳密には従いません。

私はあるアプローチを別のアプローチよりも推奨しているわけではありませんが、目の前の要件に基づいて、アプローチを柔軟にすることをお勧めします.

設定と要件に基づいてアーキテクチャを選択してください

この記事の要点は、開発者が Android プラットフォームで MVVM スタイルの GUI アーキテクチャを構築するために使用できる 2 つの異なるアプローチを検討することでした (一部は他のプラットフォームに引き継がれます)。

実際には、これら 2 つのアプローチ内でも、小さな違いについてより具体的に知ることができます。

  • View は、所有する個々のウィジェット/コントロールごとにフィールドを監視する必要がありますか、それとも単一の モデル を発行する 1 つのフィールドを監視する必要がありますか? ビュー全体を毎回新しくレンダリングするには?
  • Presenter や Controller などをミックスに追加するだけで、View を Humble Object のままにして、ViewModel を 1 対 1 にする必要がなくなるのではないでしょうか?

話は安っぽいので、コードでこれらのことを試してみることを強くお勧めします 何をすべきかを私のような人に頼る必要がないように。

最終的に、優れたアーキテクチャを形成する 2 つの要素は、次の考慮事項に帰着すると思います。

まず、好みの方法が見つかるまで、いくつかの方法を試してみてください .これを行うには、各スタイルで実際にアプリケーションを作成し (単純な場合もあります)、何が正しいかを確認します。 .

第二に、好みはさておき、異なるスタイルは異なる欠点と引き換えに異なる利点を強調する傾向があることを理解してください.最終的には、盲信ではなく、プロジェクト要件の理解に基づいて適切な選択を選択できるようになります。 .

ソフトウェア アーキテクチャの詳細:

ソーシャル

https://www.instagram.com/rkay301/
https://www.facebook.com/wiseassblog/
https://twitter.com/wiseass301
https://wiseassblog.com/


  1. ITプロのようにWindows11/10タスクマネージャーを使用する方法

    私が気に入っているWindows11/10/8の最高の機能の1つは、タスクマネージャーです。謙虚なタスクマネージャーは何年にもわたって進化し、今では新しいWindows 11/10/8タスクマネージャーが多くの情報を提供します。これは、MarkRussinovichによるProcessExplorerのようなものです。この記事では、Windows 10タスクマネージャーのいくつかの機能と、Proのように使用する方法について簡単に説明します。 Windows11/10タスクマネージャー タスクマネージャを開くと、実行中のプログラムの非常に基本的なリストが表示され、「タスクを終了」するオプション

  2. プロのように Google 画像検索を使用する方法

    壁紙からトレンディなファッショナブルなアパレル、ほぼすべての検索に至るまで、Google は常に頼りになる検索エンジンです。ほんの数秒で、検索クエリに関連する何千もの結果が得られます。実際、Google ほど優れた企業や場所はありません。 ファイルの種類、色、画像サイズに基づいて、Google 画像検索で検索結果をフィルタリングできることをご存知ですか?高度な検索演算子の使用から逆画像検索の習得まで、プロのように Google 画像検索を使用したいですか?ここでは、Google 画像検索で優位に立つためのヒントとコツを紹介します。 それでは、Google 検索ツールを最大限に活用する方法