この Codelab は、Android Kotlin の基礎コースの一部です。このコースを最大限に活用するには、Codelab を順番に進めることをおすすめします。コースのすべての Codelab は、Android Kotlin の基礎の Codelab のランディング ページに一覧表示されています。
はじめに
このレッスンの前の Codelab では、GuessTheWord アプリのコードを改善しました。アプリは ViewModel オブジェクトを使用するようになったため、画面の回転やキーボードの利用可否の変更などのデバイス設定の変更後もアプリデータが引き継がれます。また、監視可能な LiveData を追加したため、監視対象のデータが変更されるとビューに自動的に通知されます。
この Codelab では、GuessTheWord アプリを引き続き使用します。レイアウト内のビューが ViewModel オブジェクトと直接通信できるように、アプリ内の ViewModel クラスにビューをバインドします。(これまでのアプリでは、ビューはアプリのフラグメントを介して ViewModel と間接的に通信していました)。データ バインディングを ViewModel オブジェクトと統合したら、アプリのフラグメントでクリック ハンドラは不要になるため、削除します。
また、GuessTheWord アプリを変更して、LiveData をデータ バインディング ソースとして使用し、LiveData オブザーバー メソッドを使用せずにデータの変更を UI に通知します。
前提となる知識
- Kotlin で基本的な Android アプリを作成する方法。
- アクティビティとフラグメントのライフサイクルの仕組み。
- アプリで
ViewModelオブジェクトを使用する方法。 ViewModelでLiveDataを使用してデータを保存する方法。LiveDataデータの変更を監視するオブザーバー メソッドを追加する方法。
学習内容
- データ バインディング ライブラリの要素の使用方法。
ViewModelをデータ バインディングと統合する方法。LiveDataをデータ バインディングと統合する方法。- リスナー バインディングを使用して、フラグメント内のクリック リスナーを置き換える方法。
- データ バインディング式に文字列の書式設定を追加する方法。
演習内容
- GuessTheWord レイアウトのビューは、UI コントローラ(フラグメント)を使用して情報を中継することで、
ViewModelオブジェクトと間接的に通信します。この Codelab では、アプリのビューをViewModelオブジェクトにバインドして、ビューがViewModelオブジェクトと直接通信できるようにします。 - アプリを変更して、データ バインディングのソースとして
LiveDataを使用します。この変更により、LiveDataオブジェクトがデータの変更を UI に通知するようになり、LiveDataオブザーバー メソッドは不要になります。
レッスン 5 の Codelab では、スターター コードから GuessTheWord アプリを開発します。GuessTheWord は、2 人のプレーヤーが最高スコアを目指して協力する、2 プレーヤー ジェスチャー ゲームです。
最初のプレーヤーはアプリの単語を見て、2 番目のプレーヤーに単語を見せないようにしながら、各単語を順番に演じます。2 人目のプレーヤーが単語を当てようとします。
ゲームをプレイするには、最初のプレーヤーがデバイスでアプリを開き、下のスクリーンショットに示すように、「ギター」などの単語を表示します。
最初のプレーヤーは、単語自体を言わないように注意しながら、単語を演じます。
- 2 人目のプレーヤーが単語を正しく当てると、1 人目のプレーヤーが [Got It] ボタンを押します。これにより、カウントが 1 つ増え、次の単語が表示されます。
- 2 人目のプレーヤーが単語を当てられない場合、1 人目のプレーヤーが [スキップ] ボタンを押します。これにより、カウントが 1 減り、次の単語にスキップします。
- ゲームを終了するには、[End Game] ボタンを押します。(この機能は、シリーズの最初の Codelab のスターター コードには含まれていません)。
この Codelab では、ViewModel オブジェクトの LiveData とデータ バインディングを統合して、GuessTheWord アプリを改善します。これにより、レイアウト内のビューと ViewModel オブジェクト間の通信が自動化され、LiveData を使用してコードを簡素化できます。
タイトル画面 |
ゲーム画面 |
スコア画面 |
このタスクでは、この Codelab のスターター コードを見つけて実行します。前の Codelab で作成した GuessTheWord アプリをスターター コードとして使用することも、スターター アプリをダウンロードすることもできます。
- (省略可)前の Codelab のコードを使用しない場合は、この Codelab のスターター コードをダウンロードします。コードの ZIP ファイルを展開し、プロジェクトを Android Studio で開きます。
- アプリを実行して、ゲームをプレイします。
- [Got It] ボタンをタップすると、次の単語が表示され、スコアが 1 増えます。[Skip] ボタンをタップすると、次の単語が表示され、スコアが 1 減ります。[ゲームを終了] ボタンをクリックすると、ゲームが終了します。
- すべての単語を切り替えると、アプリが自動的にスコア画面に移動します。
前の Codelab では、GuessTheWord アプリのビューにアクセスする型安全な方法としてデータ バインディングを使用しました。しかし、データ バインディングの真の力は、その名前が示すように、アプリのビュー オブジェクトにデータを直接バインドすることにあります。
現在のアプリのアーキテクチャ
アプリでは、ビューは XML レイアウトで定義され、これらのビューのデータは ViewModel オブジェクトに保持されます。各ビューとその対応する ViewModel の間には UI コントローラがあり、それらの間のリレーとして機能します。

次に例を示します。
- [Got It] ボタンは、
game_fragment.xmlレイアウト ファイルでButtonビューとして定義されています。 - ユーザーが [OK] ボタンをタップすると、
GameFragmentフラグメントのクリック リスナーがGameViewModelの対応するクリック リスナーを呼び出します。 - スコアは
GameViewModelで更新されます。
Button ビューと GameViewModel は直接通信しません。GameFragment にあるクリック リスナーが必要です。
データ バインディングに渡される ViewModel
レイアウト内のビューが UI コントローラを仲介として使用せずに、ViewModel オブジェクト内のデータと直接通信できれば、よりシンプルになります。

ViewModel オブジェクトには、GuessTheWord アプリのすべての UI データが保持されます。ViewModel オブジェクトをデータ バインディングに渡すことで、ビューと ViewModel オブジェクト間の通信の一部を自動化できます。
このタスクでは、GameViewModel クラスと ScoreViewModel クラスを対応する XML レイアウトに関連付けます。また、クリック イベントを処理するリスナー バインディングも設定します。
ステップ 1: GameViewModel のデータ バインディングを追加する
このステップでは、GameViewModel を対応するレイアウト ファイル game_fragment.xml に関連付けます。
game_fragment.xmlファイルに、GameViewModel型のデータ バインディング変数を追加します。Android Studio でエラーが発生した場合は、プロジェクトをクリーンして再ビルドします。
<layout ...>
<data>
<variable
name="gameViewModel"
type="com.example.android.guesstheword.screens.game.GameViewModel" />
</data>
<androidx.constraintlayout...
GameFragmentファイルで、GameViewModelをデータ バインディングに渡します。
これを行うには、前の手順で宣言したbinding.gameViewModel変数にviewModelを割り当てます。このコードをonCreateView()内のviewModelの初期化後に配置します。Android Studio でエラーが発生した場合は、プロジェクトをクリーンして再ビルドします。
// Set the viewmodel for databinding - this allows the bound layout access
// to all the data in the ViewModel
binding.gameViewModel = viewModelステップ 2: イベント処理にリスナー バインディングを使用する
リスナー バインディングは、onClick()、onZoomIn()、onZoomOut() などのイベントがトリガーされたときに実行されるバインディング式です。リスナー バインディングはラムダ式として記述されます。
データ バインディングではリスナーを作成してビューに設定します。リスナーがリッスンしているイベントが発生すると、リスナーはラムダ式を評価します。リスナー バインディングは、Android Gradle プラグイン バージョン 2.0 以降で機能します。詳しくは、レイアウトとバインディング式をご覧ください。
このステップでは、GameFragment のクリック リスナーを game_fragment.xml ファイルのリスナー バインディングに置き換えます。
game_fragment.xmlで、skip_buttonにonClick属性を追加します。バインディング式を定義し、GameViewModelでonSkip()メソッドを呼び出します。このバインディング式はリスナー バインディングと呼ばれます。
<Button
android:id="@+id/skip_button"
...
android:onClick="@{() -> gameViewModel.onSkip()}"
... />- 同様に、
correct_buttonのクリック イベントをGameViewModelのonCorrect()メソッドにバインドします。
<Button
android:id="@+id/correct_button"
...
android:onClick="@{() -> gameViewModel.onCorrect()}"
... />end_game_buttonのクリック イベントをGameViewModelのonGameFinish()メソッドにバインドします。
<Button
android:id="@+id/end_game_button"
...
android:onClick="@{() -> gameViewModel.onGameFinish()}"
... />GameFragmentで、クリック リスナーを設定するステートメントを削除し、クリック リスナーが呼び出す関数を削除します。不要になったためです。
削除するコード:
binding.correctButton.setOnClickListener { onCorrect() }
binding.skipButton.setOnClickListener { onSkip() }
binding.endGameButton.setOnClickListener { onEndGame() }
/** Methods for buttons presses **/
private fun onSkip() {
viewModel.onSkip()
}
private fun onCorrect() {
viewModel.onCorrect()
}
private fun onEndGame() {
gameFinished()
}ステップ 3: ScoreViewModel のデータ バインディングを追加する
このステップでは、ScoreViewModel を対応するレイアウト ファイル score_fragment.xml に関連付けます。
score_fragment.xmlファイルに、ScoreViewModel型のバインディング変数を追加します。このステップは、上記のGameViewModelのステップと似ています。
<layout ...>
<data>
<variable
name="scoreViewModel"
type="com.example.android.guesstheword.screens.score.ScoreViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayoutscore_fragment.xmlで、play_again_buttonにonClick属性を追加します。リスナー バインディングを定義し、ScoreViewModelでonPlayAgain()メソッドを呼び出します。
<Button
android:id="@+id/play_again_button"
...
android:onClick="@{() -> scoreViewModel.onPlayAgain()}"
... />ScoreFragmentのonCreateView()内で、viewModelを初期化します。次に、binding.scoreViewModelバインディング変数を初期化します。
viewModel = ...
binding.scoreViewModel = viewModelScoreFragmentで、playAgainButtonのクリック リスナーを設定するコードを削除します。Android Studio にエラーが表示された場合は、プロジェクトをクリーンアップして再ビルドします。
削除するコード:
binding.playAgainButton.setOnClickListener { viewModel.onPlayAgain() }- アプリを実行します。アプリは以前と同じように動作しますが、ボタンビューは
ViewModelオブジェクトと直接通信するようになります。ビューはScoreFragmentのボタン クリック ハンドラを介して通信しなくなりました。
データ バインディングのエラー メッセージのトラブルシューティング
アプリでデータ バインディングを使用すると、コンパイル プロセスでデータ バインディングに使用される中間クラスが生成されます。アプリには、アプリをコンパイルしようとするまで Android Studio が検出しないエラーが含まれている場合があります。そのため、コードの記述中に警告や赤いコードが表示されないことがあります。しかし、コンパイル時に、生成された中間クラスから不可解なエラーが発生します。
わかりにくいエラー メッセージが表示された場合:
- Android Studio の [Build] ペインに表示されるメッセージをよく確認してください。
databindingで終わるロケーションが表示された場合は、データ バインディングにエラーがあります。 - レイアウト XML ファイルで、データ バインディングを使用する
onClick属性のエラーを確認します。ラムダ式が呼び出す関数を探し、その関数が存在することを確認します。 - XML の
<data>セクションで、データ バインディング変数のスペルを確認します。
たとえば、次の属性値では、関数名 onCorrect() のスペルが間違っています。
android:onClick="@{() -> gameViewModel.onCorrectx()}"
また、XML ファイルの <data> セクションで gameViewModel のスペルが間違っていることにも注意してください。
<data>
<variable
name="gameViewModelx"
type="com.example.android.guesstheword.screens.game.GameViewModel" />
</data>Android Studio では、アプリをコンパイルするまでこのようなエラーは検出されません。コンパイラは、次のようなエラー メッセージを表示します。
error: cannot find symbol import com.example.android.guesstheword.databinding.GameFragmentBindingImpl" symbol: class GameFragmentBindingImpl location: package com.example.android.guesstheword.databinding
データ バインディングは、ViewModel オブジェクトで使用される LiveData とうまく連携します。ViewModel オブジェクトにデータ バインディングを追加したので、LiveData を組み込む準備が整いました。
このタスクでは、LiveData オブザーバー メソッドを使用せずに、LiveData をデータ バインディング ソースとして使用してデータの変更を UI に通知するように、GuessTheWord アプリを変更します。
ステップ 1: game_fragment.xml ファイルに単語 LiveData を追加する
このステップでは、現在の単語のテキストビューを ViewModel の LiveData オブジェクトに直接バインドします。
game_fragment.xmlで、word_textテキストビューにandroid:text属性を追加します。
バインディング変数 gameViewModel を使用して、GameViewModel から LiveData オブジェクト word に設定します。
<TextView
android:id="@+id/word_text"
...
android:text="@{gameViewModel.word}"
... />word.value を使用する必要はありません。代わりに、実際の LiveData オブジェクトを使用できます。LiveData オブジェクトは、word の現在の値を表示します。word の値が null の場合、LiveData オブジェクトには空の文字列が表示されます。
GameFragmentまたはonCreateView()で、gameViewModelを初期化した後、現在のアクティビティをbinding変数のライフサイクル所有者として設定します。これにより、上記のLiveDataオブジェクトのスコープが定義され、オブジェクトがレイアウト内のビューgame_fragment.xmlを自動的に更新できるようになります。
binding.gameViewModel = ...
// Specify the current activity as the lifecycle owner of the binding.
// This is used so that the binding can observe LiveData updates
binding.lifecycleOwner = thisGameFragmentで、LiveDatawordのオブザーバーを削除します。
削除するコード:
/** Setting up LiveData observation relationship **/
viewModel.word.observe(this, Observer { newWord ->
binding.wordText.text = newWord
})- アプリを実行して、ゲームをプレイします。これで、UI コントローラのオブザーバー メソッドなしで現在の単語が更新されるようになりました。
ステップ 2: score_fragment.xml ファイルにスコア LiveData を追加する
このステップでは、LiveData score をスコア フラグメントのスコア テキストビューにバインドします。
score_fragment.xmlで、スコア テキストビューにandroid:text属性を追加します。scoreViewModel.scoreをtext属性に割り当てます。scoreは整数であるため、String.valueOf()を使用して文字列に変換します。
<TextView
android:id="@+id/score_text"
...
android:text="@{String.valueOf(scoreViewModel.score)}"
... />ScoreFragmentで、scoreViewModelを初期化した後、現在の Activity をbinding変数のライフサイクル オーナーとして設定します。
binding.scoreViewModel = ...
// Specify the current activity as the lifecycle owner of the binding.
// This is used so that the binding can observe LiveData updates
binding.lifecycleOwner = thisScoreFragmentで、scoreオブジェクトのオブザーバーを削除します。
削除するコード:
// Add observer for score
viewModel.score.observe(this, Observer { newScore ->
binding.scoreText.text = newScore.toString()
})- アプリを実行して、ゲームをプレイします。スコア フラグメントにオブザーバーがなくても、スコア フラグメントのスコアが正しく表示されていることに注目してください。
ステップ 3: データ バインディングで文字列の書式設定を追加する
レイアウトでは、データ バインディングとともに文字列の書式設定を追加できます。このタスクでは、現在の単語をフォーマットして、その単語を引用符で囲みます。また、次の図に示すように、スコア文字列の先頭に「Current Score」という接頭辞を付加するようにフォーマットします。

string.xmlで、wordテキストビューとscoreテキストビューの書式設定に使用する次の文字列を追加します。%sと%dは、現在の単語と現在のスコアのプレースホルダです。
<string name="quote_format">\"%s\"</string>
<string name="score_format">Current Score: %d</string>game_fragment.xmlで、word_textテキストビューのtext属性を更新して、quote_format文字列リソースを使用するようにします。gameViewModel.wordを渡します。これにより、現在の単語が書式設定文字列の引数として渡されます。
<TextView
android:id="@+id/word_text"
...
android:text="@{@string/quote_format(gameViewModel.word)}"
... />word_textと同様にscoreテキスト ビューの書式を設定します。game_fragment.xmlで、score_textテキストビューにtext属性を追加します。文字列リソースscore_formatを使用します。これは、%dプレースホルダで表される数値引数を 1 つ取ります。LiveDataオブジェクトscoreをこの書式設定文字列の引数として渡します。
<TextView
android:id="@+id/score_text"
...
android:text="@{@string/score_format(gameViewModel.score)}"
... />GameFragmentクラスのonCreateView()メソッド内で、scoreオブザーバー コードを削除します。
削除するコード:
viewModel.score.observe(this, Observer { newScore ->
binding.scoreText.text = newScore.toString()
})- アプリをクリーン、再ビルド、実行してから、ゲームをプレイします。現在の単語とスコアがゲーム画面でフォーマットされています。

これで、アプリで LiveData と ViewModel をデータ バインディングと統合しました。これにより、レイアウト内のビューは、フラグメントのクリック ハンドラを使用することなく、ViewModel と直接通信できます。また、LiveData オブザーバー メソッドを使用せずに、LiveData オブジェクトをデータ バインディング ソースとして使用して、データの変更について UI に自動的に通知しました。
Android Studio プロジェクト: GuessTheWord
- データ バインディング ライブラリは、
ViewModelやLiveDataなどの Android アーキテクチャ コンポーネントとシームレスに連携します。 - アプリのレイアウトはアーキテクチャ コンポーネントのデータにバインドできます。この機能は、UI コントローラのライフサイクルを管理し、データの変更について通知するのに役立っています。
ViewModel のデータ バインディング
- データ バインディングを使用すると、
ViewModelをレイアウトに関連付けることができます。 ViewModelオブジェクトは UI データを保持します。ViewModelオブジェクトをデータ バインディングに渡すことで、ビューとViewModelオブジェクト間の通信の一部を自動化できます。
ViewModel をレイアウトに関連付ける方法は次のとおりです。
- レイアウト ファイルに、
ViewModel型のデータ バインディング変数を追加します。
<data>
<variable
name="gameViewModel"
type="com.example.android.guesstheword.screens.game.GameViewModel" />
</data>GameFragmentファイルで、GameViewModelをデータ バインディングに渡します。
binding.gameViewModel = viewModelリスナー バインディング
- リスナー バインディングは、
onClick()などのクリック イベントがトリガーされたときに実行されるレイアウト内のバインディング式です。 - リスナー バインディングはラムダ式として記述される。
- リスナー バインディングを使用して、UI コントローラのクリック リスナーをレイアウト ファイルのリスナー バインディングに置き換えます。
- データ バインディングではリスナーを作成してビューに設定します。
android:onClick="@{() -> gameViewModel.onSkip()}"データ バインディングに LiveData を追加する
LiveDataオブジェクトをデータ バインディング ソースとして使用することで、データの変更について UI に自動的に通知できます。- ビューを
ViewModelのLiveDataオブジェクトに直接バインドできます。ViewModelのLiveDataが変更されると、UI コントローラのオブザーバー メソッドを使用しなくても、レイアウト内のビューを自動的に更新できます。
android:text="@{gameViewModel.word}"LiveDataデータ バインディングを機能させるには、現在の Activity(UI コントローラ)を UI コントローラのbinding変数のライフサイクル オーナーとして設定します。
binding.lifecycleOwner = thisデータ バインディングによる文字列の書式設定
- データ バインディングを使用すると、文字列リソースを文字列用の
%sや整数用の%dなどのプレースホルダでフォーマットできます。 - ビューの
text属性を更新するには、LiveDataオブジェクトを引数として書式設定文字列に渡します。
android:text="@{@string/quote_format(gameViewModel.word)}"Udacity コース:
Android デベロッパー ドキュメント:
このセクションでは、インストラクター主導のコースの一環として、この Codelab に取り組んでいる生徒向けに考えられる宿題をいくつか示します。インストラクターは、以下のようなことを行えます。
- 必要に応じて宿題を与える
- 宿題の提出方法を生徒に伝える
- 宿題を採点する
インストラクターは、これらの提案を必要なだけ使用し、必要に応じて他の宿題も自由に与えることができます。
この Codelab に独力で取り組む場合は、これらの宿題を自由に使用して知識をテストしてください。
以下の質問に回答してください
問題 1
リスナー バインディングの説明として正しくないものは次のうちどれですか。
- リスナー バインディングは、イベントの発生時に実行されるバインディング式である。
- リスナー バインディングは、Android Gradle プラグインのすべてのバージョンで機能する。
- リスナー バインディングはラムダ式として記述される。
- リスナー バインディングはメソッド参照に似ているが、任意のデータ バインディング式を実行できる。
問題 2
アプリに文字列リソース <string name="generic_name">Hello %s</string> が含まれているとします。
データ バインディング式を使用して文字列をフォーマットする構文として、正しいものは次のうちどれですか。
android:text= "@{@string/generic_name(user.name)}"android:text= "@{string/generic_name(user.name)}"android:text= "@{@generic_name(user.name)}"android:text= "@{@string/generic_name,user.name}"
問題 3
リスナー バインディング式は、どのようなときに評価され実行されますか。
LiveDataで保持されているデータが変更されたとき- 設定の変更によってアクティビティが再作成されたとき
onClick()などのイベントが発生したとき- アクティビティがバックグラウンドに移動したとき
次のレッスンに進む:
このコースの他の Codelab へのリンクについては、Android Kotlin の基礎の Codelab のランディング ページをご覧ください。


