この Codelab は、Android Kotlin の基礎コースの一部です。このコースを最大限に活用するには、Codelab を順番に進めることをおすすめします。コースのすべての Codelab は、Android Kotlin の基礎の Codelab のランディング ページに一覧表示されています。
タイトル画面 |
ゲーム画面 |
スコア画面 |
はじめに
この Codelab では、Android アーキテクチャ コンポーネントの 1 つである ViewModel について学習します。
ViewModelクラスを使用して、ライフサイクルを意識した方法で UI 関連のデータを保存し管理します。ViewModelクラスを使用すると、画面の回転やキーボードの利用可能性の変更などのデバイス構成の変更後もデータを維持することができます。ViewModelFactoryクラスを使用して、構成の変更後も存続するViewModelオブジェクトをインスタンス化して返します。
前提となる知識
- Kotlin で基本的な Android アプリを作成する方法。
- ナビゲーション グラフを使用してアプリにナビゲーションを実装する方法。
- アプリのデスティネーション間を移動し、ナビゲーション デスティネーション間でデータを渡すコードを追加する方法。
- アクティビティとフラグメントのライフサイクルの仕組み。
- アプリにログ情報を追加し、Android Studio の Logcat を使用してログを読み取れること
学習内容
- 推奨される Android アプリ アーキテクチャの使用方法。
- アプリで
Lifecycle、ViewModel、ViewModelFactoryクラスを使用する方法。 - デバイスの構成変更後も UI データを維持する方法
- ファクトリ メソッド デザイン パターンとその使用方法。
- インターフェース
ViewModelProvider.Factoryを使用してViewModelオブジェクトを作成する方法。
演習内容
- アプリに
ViewModelを追加して、構成変更後もデータが維持されるようにアプリのデータを保存します。 ViewModelFactoryとファクトリー メソッド デザイン パターンを使用して、コンストラクタ パラメータを含むViewModelオブジェクトをインスタンス化します。
レッスン 5 の Codelab では、スターター コードから GuessTheWord アプリを開発します。GuessTheWord は、2 人のプレーヤーが最高スコアを目指して協力する、2 プレーヤー ジェスチャー ゲームです。
最初のプレーヤーはアプリの単語を見て、2 番目のプレーヤーに単語を見せないようにしながら、各単語を順番に演じます。2 人目のプレーヤーが単語を当てようとします。
ゲームをプレイするには、最初のプレーヤーがデバイスでアプリを開き、下のスクリーンショットに示すように、「ギター」などの単語を表示します。
最初のプレーヤーは、単語自体を言わないように注意しながら、単語を演じます。
- 2 人目のプレーヤーが単語を正しく当てると、1 人目のプレーヤーが [Got It] ボタンを押します。これにより、カウントが 1 つ増え、次の単語が表示されます。
- 2 人目のプレーヤーが単語を当てられない場合、1 人目のプレーヤーが [スキップ] ボタンを押します。これにより、カウントが 1 減り、次の単語にスキップします。
- ゲームを終了するには、[End Game] ボタンを押します。(この機能は、シリーズの最初の Codelab のスターター コードには含まれていません)。
このタスクでは、スターター アプリをダウンロードして実行し、コードを確認します。
ステップ 1: 開始する
- GuessTheWord スターター コードをダウンロードして、Android Studio でプロジェクトを開きます。
- Android 搭載デバイスまたはエミュレータでアプリを実行します。
- ボタンをタップします。[Skip] ボタンをタップすると、次の単語が表示され、スコアが 1 減ります。[Got It] ボタンをタップすると、次の単語が表示され、スコアが 1 増えます。[End Game] ボタンは実装されていないため、タップしても何も起こりません。
ステップ 2: コードのウォークスルーを行う
- Android Studio でコードを確認して、アプリの動作を把握します。
- 特に重要な以下のファイルを確認してください。
MainActivity.kt
このファイルには、デフォルトのテンプレート生成コードのみが含まれています。
res/layout/main_activity.xml
このファイルには、アプリのメイン レイアウトが含まれています。NavHostFragment は、ユーザーがアプリ内を移動する際に他のフラグメントをホストします。
UI フラグメント
スターター コードには、com.example.android.guesstheword.screens パッケージの下の 3 つの異なるパッケージに 3 つのフラグメントがあります。
title/TitleFragment: タイトル画面- ゲーム画面の
game/GameFragment - スコア画面の
score/ScoreFragment
screens/title/TitleFragment.kt
タイトル フラグメントは、アプリの起動時に最初に表示される画面です。クリック ハンドラは、ゲーム画面に移動するために [Play] ボタンに設定されています。
screens/game/GameFragment.kt
メイン フラグメントであり、ゲームのアクションのほとんどはここで発生します。
- 現在の単語と現在のスコアの変数が定義されています。
resetList()メソッド内で定義されたwordListは、ゲームで使用される単語のサンプル リストです。onSkip()メソッドは、[スキップ] ボタンのクリック ハンドラです。スコアを 1 減らし、nextWord()メソッドを使用して次の単語を表示します。onCorrect()メソッドは、[Got It] ボタンのクリック ハンドラです。このメソッドは、onSkip()メソッドと同様に実装されます。唯一の違いは、このメソッドではスコアから減算するのではなく、1 を加算することです。
screens/score/ScoreFragment.kt
ScoreFragment はゲームの最終画面で、プレーヤーの最終スコアが表示されます。この Codelab では、この画面を表示して最終スコアを表示する実装を追加します。
res/navigation/main_navigation.xml
ナビゲーション グラフは、ナビゲーションを介してフラグメントがどのように接続されているかを示しています。
- タイトル フラグメントから、ゲーム フラグメントに移動できます。
- ゲーム フラグメントから、スコア フラグメントに移動できます。
- スコア フラグメントから、ゲーム フラグメントに戻ることができます。
このタスクでは、GuessTheWord スターター アプリの問題を見つけます。
- スターター コードを実行し、数個の単語をプレイします。各単語の後に [Skip] または [Got It] をタップします。
- ゲーム画面に単語と現在のスコアが表示されるようになりました。デバイスまたはエミュレータを回転して、画面の向きを変更すると、現在のスコアが失われます。
- ゲームを数語プレイします。ゲーム画面にスコアが表示されたら、アプリを閉じて再び開きます。アプリの状態が保存されていないため、ゲームが最初から再開されることに注目してください。
- 数個の単語をプレイしてから、[End Game] ボタンをタップします。何も起こらないことを確認します。
アプリに関する問題:
- スターター アプリでは、デバイスの画面の向きが変化したときや、アプリがシャットダウンして再起動したときなどの構成変更時に、アプリの状態が保存されません。
この問題は、onSaveInstanceState()コールバックを使用して解決できます。ただし、onSaveInstanceState()メソッドを使用する場合、バンドルに状態を保存するためのコードや、その状態を取得するロジックを実装する必要があります。また、保存できるデータの量は最小限です。 - ユーザーが [End Game] ボタンをタップしても、ゲーム画面がスコア画面に移動しません。
この問題を解決するには、この Codelab で学習するアプリ アーキテクチャ コンポーネントを使用します。
アプリ アーキテクチャ
アプリ アーキテクチャは、コードが整理され、特定のシナリオでパフォーマンスが向上し、操作が容易になるように、アプリのクラスとその関係を設計する方法です。この 4 つの Codelab では、GuessTheWord アプリに加える改善は Android アプリ アーキテクチャのガイドラインに沿っており、Android アーキテクチャ コンポーネントを使用します。Android アプリのアーキテクチャは、MVVM(モデル - ビュー - ビューモデル)アーキテクチャ パターンに似ています。
GuessTheWord アプリは、関心の分離という設計原則に従い、クラスに分割されています。各クラスは別々の関心事に対応しています。このレッスン最初の Codelab では、UI コントローラ、ViewModel、ViewModelFactory を使用します。
UI コントローラ
UI コントローラは、Activity や Fragment などの UI ベースのクラスです。UI コントローラには、ビューの表示やユーザー入力の取得など、UI やオペレーティング システムとのやり取りを処理するロジックのみを含めるべきです。表示するテキストを決定するロジックなどの意思決定ロジックは、UI コントローラに入れないでください。
GuessTheWord スターター コードでは、UI コントローラは GameFragment、ScoreFragment,、TitleFragment の 3 つのフラグメントです。「関心の分離」という設計原則に従い、GameFragment はゲーム要素を画面に描画し、ユーザーがボタンをタップしたタイミングを把握するだけで、それ以上のことは行いません。ユーザーがボタンをタップすると、この情報が GameViewModel に渡されます。
ViewModel
ViewModel は、関連付けられたフラグメントまたはアクティビティに表示されるデータを保持します。ViewModelまた、UI コントローラがデータを表示するための前処理として、簡単な計算と変換を行えます。ViewModelこのアーキテクチャでは、ViewModel が意思決定を行います。GameViewModel は、スコア値、単語のリスト、現在の単語など、画面に表示するデータを保持します。GameViewModel には、データの現在の状態を判断するための簡単な計算を行うビジネス ロジックも含まれています。
ViewModelFactory
ViewModelFactory は、コンストラクタ パラメータの有無にかかわらず、ViewModel オブジェクトをインスタンス化します。

後の Codelab では、UI コントローラと ViewModel に関連する他の Android アーキテクチャ コンポーネントについて学習します。
ViewModel クラスは、UI 関連のデータを保存し管理するように設計されています。このアプリでは、各 ViewModel は 1 つのフラグメントに関連付けられています。
このタスクでは、アプリに最初の ViewModel(GameFragment の GameViewModel)を追加します。また、ViewModel がライフサイクルを認識するとはどういうことかも学びます。
ステップ 1: GameViewModel クラスを追加する
build.gradle(module:app)ファイルを開きます。dependenciesブロック内で、ViewModelの Gradle 依存関係を追加します。
ライブラリの最新バージョンを使用すると、ソリューション アプリは想定どおりにコンパイルされます。そうでない場合は、問題を解決するか、以下のバージョンに戻してみてください。
//ViewModel
implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'screens/game/パッケージ フォルダに、GameViewModelという名前の新しい Kotlin クラスを作成します。GameViewModelクラスが抽象クラスViewModelを拡張するようにします。ViewModelがライフサイクルを認識する方法を理解するために、logステートメントを含むinitブロックを追加します。
class GameViewModel : ViewModel() {
init {
Log.i("GameViewModel", "GameViewModel created!")
}
}ステップ 2: onCleared() をオーバーライドしてロギングを追加する
ViewModel は、関連付けられたフラグメントの接続が解除されたとき、またはアクティビティが終了したときに破棄されます。ViewModel が破棄される直前には、onCleared() コールバックが呼び出されてリソースがクリーンアップされます。
GameViewModelクラスで、onCleared()メソッドをオーバーライドします。onCleared()内にログ ステートメントを追加して、GameViewModelのライフサイクルを追跡します。
override fun onCleared() {
super.onCleared()
Log.i("GameViewModel", "GameViewModel destroyed!")
}ステップ 3: GameViewModel をゲーム フラグメントに関連付ける
ViewModel は UI コントローラに関連付ける必要があります。この 2 つを関連付けるには、UI コントローラ内に ViewModel への参照を作成します。
このステップでは、対応する UI コントローラ(GameFragment)内に GameViewModel の参照を作成します。
GameFragmentクラスで、GameViewModel型のフィールドをトップレベルにクラス変数として追加します。
private lateinit var viewModel: GameViewModelステップ 4: ViewModel を初期化する
画面の回転などの構成の変更中には、フラグメントなどの UI コントローラが再作成されます。ただし、ViewModel インスタンスは存続します。ViewModel クラスを使用して ViewModel インスタンスを作成すると、フラグメントが再作成されるたびに新しいオブジェクトが作成されます。代わりに、ViewModelProvider を使用して ViewModel インスタンスを作成します。

ViewModelProvider の仕組み:
ViewModelProviderは、既存のViewModelが存在する場合はそれを返し、存在しない場合は新しいViewModelを作成します。ViewModelProviderは、指定されたスコープ(アクティビティまたはフラグメント)に関連付けてViewModelインスタンスを作成します。- 作成された
ViewModelは、スコープが存続している限り保持されます。たとえば、スコープがフラグメントの場合、ViewModelはフラグメントがデタッチされるまで保持されます。
ViewModelProviders.of() メソッドを使用して ViewModelProvider を作成し、ViewModel を初期化します。
GameFragmentクラスで、viewModel変数を初期化します。このコードをonCreateView()のバインディング変数の定義の後に追加します。ViewModelProviders.of()メソッドを使用し、関連するGameFragmentコンテキストとGameViewModelクラスを渡します。ViewModelオブジェクトの初期化の上に、ViewModelProviders.of()メソッド呼び出しをログに記録するログ ステートメントを追加します。
Log.i("GameFragment", "Called ViewModelProviders.of")
viewModel = ViewModelProviders.of(this).get(GameViewModel::class.java)- アプリを実行します。Android Studio で [Logcat] ペインを開き、
Gameでフィルタします。デバイスまたはエミュレータの [再生] ボタンをタップします。ゲーム画面が開きます。
Logcat に示されているように、GameFragmentのonCreateView()メソッドがViewModelProviders.of()メソッドを呼び出してGameViewModelを作成します。GameFragmentとGameViewModelに追加したロギング ステートメントが Logcat に表示されます。

- デバイスやエミュレータで自動回転設定を有効にして、画面の向きを数回変更します。
GameFragmentは毎回破棄されて再作成されるため、ViewModelProviders.of()は毎回呼び出されます。ただし、GameViewModelは一度だけ作成され、呼び出しごとに再作成または破棄されることはありません。
I/GameFragment: Called ViewModelProviders.of I/GameViewModel: GameViewModel created! I/GameFragment: Called ViewModelProviders.of I/GameFragment: Called ViewModelProviders.of I/GameFragment: Called ViewModelProviders.of
- ゲームを終了するか、ゲーム フラグメントから移動します。
GameFragmentが破棄されます。関連付けられているGameViewModelも破棄され、コールバックonCleared()が呼び出されます。
I/GameFragment: Called ViewModelProviders.of I/GameViewModel: GameViewModel created! I/GameFragment: Called ViewModelProviders.of I/GameFragment: Called ViewModelProviders.of I/GameFragment: Called ViewModelProviders.of I/GameViewModel: GameViewModel destroyed!
ViewModel は構成の変更後も保持されるため、構成の変更後も保持する必要があるデータを保存するのに適しています。
- 画面に表示するデータと、そのデータを処理するコードを
ViewModelに配置します。 - アクティビティ、フラグメント、ビューは構成の変更後も存続しないため、
ViewModelにフラグメント、アクティビティ、ビューへの参照を含めることはできません。

比較のため、ViewModel を追加する前と追加した後のスターター アプリでの GameFragment UI データの処理方法を以下に示します。ViewModel
ViewModelを追加する前:
画面の回転などの構成変更が発生すると、ゲーム フラグメントが破棄され、再作成されます。データが失われます。ViewModelを追加してゲーム フラグメントの UI データをViewModelに移動すると、フラグメントで表示する必要があるすべてのデータがViewModelになります。
アプリで構成変更が発生しても、ViewModelは存続し、データは保持されます。

このタスクでは、アプリの UI データと、データを処理するメソッドを GameViewModel クラスに移動します。これは、構成変更時にデータが保持されるようにするためです。
ステップ 1: データ フィールドとデータ処理を ViewModel に移動する
次のデータ フィールドとメソッドを GameFragment から GameViewModel に移動します。
word、score、wordListのデータ フィールドを移動します。wordとscoreがprivateでないことを確認します。
バインディング変数GameFragmentBindingにはビューへの参照が含まれているため、移動しないでください。この変数は、レイアウトの拡張、クリック リスナーの設定、画面へのデータの表示(フラグメントの役割)に使用されます。resetList()メソッドとnextWord()メソッドを移動します。これらのメソッドは、画面に表示する単語を決定します。onCreateView()メソッド内で、resetList()とnextWord()へのメソッド呼び出しをGameViewModelのinitブロックに移動します。
これらのメソッドはinitブロックに含める必要があります。これは、フラグメントが作成されるたびにではなく、ViewModelが作成されるときに単語リストをリセットする必要があるためです。GameFragmentのinitブロックでログ ステートメントを削除できます。
GameFragment の onSkip() と onCorrect() のクリック ハンドラには、データを処理して UI を更新するコードが含まれています。UI を更新するコードはフラグメントに残す必要がありますが、データを処理するコードは ViewModel に移動する必要があります。
今のところ、両方に同じメソッドを配置します。
onSkip()メソッドとonCorrect()メソッドをGameFragmentからGameViewModelにコピーします。GameViewModelで、onSkip()メソッドとonCorrect()メソッドがprivateでないことを確認します。これらのメソッドはフラグメントから参照されるためです。
リファクタリング後の 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() {
if (!wordList.isEmpty()) {
score--
}
nextWord()
}
fun onCorrect() {
if (!wordList.isEmpty()) {
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 ViewModelProviders.of")
viewModel = ViewModelProviders.of(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() {
if (!wordList.isEmpty()) {
score--
}
nextWord()
}
private fun onCorrect() {
if (!wordList.isEmpty()) {
score++
}
nextWord()
}
/** Methods for updating the UI **/
private fun updateWordText() {
binding.wordText.text = word
}
private fun updateScoreText() {
binding.scoreText.text = score.toString()
}
}ステップ 2: GameFragment のクリック ハンドラとデータ フィールドへの参照を更新する
GameFragmentで、onSkip()メソッドとonCorrect()メソッドを更新します。スコアを更新するコードを削除し、代わりにviewModelで対応するonSkip()メソッドとonCorrect()メソッドを呼び出します。nextWord()メソッドをViewModelに移動したため、ゲーム フラグメントからアクセスできなくなりました。GameFragmentのonSkip()メソッドとonCorrect()メソッドで、nextWord()の呼び出しをupdateScoreText()とupdateWordText()に置き換えます。これらのメソッドは、データを画面に表示します。
private fun onSkip() {
viewModel.onSkip()
updateWordText()
updateScoreText()
}
private fun onCorrect() {
viewModel.onCorrect()
updateScoreText()
updateWordText()
}GameFragmentで、score変数とword変数を更新してGameViewModel変数を使用します。これらの変数はGameViewModelに含まれるようになったためです。
private fun updateWordText() {
binding.wordText.text = viewModel.word
}
private fun updateScoreText() {
binding.scoreText.text = viewModel.score.toString()
}GameViewModelのnextWord()メソッド内で、updateWordText()メソッドとupdateScoreText()メソッドの呼び出しを削除します。これらのメソッドはGameFragmentから呼び出されるようになりました。- アプリをビルドして、エラーがないことを確認します。エラーが発生した場合は、プロジェクトをクリーンアップして再ビルドします。
- アプリを実行して、数語プレイします。ゲーム画面でデバイスを回転させます。向きを変更した後も現在のスコアと現在の単語が保持されています。
パフォーマンスは良好です。これで、アプリのデータはすべて ViewModel に保存されるため、構成変更後も保持されます。
このタスクでは、[End Game] ボタンのクリック リスナーを実装します。
GameFragmentで、onEndGame()というメソッドを追加します。ユーザーが [End Game] ボタンをタップすると、onEndGame()メソッドが呼び出されます。
private fun onEndGame() {
}GameFragmentのonCreateView()メソッド内で、[Got It] ボタンと [Skip] ボタンのクリック リスナーを設定するコードを探します。これらの 2 行のすぐ下に、[End Game] ボタンのクリック リスナーを設定します。バインディング変数bindingを使用します。クリック リスナー内で、onEndGame()メソッドを呼び出します。
binding.endGameButton.setOnClickListener { onEndGame() }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)
}onEndGame()メソッドで、gameFinished()メソッドを呼び出します。
private fun onEndGame() {
gameFinished()
}- アプリを実行し、ゲームをプレイして、いくつかの単語を切り替えます。[End Game ] ボタンをタップします。アプリはスコア画面に移動しますが、最終スコアは表示されません。これは次のタスクで修正します。
|
|
ユーザーがゲームを終了すると、ScoreFragment にスコアが表示されません。ScoreFragment で表示されるスコアを保持する ViewModel が必要です。ファクトリ メソッド パターンを使用して、ViewModel の初期化中にスコア値を渡します。
ファクトリ メソッド パターンは、ファクトリ メソッドを使用してオブジェクトを作成する作成デザイン パターンです。ファクトリ メソッドは、同じクラスのインスタンスを返すメソッドです。
このタスクでは、スコア フラグメントのパラメータ化されたコンストラクタと ViewModel をインスタンス化するファクトリ メソッドを使用して ViewModel を作成します。
scoreパッケージの下に、ScoreViewModelという名前の新しい Kotlin クラスを作成します。このクラスは、スコア フラグメントのViewModelになります。ViewModel.からScoreViewModelクラスを拡張します。最終スコアのコンストラクタ パラメータを追加します。ログ ステートメントを含むinitブロックを追加します。ScoreViewModelクラスに、最終スコアを保存するscoreという変数を追加します。
class ScoreViewModel(finalScore: Int) : ViewModel() {
// The final score
var score = finalScore
init {
Log.i("ScoreViewModel", "Final score is $finalScore")
}
}scoreパッケージの下に、ScoreViewModelFactoryという別の Kotlin クラスを作成します。このクラスは、ScoreViewModelオブジェクトのインスタンス化を担当します。ViewModelProvider.FactoryからScoreViewModelFactoryクラスを拡張します。最終スコアのコンストラクタ パラメータを追加します。
class ScoreViewModelFactory(private val finalScore: Int) : ViewModelProvider.Factory {
}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")
}ScoreFragmentで、ScoreViewModelとScoreViewModelFactoryのクラス変数を作成します。
private lateinit var viewModel: ScoreViewModel
private lateinit var viewModelFactory: ScoreViewModelFactoryScoreFragmentのonCreateView()内で、binding変数を初期化した後、viewModelFactoryを初期化します。ScoreViewModelFactoryを使用します。引数バンドルから最終スコアをScoreViewModelFactory()のコンストラクタ パラメータとして渡します。
viewModelFactory = ScoreViewModelFactory(ScoreFragmentArgs.fromBundle(arguments!!).score)onCreateView(で、viewModelFactoryを初期化した後、viewModelオブジェクトを初期化します。ViewModelProviders.of()メソッドを呼び出し、関連付けられたスコア フラグメント コンテキストとviewModelFactoryを渡します。これにより、viewModelFactoryクラス.で定義されたファクトリ メソッドを使用してScoreViewModelオブジェクトが作成されます。
viewModel = ViewModelProviders.of(this, viewModelFactory)
.get(ScoreViewModel::class.java)onCreateView()メソッドで、viewModelを初期化した後、scoreTextビューのテキストをScoreViewModelで定義された最終スコアに設定します。
binding.scoreText.text = viewModel.score.toString()- アプリを実行して、ゲームをプレイします。単語をいくつかまたはすべて確認し、[End Game] をタップします。スコア フラグメントに最終スコアが表示されるようになりました。

- 省略可:
ScoreViewModelでフィルタリングして、Logcat のScoreViewModelログを確認します。スコア値が表示されます。
2019-02-07 10:50:18.328 com.example.android.guesstheword I/ScoreViewModel: Final score is 15
このタスクでは、ViewModel を使用するために ScoreFragment を実装しました。また、ViewModelFactory インターフェースを使用して ViewModel のパラメータ化されたコンストラクタを作成する方法も学びました。
これで、アプリのアーキテクチャを変更して、Android アーキテクチャ コンポーネントの 1 つである ViewModel を使用するようにした。アプリのライフサイクルに関する問題が解決され、ゲームのデータが構成変更後も引き継がれるようになりました。また、ViewModelFactory インターフェースを使用して ViewModel を作成するためのパラメータ化されたコンストラクタを作成する方法も学びました。
Android Studio プロジェクト: GuessTheWord
- Android のアプリ アーキテクチャ ガイドラインでは、異なる役割を持つクラスに分割することを推奨しています。
- UI コントローラは、
ActivityやFragmentなどの UI ベースのクラスです。UI コントローラには、UI やオペレーティング システムとのやり取りを処理するロジックのみを含めるべきです。UI に表示するデータを含めるべきではありません。そのデータをViewModelに入れます。 ViewModelクラスは、UI 関連のデータを保存し管理します。ViewModelクラスを使用すると、画面の回転などの構成変更後もデータを維持することができます。ViewModelは、推奨される Android アーキテクチャ コンポーネントの 1 つです。ViewModelProvider.Factoryは、ViewModelオブジェクトの作成に使用できるインターフェースです。
次の表は、UI コントローラと、それらのデータを保持する ViewModel インスタンスを比較したものです。
UI コントローラ | ViewModel |
UI コントローラの例としては、この Codelab で作成した |
|
UI に表示するデータは含まれていません。 | UI コントローラが UI に表示するデータが含まれます。 |
データを表示するコードと、クリック リスナーなどのユーザー イベント コードが含まれています。 | データ処理のコードが含まれています。 |
構成が変更されるたびに破棄され、再作成されます。 | 関連付けられた UI コントローラが完全に消滅したとき(アクティビティの場合はアクティビティが終了したとき、フラグメントの場合はフラグメントがデタッチされたとき)にのみ破棄されます。 |
ビューが含まれています。 | アクティビティ、フラグメント、ビューへの参照を含めることはできない。これらは構成の変更後も存続しないが、 |
関連する | 関連する UI コントローラへの参照が含まれていません。 |
Udacity コース:
Android デベロッパー ドキュメント:
- ViewModel の概要
- ライフサイクル対応コンポーネントによるライフサイクルへの対応
- アプリ アーキテクチャ ガイド
ViewModelProviderViewModelProvider.Factory
その他:
- MVVM(モデル-ビュー-ビューモデル)アーキテクチャ パターン。
- 関心の分離(SoC)の設計原則
- ファクトリー メソッド パターン
このセクションでは、インストラクター主導のコースの一環として、この Codelab に取り組んでいる生徒向けに考えられる宿題をいくつか示します。インストラクターは、以下のようなことを行えます。
- 必要に応じて宿題を与える
- 宿題の提出方法を生徒に伝える
- 宿題を採点する
インストラクターは、これらの提案を必要なだけ使用し、必要に応じて他の宿題も自由に与えることができます。
この Codelab に独力で取り組む場合は、これらの宿題を自由に使用して知識をテストしてください。
以下の質問に回答してください
問題 1
デバイス設定の変更時にデータが失われないようにするには、どのクラスにアプリデータを保存する必要がありますか。
ViewModelLiveDataFragmentActivity
問題 2
ViewModel に、フラグメント、アクティビティ、ビューへの参照を含めることはできない。正誤問題
- 正しい
- 誤り
問題 3
ViewModel はどのようなときに破棄されますか。
- デバイスの画面の向きの変更中に、関連する UI コントローラが破棄され、再作成されたとき。
- 画面の向きを変更しているとき。
- 関連する UI コントローラが終了したとき(アクティビティの場合)、またはデタッチされたとき(フラグメントの場合)。
- ユーザーが [戻る] ボタンを押したとき。
問題 4
ViewModelFactory インターフェースの用途は何ですか。
ViewModelオブジェクトをインスタンス化する。- 画面の向きの変更中にデータを保持する。
- 画面に表示されているデータを更新する。
- アプリデータが変更されたときに通知を受け取る。
次のレッスンに進む:
このコースの他の Codelab へのリンクについては、Android Kotlin の基礎の Codelab のランディング ページをご覧ください。




