Android Kotlin基礎講座 05.3: ViewModelとLiveDataのデータバインディング

タスク:データバインディングにLiveDataを追加する

データバインディングはViewModelオブジェクトと共に使われるLiveDataと相性が良いです。ここまででViewModelオブジェクトにデータバインディングを追加してきました。LiveDataを組み込む準備は整っています。

このタスクでは、データの変化をUIに通知するためのデータバインディングソースとして、LiveDataを使用するようにGuessTheWordアプリを変更していきます。LiveDataオブザーバーメソッドは使わなくなります。

ステップ1:game_fragment.xmlファイルにword LiveDataを追加する

このステップでは、現在の単語用のテキストビューをViewModel内のLiveDataオブジェクトに直接結合させます。

  1. game_fragment.xmlのword_textテキストビューにandroid:text属性を追加してください。

それをバインディング変数であるgameViewModelを使って、LiveDataオブジェクトであるGameViewModelのwordにセットします。

<TextView
   android:id="@+id/word_text"
   ...
   android:text="@{gameViewModel.word}"
   ... />

word.valueはつかう必要がないことを覚えておいてください。代わりに、実際のLiveDataオブジェクトを使うことができます。LiveDataオブジェクトはwordの現在の値を表示します。もしwordの値がnullの場合、LiveDataオブジェクトは空のstringを表示します。

  1. GameFragmentのonCreateView()内、gameViewModelの初期化文のあとでbinding変数のライフサイクルオーナーとしてフラグメントビューを設定してください。これは上記のLiveDataオブジェクトのスコープを決め、game_fragment.xmlレイアウト内のビューを自動で更新できるようにします。
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
  1. GameFragment内のLiveDataであるword用のオブザーバーを削除してください。

削除するコード:

/** Setting up LiveData observation relationship **/
viewModel.word.observe(viewLifecycleOwner, Observer { newWord ->
   binding.wordText.text = newWord
})
  1. アプリを起動してゲームをプレイしてください。現在の単語がUI controllerのオブザーバーメソッド無しで更新されるようになっています。

ステップ2:score_fragment.xmlファイルにscore LiveDataを追加する

このステップでは、LiveDataであるscoreをscore_fragmentのscoreテキストビューに結合します。

  1. score_fragment.xmlのscoreテキストビューにandroid:text属性を追加してください。
    text属性にscoreViewModel.scoreを代入してください。scoreはinteger型なので、String.valueOf()を使ってstring1型に変換してください。
<TextView
   android:id="@+id/score_text"
   ...
   android:text="@{String.valueOf(scoreViewModel.score)}"
   ... />
  1. ScoreFragment内、scoreViewModelの初期化文の後で、binding変数のライフサイクルオーナーとして現在のアクティビティを設定してください。
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
  1. ScoreFragmentのscoreオブジェクト用のオブザーバーを削除してください。

削除するコード:

// Add observer for score
viewModel.score.observe(viewLifecycleOwner, Observer { newScore ->
   binding.scoreText.text = newScore.toString()
})
  1. アプリを起動してゲームをプレイしてください。スコアフラグメントの得点が正確に表示されていることを確認してください。オブザーバー無しで更新されています。

ステップ3:データバインディングを用いてstringフォーマットを追加する

レイアウトではデータバインディングと共にstringフォーマットを追加することができます。このタスクでは現在の単語の周りに引用符を追加するようにフォーマットしていきます。また得点用のstringも下の画像のようにCurrent Scoreという接頭辞を付けるようにフォーマットします。

  1. string.xmlに以下のstringを追加してください。これらはwordとscoreテキストビューをフォーマットするために使います。%sと%dは現在の単語と得点用のプレースホルダーです。
<string name="quote_format">\"%s\"</string>
<string name="score_format">Current Score: %d</string>
  1. game_fragment.xmlのword_textテキストビューのtext属性をquote_format stringリソースを使うように更新してください。gameViewModel.wordを渡してください。これは現在の単語を引数としてフォーマットされるstringに渡しています。
<TextView
   android:id="@+id/word_text"
   ...
   android:text="@{@string/quote_format(gameViewModel.word)}"
   ... />
  1. word_textと同様にscoreテキストビューをフォーマットしてください。game_fragment.xmlのscore_textテキストビューにtext属性を追加してください。score_format stringリソースを使ってください。これは%dプレースホルダーで置き換えられた部分に、引数として数値を取ります。LiveDataであるscoreオブジェクトを引数として渡してください。
<TextView
   android:id="@+id/score_text"
   ...
   android:text="@{@string/score_format(gameViewModel.score)}"
   ... />
  1. GameFragmentクラスのonCreateView()メソッド内のscoreオブザーバーコードを削除してください。

削除するコード:

viewModel.score.observe(viewLifecycleOwner, Observer { newScore ->
   binding.scoreText.text = newScore.toString()
})
  1. アプリを起動して、ゲームをプレイしてください。ゲーム画面の現在の単語と得点がフォーマットされていることを確認してください。

完成済みプロジェクト

お疲れさまでした。LiveDataとViewModelをデータバインディングと統合できました。これによりレイアウトのビューがフラグメントのクリックハンドラーを使うわずにViewModelと直接やり取りできるようになりました。またLiveDataオブザーバーメソッド無しでデータの変化をUIに自動で通知するためのデータバインディングソースとして、LiveDataオブジェクトを使いました。

完成済みプロジェクトは以下からダウンロードできます。

GuessTheWord