iOSエンジニアのつぶやき

毎朝8:30に iOS 関連の技術について1つぶやいています。まれに釣りについてつぶやく可能性があります。

知識ゼロからの Kotlin Android アプリリリースへの軌跡 / Day15【ViewModel編】

学ぶこと

すること

アプリの概要

レッスン5のコードラボでは、スターターコードから始めて、GuessTheWord アプリを開発します。GuessTheWord は、二人用の charades-style のゲームで、Player が協力して可能な限り最高のスコアを達成します。

最初のプレーヤーは、実際に単語自体を言わないように注意しながら、単語を実行します。

  • 二番目のプレーヤーが単語を正しく推測すると、最初のプレーヤーが Got it ボタンを押します。これにより、カウントが1つ増え、次の単語が表示されます。
  • 二番目のプレーヤーが単語を推測できない場合、最初のプレーヤーはスキップボタンを押します。これにより、カウントが1つ減り、次の単語にスキップします。
  • ゲームを終了するには、End Game ボタンを押します。(この機能は、シリーズの最初のコードラボのスターターコードには含まれていません)

Starter code を調べる

このタスクでは、Starter アプリをダウンロードして実行し、コードを調べます。

Get started

  1. Starter code をダウンロードして Android Studio で調べます。
  2. エミュレータでアプリを実行します。
  3. ボタンをタップします。Skip ボタンは次の単語を表示してスコアを1つ減らし、Got it ボタンは次の単語を表示してスコアを1つ増やします。End Game ボタンは実装されていないため、タップしても何も起こりません。

Code walkthrough を実行する

  1. Android Studio でコードを調べて、アプリがどのように機能するかを確認します。
  2. 特に重要な以下のファイルを必ず確認したください。

MainActivity.kt

このファイルには、デフォルトのテンプレート生成コードのみが含まれています。

res/layout/main_activity.xml

このファイルには、アプリの main layout が含まれています。NavHostFragment は、ユーザがアプリ内を移動する時に他の Fragment をホストします。

UI Fragment

Starter code には、com.example.android.guesstheword.screens パッケージの下の3つの異なるパッケージに3つの Fragment があります。

  • タイトル画面の title/TitleFragment
  • ゲーム画面の game/GameFragment
  • スコア画面の score/ScoreFragment

screens/title/TitleFragment.kt

Title Fragment は、アプリの起動時に表示される最初の画面です。クリックハンドラーが Play ボタンに設定され、ゲーム画面に移動します。

screens/game/GameFragment.kt

これは、ゲームのアクションのほとんどが行われる main fragment です。

  • 変数は、現在の単語と現在のスコアに対して定義されます。
  • resetList() メソッド内で定義されている wordList は、ゲームで使用される単語のサンプルリストです。
  • onSkip() メソッドは、Skip ボタンのクリックハンドラーです。スコアを1減らし、nextWord() メソッドを使用して次の単語を表示します。
  • onCorrect() メソッドは、GotIt ボタンのクリックハンドラーです。このメソッドは、onSkip() メソッドと同様に実装されます。

screens/score/ScoreFragment.kt

ScoreFragment はゲームの最終画面であり、プレーヤーの最終スコアを表示します。このコードラボでは、この画面を使用して最終スコアを表示する実装を追加します。

res/navigation/main_navigation.xml

navigation graph は、fragment が navigation を介してどのように接続されているかを示しています:

  • TitleFragment から、ユーザは GameFragment に移動できます。
  • GameFragment から、ユーザは ScoreFragment に移動できます。
  • ScoreFragmet から、ユーザは GameFragment に戻ることができます。

Starter app で問題を見つける

このタスクでは、GuessTheWord Starter app の問題を見つけます。

  1. Starter code を実行し、各単語の後に Skip または Got it をタップして、いくつかの単語でゲームをプレイします。

  2. ゲーム画面に単語と現在のスコアが表示されます。デバイスまたはエミュレータを回転させて、画面のむきをを変更します。現在のスコアが失われていることに注意してください。

  3. さらにいくつかの単語でゲームを実行します。ゲーム画面にスコアが表示されたら、アプリ閉じてから再度開きます。アプリの状態が保存されてないため、ゲームが最初から再開されることに注意してください。

  4. いくつかの単語でゲームをプレイしてから、End Game ボタンをタップします。何も起こらないことに注意してください。

アプリの問題:

  • Starter app は、デバイスの向きが変わった時やアプリがシャットダウンして再起動した時など、configuration の変更中にアプリの状態を保存および復元しません。 この問題は、onSaveInstanceState コールバックを使用して解決できます。ただし、onSaveInstanceState() メソッドを使用するには、状態をバンドルに保存するための追加のコードを記述し、その状態を取得するロジックを実装する必要があります。また、保存できるデータの量は最小限です。

  • ユーザが End Game をタップした時に、ゲーム画面はスコア画面に navigate されません。

これらの問題は、このコードラボで学習した app architecture components を使用して解決できます。

App architecture

app architecture は、アプリのクラスとそれらの間の関係を設計する方法であり、コードが整理され、特定のシナリオで適切に機能し、操作が簡単です。この4つのコードラボのセットでは、GuessTheWord アプリに加えた改善は、Android app architecture ガイドラインに従い、Android Architecture Components を使用します。Android app architecture は、MVVM architecture パターンに似ています。

GuessTheWordアプリは、関心の分離 の設計原則に従い、クラスに分割され、各クラスは個別の関心に対処します。レッスンのこの最初のコードラボでは、使用するクラスは UI controller、ViewModel および ViewModelFactory です。

UI controller

UI controller は、ActivityFragment などの UI-based のクラスです。UI controller には、View の表示やユーザ入力のキャプチャなど、UI と operation-system の相互作用を処理するロジックのみを含める必要があります。表示するテキストを決定するロジックなどの意思決定ロジックを UI controller に入れないでください。

GuessTheWord starter code では、UI controller は GameFragmentScoreFragmentTitleFragment の3つの Fragment です。"関心の分離" の設計原則に従って、GameFragment は、ゲーム要素を画面に描画し、ユーザがボタンをいつタップしたかを知ることだけを担当します。ユーザがボタンをタップすると、この情報が GameViewModel に渡されます。

ViewModel

ViewModel は、ViewModel に関連付けられた Fragment または Activity に表示されるデータを保持します。ViewModel は、データに対して簡単な計算と変換を実行して、UI controller によって表示されるデータを準備できます。この architecture では、ViewModel が意思決定を実行します。

GameViewModel は、スコア値、単語のリスト、現在の単語などのデータを保持します。これは、これらが画面に表示されるデータであるためです。GameViewModel には、データの現在の状態を判断するための簡単な計算を実行するためのビジネスロジックも含まれています。

ViewModelFactory

ViewModelFactory は、コンストラクターパラメーターの有無に関わらず、ViewModel オブジェクトをインスタンス化 します。

後のコードラボでは、UI controller と ViewModel に関連する他の Android Architecture Components について学習します。

GameViewModel を作成する

ViewModel クラスは、UI 関連のデータを格納および管理するように設計されています。このアプリでは、各 ViewModel が1つの Fragment に関連付けられています。

このタスクでは、最初の ViewModel をアプリに追加します。GameFragment のための GameViewModel です。また、ViewModel がライフサイクル対応であることの意味についても学びます。

GameViewModel クラスを追加する

  1. build.gradle(module:app) ファイルを開きます。dependencies ブロック内に、ViewModel の Gradle dependency を追加します。
//ViewModel
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0'
  1. パッケージ screens/game/ フォルダーに、GameViewModel という新しい Kotlin クラスを作成します。

  2. GameViewModel クラスに抽象クラス ViewModel を拡張させます。

  3. ViewModel がどのようにライフサイクルに対応しているかをよりよく理解するために、log ステートメントを使用して init ブロックを追加します。

class GameViewModel : ViewModel() {
   init {
       Log.i("GameViewModel", "GameViewModel created!")
   }
}

onCleard() をオーバーライドし、ロギングを追加

ViewModel は、関連づけられた Fragment が detach されるか、Activity が終了すると破棄されます。ViewModel が破棄される直前に、onCleared() コールバックが呼び出されてリソースがクリーンアップされます。

  1. GameViewModel クラスで、onCleared() メソッドをオーバーライドします。
  2. onCleared() 内にログステートメントを追加して、GameViewModel のライフサイクルを追跡します。
override fun onCleared() {
   super.onCleared()
   Log.i("GameViewModel", "GameViewModel destroyed!")
}

GameViewModel をゲームフラグメントに関連付けます

ViewModel は UI Controller に関連付ける必要があります。2つを関連付けるには、UI Controller 内に ViewModel への参照を作成します。

このステップでは、対応する UI Controller (GameFragmen)内に GameViewModel への参照を作成します。

  1. GameFragment クラスで、GameViewModel タイプのフィールドをクラス変数として追加します。
private lateinit var viewModel: GameViewModel

ViewModel を初期化する

画面の回転などの configuration 変更中に、Fragment などの UI Controller が再作成されます。ただし、ViewModel インスタンスは存続します。ViewModel クラスを使用して ViewModel インスタンスを作成すると、Fragmnet が再作成されるたびに新しいオブジェクトが作成されます。代わりに、ViewModelProvider を使用して ViewModel インスタンスを作成します。

Important: ViewModelインスタンスを直接インスタンス化するのではなく、常に ViewModelProvider を使用して ViewModel オブジェクトを作成してください。

ViewModelProvider の仕組み:

  • ViewModelProvider は、既存の ViewModel が存在する場合はそれを返し、まだ存在しない場合は新しい ViewModel を作成します。
  • ViewModelProvider は、指定されたスコープ(Activity または Fragment)に関連付けられた ViewModel インスタンスを作成します。
  • 作成された ViewModel は、スコープが有効である限り保持されます。例えば、スコープが Fragment の場合、Fragment がデタッチされるまで ViewModel は保持されます。

ViewModelProvider.get()) メソッドを使用して ViewModelProvider を作成し、ViewModel を初期化します。

  1. GameFragment クラスで、viewModel 変数を初期化します。binding 変数の定義後、このコードを onCreateView() 内に配置します。ViewModelProvider.get() メソッドを使用して、関連する GameFragment コンテキストと GameViewModel クラスを渡します。

  2. ViewModel オブジェクトの初期化の上に、ViewModelProvider.get() メソッド呼び出しをログに記録するログステートメントを追加します。

Log.i("GameFragment", "Called ViewModelProvider.get")
viewModel = ViewModelProvider(this).get(GameViewModel::class.java)
  1. アプリを実行します。Android Studio で Logcat ペインを開き、Game でフォルタリングします。エミュレータPlay ボタンをタップします。ゲーム画面が開きます。

Logcat に示されているように、GameFragmentonCreateView() メソッドは、ViewModelProvider.get() メソッドを呼び出して GameViewModel を作成します。GameFragmentGameViewModel に追加したロギングステートメントが Logcat に表示されます。

I/GameFragment: Called ViewModelProvider.get
I/GameViewModel: GameViewModel created!

バイスまたはエミュレータで自動回転設定を有効にし、画面の向きを数回変更します。GameFragment は毎回破棄されて再作成されるため、ViewModelProvider.get() が毎回呼び出されます。ただし、GameViewModel は一回だけ作成され、呼び出しごとに再作成または破棄されることはありません。

I/GameFragment: Called ViewModelProvider.get
I/GameViewModel: GameViewModel created!
I/GameFragment: Called ViewModelProvider.get
I/GameFragment: Called ViewModelProvider.get
I/GameFragment: Called ViewModelProvider.get
  1. ゲームを終了するか、game fragment から移動します。GameFragment が破棄されます。関連する GameViewModel も破棄され、コールバック onCleared() が呼び出されます。
I/GameFragment: Called ViewModelProvider.get
I/GameViewModel: GameViewModel created!
I/GameFragment: Called ViewModelProvider.get
I/GameFragment: Called ViewModelProvider.get
I/GameFragment: Called ViewModelProvider.get
I/GameViewModel: GameViewModel destroyed!

GameViewModel にデータを入力します

ViewModel は configuration の変更に耐えるため、configuration の変更に耐える必要があるデータに適した場所です。

  • 画面に表示するデータと、そのデータを処理するコードを ViewModel に配置します。
  • ActivityFragmentView は構成の変更後も存続しないため、ViewModel に Fragment、Activity、View への参照を含めることはできません。

比較のために、ViewModel を追加する前と ViewModel を追加した後、スターターアプリで GameFragment UI データがどのように処理されるかを次に示します:

  • ViewModel 追加前:
    • アプリが画面の回転などの configuration 変更を行うと、GameFragment が破棄され、再作成されます。データは失われます。
  • ViewModel を追加し、UI データを ViewModel に移動した後:
    • Fragment が表示する必要のある全てのデータは、ViewModel になりました。アプリで構成が変更されても、ViewModel は存続し、データは保持されます。

このタスクでは、データを処理するためのメソッドとともに、アプリの UI データを GameViewModel クラスに移動します。これを行うと、configuration の変更中にデータが保持されます。

データフィールドとデータ処理を ViewModel に移動します

次のデータフィールドとメソッドを GameFragment から GameViewModel に移動します:

  1. wordscorewordList のデータフィールドを移動します。wordscoreprivate でないことを確認してください。

View への参照が含まれているため、binding 変数の GameFragmentBinding を移動しないでください。この変数は、レイアウトを Inflate し、クリックリスナーを設定し、画面にデータを表示するために使用されます。これは Fragment の責任です。

  1. resetList() メソッドと nextWord() メソッドを移動します。これらのメソッドは、画面に表示する単語を決定します。

  2. onCreateView() メソッド内から、resetList() および nextWord() へのメソッド呼び出しを GameViewModelinit ブロックに移動します。

Fragment が作成されるたびにではなく、ViewModel の作成時に単語リストをリセットする必要があるため、これらのメソッドは init ブロックに含まれている必要があります。GameFragmentinit ブロックにあるログステートメントを削除できます。

GameFragmnetonSkip() および onCorrect() クリックハンドラーには、データを処理して UI を更新するためのコードが含まれています。UI を更新するコードは Fragment 内にとどまる必要がありますが、データを処理するためのコードを ViewModel に移動する必要があります。

今は、両方の場所に同じメソッドを配置します:

  1. onSkip() メソッドと onCorrect() メソッドを GameFragment から GameViewModel にコピーします。
  2. GameViewModel で、onSkip メソッドと onCorrect メソッドが private ではないことを確認します。これは、これらのメソッドを Fragment から参照するためです。

リファクタリング後の GameViewModel クラスのコードは次の通りです:

class GameViewModel : ViewModel() {
   // The current word
   var word = ""
   // The current score
   var score = 0
   // The list of words - the front of the list is the next word to guess
   private lateinit var wordList: MutableList<String>

   /**
    * Resets the list of words and randomizes the order
    */
   private fun resetList() {
       wordList = mutableListOf(
               "queen",
               "hospital",
               "basketball",
               "cat",
               "change",
               "snail",
               "soup",
               "calendar",
               "sad",
               "desk",
               "guitar",
               "home",
               "railway",
               "zebra",
               "jelly",
               "car",
               "crow",
               "trade",
               "bag",
               "roll",
               "bubble"
       )
       wordList.shuffle()
   }

   init {
       resetList()
       nextWord()
       Log.i("GameViewModel", "GameViewModel created!")
   }
   /**
    * Moves to the next word in the list
    */
   private fun nextWord() {
       if (!wordList.isEmpty()) {
           //Select and remove a word from the list
           word = wordList.removeAt(0)
       }
       updateWordText()
       updateScoreText()
   }
 /** Methods for buttons presses **/
   fun onSkip() {
       score--
       nextWord()
   }

   fun onCorrect() {
       score++
       nextWord()
   }

   override fun onCleared() {
       super.onCleared()
       Log.i("GameViewModel", "GameViewModel destroyed!")
   }
}

GameFragment のコード:

/**
* Fragment where the game is played
*/
class GameFragment : Fragment() {


   private lateinit var binding: GameFragmentBinding


   private lateinit var viewModel: GameViewModel


   override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                             savedInstanceState: Bundle?): View? {

       // Inflate view and obtain an instance of the binding class
       binding = DataBindingUtil.inflate(
               inflater,
               R.layout.game_fragment,
               container,
               false
       )

       Log.i("GameFragment", "Called ViewModelProvider.get")
       viewModel = ViewModelProvider(this).get(GameViewModel::class.java)

       binding.correctButton.setOnClickListener { onCorrect() }
       binding.skipButton.setOnClickListener { onSkip() }
       updateScoreText()
       updateWordText()
       return binding.root

   }


   /** Methods for button click handlers **/

   private fun onSkip() {
       score--
       nextWord()
   }

   private fun onCorrect() {
       score++
       nextWord()
   }


   /** Methods for updating the UI **/

   private fun updateWordText() {
       binding.wordText.text = word
   }

   private fun updateScoreText() {
       binding.scoreText.text = score.toString()
   }
}

GameFragment のクリックハンドラーとデータフィールドへの参照を更新します

  1. GameFragment で、onSkip() メソッドと onCorrect() メソッドを更新します。コードを削除してスコアを更新し、代わりに viewModel で対応する onSkip() メソッドと onCorrect() メソッドを呼び出します。

  2. nextWord() メソッドを ViewModel に移動したため、GameFragment はそれにアクセスできなくなりました。

GameFragmentonSkip() メソッドと onCorrect() メソッドで、nextWord() の呼び出しを updateScoreText()updateWordText() 置き換えます。これらのメソッドは、画面にデータを表示します。

private fun onSkip() {
   viewModel.onSkip()
   updateWordText()
   updateScoreText()
}
private fun onCorrect() {
   viewModel.onCorrect()
   updateScoreText()
   updateWordText()
}
  1. scoreword 変数は現在 GameViewModel にあるため、GameFragment にある変数を更新します。
private fun updateWordText() {
   binding.wordText.text = viewModel.word
}

private fun updateScoreText() {
   binding.scoreText.text = viewModel.score.toString()
}

Reminder: Activity、Fragment および View は configuration の変更後も存続しないため、ViewModel にアプリの Activity、Fragment、View への参照を含めることはできません。

  1. GameViewModelnextWord() メソッド内で、updateWordText() メソッドと updateScoreText() メソッドの呼び出しを削除します。これらのメソッドは現在、GameFragment から呼び出されています。

  2. アプリをビルドし、エラーがないことを確認します。エラーが発生した場合は、プロジェクトをクリーンアップして rebuild します。

  3. アプリを実行し、いくつかの Word でゲームをプレイします。ゲーム画面を表示している時に、デバイスを回転させます。現在のスコアと単語は、向きを変更した後も保持されることに注目してください。

End Game ボタンのクリックリスナーを実装する

このタスクでは、End Game ボタンのクリックリスナーを実装します。

  1. GameFragment で、onEndGame() と呼ばれるメソッドを追加します。onEndGame() メソッドは、ユーザが End Game ボタンをタップした時に呼ばれます。
private fun onEndGame() {
   }
  1. GameFragmentonCreateView() メソッド内で、GotIt ボタンと Skip ボタンのクリックリスナーを設定するコードを見つけます。これら2行のすぐ下に、End Game ボタンのクリックリスナーを設定します。binding 変数を使用します。クリックリスナー内で、onEndGame() メソッドを呼び出します。
binding.endGameButton.setOnClickListener { onEndGame() }
  1. GameFragment で、gameFinished() というメソッドを追加して、アプリをスコア画面に移動します。Safe Args を使用して、引数としてスコアを渡します。
/**
* Called when the game is finished
*/
private fun gameFinished() {
   Toast.makeText(activity, "Game has just finished", Toast.LENGTH_SHORT).show()
   val action = GameFragmentDirections.actionGameToScore()
   action.score = viewModel.score
   NavHostFragment.findNavController(this).navigate(action)
}
  1. onEndGame() メソッド内で、gameFinished() メソッドを呼び出します。
private fun onEndGame() {
   gameFinished()
}
  1. アプリを実行し、ゲームをプレイします。End Game ボタンをタップします。アプリはスコア画面に移動しますが、最終スコアは表示されないことに注意してください。次のタスクでこれを修正します。

ViewModelFactory を使用する

ユーザがゲームを終了すると、ScoreFragment はスコアを表示しません。ViewModelScoreFragment によって表示されるスコアを保持する必要があります。factory medhot pattern を使用して、ViewModel の初期化中にスコア値を渡します。

factory method pattern は、factory method を使用してオブジェクトを作成する creational design pattern です。factory method は、同じクラスのインスタンスを返すメソッドです。

このタスクでは、スコアフラグメントのパラメーター化されたコンストラクターViewModelインスタンス化するファクトリメソッドを使用して ViewModel を作成します。

  1. スコアパッケージの下に、ScoreViewModel という新しい Kotlin クラスを作成します。このクラスは、score fragment の ViewModel になります。

  2. ViewModel から ScoreViewModel クラスを拡張します。最終スコアのコンストラクターパラメーターを追加します。ログステートメントを使用して init ブロックを追加します。

  3. ScoreViewModel クラスで、score という変数を追加して、最終スコアを保存します。

class ScoreViewModel(finalScore: Int) : ViewModel() {
   // The final score
   var score = finalScore
   init {
       Log.i("ScoreViewModel", "Final score is $finalScore")
   }
}
  1. score パッケージの下に、ScoreViewModelFactory という別の Kotlin クラスを作成します。このクラスは、ScoreViewModel オブジェクトのインスタンス化を担当します。

  2. ViewModelProvider.Factory から ScoreViewModelFactory クラスを拡張します。最終スコアのコンストラクターパラメーターを追加します。

class ScoreViewModelFactory(private val finalScore: Int) : ViewModelProvider.Factory {
}
  1. ScoreViewModelFactory で、Android Studio は実装されていない抽象メンバーに関するエラーを表示します。エラーを解決するには、create() メソッドをオーバーライドします。create() メソッドで、新しく作成された ScoreViewModel オブジェクトを返します。
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
   if (modelClass.isAssignableFrom(ScoreViewModel::class.java)) {
       return ScoreViewModel(finalScore) as T
   }
   throw IllegalArgumentException("Unknown ViewModel class")
}
  1. ScoreFragment で、ScoreViewModel および ScoreViewModelFactory のクラス変数を作成します。
private lateinit var viewModel: ScoreViewModel
private lateinit var viewModelFactory: ScoreViewModelFactory
  1. ScoreFragmentonCreateView() 内で、binding 変数を初期化した後、viewModelFactory を初期化します。ScoreViewModelFactory を使用します。コンストラクターパラメータとして、argument bundle から final score を ScoreViewModelFactory() に渡します。
viewModelFactory = ScoreViewModelFactory(ScoreFragmentArgs.fromBundle(arguments!!).score)
  1. onCreateView() で、viewModelFactory を初期化した後、viewModel オブジェクトを初期化します。ViewModelProvider.get() メソッドを呼び出し、関連する score fragment conext と viewModelFactory を渡します。これにより、viewModelFactory クラスで定義されたファクトリメソッドを使用して ScoreViewModel オブジェクトが作成されます。
viewModel = ViewModelProvider(this, viewModelFactory)
       .get(ScoreViewModel::class.java)
  1. onCreateView() メソッドで、viewModel を初期化した後、scoreText view のテキストを ScoreViewModel で定義された最終スコアに設定します。
binding.scoreText.text = viewModel.score.toString()
  1. アプリを実行してゲームをプレイします。スコアフラグメントに最終スコアが表示されていることに注意してください。

  1. option: ScoreViewModel でフィルタリングして、Logcat の ScoreViewModel ログを確認します。スコア値が表示されます。
2019-02-07 10:50:18.328 com.example.android.guesstheword I/ScoreViewModel: Final score is 15

Note: このアプリでは、スコアを viewModel.score 変数に直接割り当てることができるため、ScoreViewModelViewModelFactory を追加する必要はありません。ただし、viewModel の初期化時にデータが必要になる場合があります。

このタスクでは、ViewModel を使用するように ScoreFragment を実装しました。また、ViewModelFactory インターフェースを使用して ViewModel のパラメーター化されたコンストラクターを作成する方法も学習しました。

まとめ

  • Android app architecture ガイドラインでは、異なる責任を持つクラスを分離することを推奨しています。
  • UI Controller は ActivityFragment などの UI のベースとなるクラスです。UI controllers には、UI とオペレーティングシステムの相互作用を処理するロジックのみを含める必要があります。UI に表示するデータを含めるべきではありません。そのようなデータは ViewModel に配置します。

  • ViewModel クラスは、UI 関連のデータを格納および管理します。ViewModel クラスを使用すると、データは画面の回転などの configuration の変更に耐えることができます。

  • ViewModel は、推奨される Android Architecture Components の1つです。

  • ViewModelProvider.Factory は、ViewModel オブジェクトを作成するために使用できるインターフェースです。

次の表は、UI controllers とそれらのデータを保持する ViewModel インスタンスを比較しています:

UI controller ViewModel
UI controller の例は、このコードラボで作成した ScoreFragment です。 ViewModel の例は、このコードラボで作成した ScoreViewModel です。
UI に表示されるデータは含まれていません。 UI controller が UI に表示するデータが含まれます。
データを表示するためのコードと、クリックリスナーなどのユーザーイベントコードが含まれています。 データ処理用のコードが含まれています。
configuration が変更されるたびに破棄され、再作成されます。 関連づけられた UI controller が完全に消えた場合にのみ破棄されます。Activity の場合、Activity が終了した場合、または Fragment の場合、Fragment が切り離された場合
View が含まれています。 Activity、Fragment、または View への参照を含めることはできません。これらは configuration の変更後も存続しないためですが、ViewModel には存続します。
関連する ViewModel への参照が含まれています。 関連する UI controller への参照は含まれていません。

その他の記事

yamato8010.hatenablog.com

yamato8010.hatenablog.com

yamato8010.hatenablog.com