学ぶこと
- Data Binding Library の要素の使用方法
ViewModel
をデータバインディングと統合する方法LiveData
をデータバインディングと統合する方法- listener bindings を使用して fragment 内のクリックリスナーを置き換える方法
- データバインディング式に文字列フォーマットを追加する方法
すること
- GuessTheWord レイアウトの View は、UI controller(fragment) を使用して情報を中継し、
ViewModel
オブジェクトと間接的に通信します。このコードラボでは、アプリの View をViewModel
オブジェクトにバインドして、View がViewModel
オブジェクトと直接通信するようにします。 LiveData
をデータバインディングソースとして使用するようにアプリを変更します。この変更後、LiveData
オブジェクトはデータの変更について UI に通知し、LiveData
オブザーバーメソッドは不要になります。
アプリの概要
このコードラボでは、ViewModel
オブジェクトの LiveData
とデータバインディングを統合することにより、GuessTheWord アプリを改善します。これにより、レイアウト内の View と ViewModel
オブジェクト間の通信が自動化され、LiveData
を使用してコードを簡素化できます。
![]() |
![]() |
![]() |
---|---|---|
ViewModel データバインディングを追加する
以前のコードラボでは、GuessTheWord アプリの View にアクセスするためのタイプセーフな方法としてデータバインディングを使用していました。ただし、データバインディングの真の力は、データをアプリの View オブジェクトに直接バインドすることにあります。
現在の App Architecture
アプリでは、View は XML レイアウトで定義され、それらの View のデータは ViewModel
オブジェクトに保持されます。各 View とそれに対応する ViewModel
の間には、View 間のリレーとして機能する UI controller があります。
example:
- Got It ボタンは、
game_fragment.xml
レイアウトファイルのButton
view として定義されています。 - ユーザーが Got It ボタンをタップすると、
GameFragment
のクリックリスナーがGameViewModel
の対応するクリックリスナーを呼び出します。 - スコアは
GameViewModel
で更新されます。
Button
view と GameViewModel
は直接通信しません。これらには、GameFragment
にあるクリックリスナーが必要です。
data binding に渡された ViewModel
レイアウト内の View が、中間として UI controller に依存せずに、ViewModel
オブジェクト内のデータと直接通信する場合はより簡単になります。
ViewModel
オブジェクトは、GuessTheWord アプリの全ての UI データを保持します。ViewModel
オブジェクトをデータバインディングに渡すことで、View と ViewModel
オブジェクト間の通信の一部を自動化できます。
このタスクでは、GameViewModel
クラスと、ScoreViewModel
クラスを対応する XML レイアウトに関連付けます。また、クリックイベントを処理するリスナーバインディングを設定します。
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
を割り当てます。viewModel
が初期化された後、このコードを onCreateView()
内に配置します。Android Studio でエラーが発生した場合は、プロジェクトをクリーンアップして再構築してください。
// Set the viewmodel for databinding - this allows the bound layout access // to all the data in the ViewModel binding.gameViewModel = viewModel
イベント処理にリスナーバインディングを使用する
Listener bindings は、onClick()
、onZoomIn()
、onZoomOut()
などのイベントがトリガーされた時に実行されるバインディング式です。リスナーバインディングはラムダ式として記述されます。
データバインディングはリスナーを作成し、View にリスナーを設定します。リッスンされたイベントが発生すると、リスナーはラムダ式を評価します。リスナーバインディングは、Android Gradle Plugin version 2.0 以降で機能します。詳細については、Layout and binding expressions 式を参照してください。
このステップでは、GameFragment
のクリックリスナーを game_fragment.xml
ファイルのリスナーバインディングに置き換えます。
game_fragment.xml
で、onClick
attributes をskip_button
に追加します。バインディング式を定義し、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() }
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.ConstraintLayout
score_fragment.xml
で、onClick
attribute をplay_again_button
に追加します。リスナーバインディングを定義し、ScoreViewModel
で、onPlayAgain()
メソッドを呼び出します。
<Button android:id="@+id/play_again_button" ... android:onClick="@{() -> scoreViewModel.onPlayAgain()}" ... />
ScoreFragment
のonCreateView()
内で、viewModel
を初期化します。次に、binding.scoreViewModel
バインディング変数を初期化します。
viewModel = ... binding.scoreViewModel = viewModel
ScoreFragment
で、playAgainButton
のクリックリスナーを設定するコードを削除します。Android Studio にエラーが表示された場合は、プロジェクトをクリーンアップして再構築します。
削除するコード:
binding.playAgainButton.setOnClickListener { viewModel.onPlayAgain() }
- アプリを実行します。アプリは以前と同じように機能するはずですが、button view は
ViewModel
オブジェクトと直接通信するようになりました。View は、ScoreFragment
のボタンクリックハンドラーを介して通信しなくなりました。
Troubleshooting data-binding error messages
アプリがデータバインディングを使用する場合、コンパイルプロセスは、データバインディングに使用される中間クラスを生成します。アプリには、アプリをコンパイルしようとするまで Android Studio が検出しなエラーが含まれている可能性があるため、コードの記述中に警告や赤い Error code は表示されません。ただし、コンパイル時に、生成された中間クラスから発生する不可解なエラーが発生します。
不可解なエラーメッセージが表示された場合:
Android Studio の Build ペインを注意深くみてください。data binding で終わる場所が表示された場合は、
databinding
にエラーがあります。レイアウト XML ファイルで、データバインディングを使用する
onClick
attributes のエラーを確認します。ラムダ式が呼び出す関数を探し、それが存在することを確認します。
たとえば、次の 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
data binding に LiveData を追加する
データバインディングは、ViewModel
オブジェクトで使用される LiveData
でうまく機能します。ViewModel
オブジェクトにデータバインディングを追加したので、LiveData
を組み込む準備ができました。
このタスクでは、GuessTheWord アプリを変更して、LiveData
をデータバインディングソースとして使用し、LiveData
オブザーバーメソッドを使用せずに、データの変更について UI に通知します。
word LiveData を game_fragment.xml ファイルに追加します
このステップでは、現在の word text view を ViewModel
の LiveData
オブジェクトに直接バインドします。
game_fragment.xml
で、android:text
attribute をword_text
text view に追加します。
バインディング変数 gameViewModel
を使用して、GameViewModel
からの word
である LiveData
オブジェクトに設定します。
<TextView android:id="@+id/word_text" ... android:text="@{gameViewModel.word}" ... />
word.value
を使用する必要がないことに注意してください。代わりに、実際の LiveData
オブジェクトを使用できます。LiveData
オブジェクトは、word
の現在の値を表示します。word
の値が null
の場合、LiveData
オブジェクトは空の文字列を表示します。
GameFragment
のonCreateView()
で、gameViewModel
を初期化した後、Fragment view をバインディング変数の Lifecycle Owner として設定します。これにより、上記のLiveData
オブジェクトスコープが定義され、オブジェクトがgame_fragment.xml
の view を自動的に更新出来るようになります。
binding.gameViewModel = ... // Specify the fragment view as the lifecycle owner of the binding. // This is used so that the binding can observe LiveData updates binding.lifecycleOwner = viewLifecycleOwner
GameFragment
で、LiveData
word
のオブザーバを削除します。
削除するコード:
/** Setting up LiveData observation relationship **/ viewModel.word.observe(viewLifecycleOwner, Observer { newWord -> binding.wordText.text = newWord })
- アプリを実行してゲームをプレイします。現在、UI controller のオブザーバーメソッドなしで現在の word が更新されています。
score LiveData を score_fragment.xml
ファイルに追加する
このステップでは、LiveData
score
を score fragment の score text view にバインドします。
score_fragment.xml
で、android:text
attribute を score text view に追加します。scoreViewModel.score
をtext
attribute に割り当てます。score
は整数であるため、String.valueOf()
を使用して文字列に変換します。
<TextView android:id="@+id/score_text" ... android:text="@{String.valueOf(scoreViewModel.score)}" ... />
ScoreFragment
で、scoreViewModel
を初期化した後、現在の Activity をbinding
変数の lifecycle owner として設定します。
binding.scoreViewModel = ... // Specify the fragment view as the lifecycle owner of the binding. // This is used so that the binding can observe LiveData updates binding.lifecycleOwner = viewLifecycleOwner
ScoreFragment
で、score
オブジェクトのオブザーバーを削除します。
削除するコード:
// Add observer for score viewModel.score.observe(viewLifecycleOwner, Observer { newScore -> binding.scoreText.text = newScore.toString() })
- アプリを実行してプレイします。score fragment にオブザーバーがなくても、score fragment のスコアが正しく表示されていることに注目してください。
Data binding を使用した文字列フォーマットの追加
レイアウトでは、Data binding とともに string formatting を追加できます。このタスクでは、現在の単語をフォーマットして、その周りに引用符を追加します。次の図に示すように、Current Score をフォーマットして、現在のスコアの前に付けます。
string.xml
に、次の文字列を追加します。これらの文字列を使用して、word
とscore
のフォーマットを行います。%s
と%d
は、現在の word と score のプレースホルダーです。
<string name="quote_format">\"%s\"</string> <string name="score_format">Current Score: %d</string>
game_fragment.xml
で、quote_format
文字列リソースを使用するようにword_text
テキストビューのtext
attribute を更新します。gameViewModel.word
を渡します。これにより、現在の word が引数としてフォーマット文字列に渡されます。
<TextView android:id="@+id/word_text" ... android:text="@{@string/quote_format(gameViewModel.word)}" ... />
word_text
と同様にscore
テキストビューをフォーマットします。game_fragment.xml
で、text
attribute をscore_text
テキストビューに追加します。%d
プレースホルダーで表される1つの数値引数を取る文字列リソースscore_format
を使用します。この formatting string の引数として、LiveData
オブジェクトscore
を渡します。
<TextView android:id="@+id/score_text" ... android:text="@{@string/score_format(gameViewModel.score)}" ... />
GameFragment
クラスのonCreateView()
メソッド内で、score
オブザーバーコードを削除します。
削除するコード:
viewModel.score.observe(viewLifecycleOwner, Observer { newScore ->
binding.scoreText.text = newScore.toString()
})
- アプリを Clean、rebuild してからゲームをプレイします。current の word と score がゲーム画面でフォーマットされていることに注目してください。
まとめ
- Data binding ライブラリは、
ViewModel
やLiveData
などのAndroid
アーキテクチャコンポーネントとシームレスに連携します。 - アプリのレイアウトは、アーキテクチャコンポーネントのデータにバインドできます。これにより、UI controller's のライフサイクルを管理し、データの変更について通知することができます。s
ViewModel データバインディング
- data binding を使用して、
ViewModel
をレイアウトに関連付けることができます。 ViewModel
オブジェクトは UI データを保持します。ViewModel
オブジェクトをデータバインディングに渡すことで、View とViewModel
オブジェクト間の通信の一部を自動化できます。
ViewModel
をレイアウトに関連付ける方法:
- レイアウトファイルで、
ViewModel
タイプのデータバインディング変数を追加します。
<data> <variable name="gameViewModel" type="com.example.android.guesstheword.screens.game.GameViewModel" /> </data>
GameFragment
ファイルで、GameViewModel
をデータバインディングに渡します。
binding.gameViewModel = viewModel
Listener bindings
- Listener bindings は、
onClick()
などのクリックイベントがトリガーされた時に実行されるレイアウト内のバインディング式です。 - Listener bindings はラムダ式として記述されます。
- リスナーバインディングを使用して、UI controllers のクリックリスナーをレイアウトファイルのリスナーバインディングに置き換えます。
- データバインディングはリスナーを作成し、View にリスナーを設定します。
android:onClick="@{() -> gameViewModel.onSkip()}"
Data binding への LiveData の追加
LiveData
オブジェクトを data-binding ソースとして使用して、データの変更について UI に自動的に通知できます。- View を
ViewModel
のLiveData
オブジェクトに直接バインドできます。ViewModel
のLiveData
が変更されると、UI controllers のオブザーバーメソッドなしで、レイアウトの View を自動的に更新できます。
android:text="@{gameViewModel.word}"
LiveData
data binding を機能させるには、現在の activity(UI controller) を UI controller のbinding
変数の lifecycle owner として設定します。
binding.lifecycleOwner = this
Data binding を使用した String formatting
- データバインディングを使用すると、文字列の場合は
%s
、整数の場合は%d
などのプレースホルダーを使用して文字列リソースをフォーマットできます。 - View の
text
attribute を更新するには、LiveData
オブジェクトを引数としてフォーマット文字列に渡します。
android:text="@{@string/quote_format(gameViewModel.word)}"