カテゴリー
Kotlin言語

Androidアプリ:LiveData + Data Binding でUIを自動更新(Kotlin編)

Android Developers > スタートガイド > Kotlin と Android をゼロから学ぶ > Unit3 – Architecture の内容を中心にまとめたものです(翻訳ではありません)


LiveData と Data Binding を組み合わせる

LiveData を使うとき、値の変化の検知に observe のかわりに Data Binding を使うこともできるの

  • データ更新を検知してUI表示を更新するのに ” LiveData + オブザーバ ” を使う方法もあります  ViewModel と「LiveData + オブザーバ」の使い方 
  • オブザーバを使う場合、データ更新は Kotlin コード内に記述された observe で検知され、UI表示の更新処理も observe 内に記述された処理によって行います
  • ” LiveData + Data Binding ” の場合、データ更新を LiveData にバインドされているUI部品レイアウトが検知し、レイアウト自身が自動的に表示を更新します

まとめますね

ポイント

  • LiveData + オブザーバ・・・UI の更新処理は Kotlin コードで行う( observe を使って記述する)
  • LiveData + Data Binding・・・UI の更新処理はレイアウトが行う(予めデータバインドをしておく)

View Binding と Data Binding の違い

View Binding で十分なときは、無理に Data Binding を使おうとしなくていいんです

  • View Binding でもコードにUI部品(View)をバインドできます。View Binding はコードの中で View により簡単にアクセスできるようにする機能です
  • View Binding は一方通行のバインディングです。UI部品(View)からコードにアクセスすることはできません
  • View Binding を有効にすると、アプリにあるXMLレイアウトファイルのそれぞれに対応するバインディングクラスが自動生成されます
  • バインディングクラスのインスタンスは、関連付けられたレイアウトにある ID を持ったすべての View への直接参照を持っています
  • View Binding を使うと、こんな感じになります
binding.textView.text = viewModel.sampleData

View Binding は片方向、Data Binding は双方向なんですね

  • Data binding は双方向のバインディングです
  • Data Binding では View(レイアウトファイル)からアプリのデータを参照することができます。View Binding ではできません
  • View Binding のかわりに、より高機能な Data Binding を使えばよさそうですが、Data Binding を利用するにはレイアウトファイル自体をデータバインディング用に変更したりする必要もあり、それなりに手間がかかります
  • 多くのアプリでは、Data Binding の利用で得られるメリットは View Binding を利用したときと変わりません。View Binding の方がシンプルに実装できますし、パフォーマンスも向上します
  • findViewById() で呼び出していた部分をバインディングで置き換えるような使い方がほとんどであれば、Data Binding ではなく、View Binding を使うのがおすすめです
  • Data Binding を使うメリットは、アクティビティやフラグメントにUIフレームワークコールが多数ある場合、これらを削除できる点です。メンテナンスが簡単になり、アプリの性能向上、メモリリーク や null pointer exception の防止などが期待できます

Data Binding を利用する手順

Data Binding を利用するときは、次のような作業が必要なの

うっ、ちょっと大変かも…

  1. モジュールレベルの build.gradle を開いて View Binding のかわりに Data Binding を設定します
  2. レイアウトファイル(XML)をデータバインディングレイアウトに書き換えます(変換します)
  3. レイアウトファイル(XML)にレイアウト変数を追加します
  4. Kotlin コード内で設定されている変数(この変数を LiveData 型にしておくとUIが自動更新されます)を使って、XMLレイアウト内で設定したレイアウト変数を Kotlinコード内で初期化します
  5. レイアウトファイル(XML)のUI部品(View)にバインディング式を使ってデータをバインドします。LiveData 型のデータにバインドされていればUI表示はデータに合わせて自動で更新されます

バインディング式の記述

  • バインディング式では、必要に応じて strings.xml のような他のリソースファイル(XML)を参照して利用します
  • リソースファイル中の記述に %s のようなプレースホルダーが使用されている場合、リソースパラメータとしてレイアウト変数を指定することで、コード中の変数の値を利用できます
  • 詳しい説明はこのページの最後の方にあります

Data Binding の設定(build.gradle)

最初に Android Studio で Gradle の設定をしますね

View Binding のかわりに Data Binding を使うんですね

  1. モジュールレベルの build.gradle ファイルを開きます
  2. buildFeatures セクションの中で dataBinding プロパティを 有効にします
  3. 変更後、Android Studio によって Sync が表示されるのでこれをクリックします

変更前

buildFeatures {
   viewBinding = true
}

変更後

buildFeatures {
   dataBinding = true
}
  • Kotlin プロジェクトで Data Binding を使うには、kotlin-kapt プラグインを適用する必要があります
  • モジュールレベルの build.gradle に設定があるか確認します(なければ追加します)
plugins {
   ...
   id 'kotlin-kapt'
}
  • この設定によって、アプリにあるすべてのレイアウトXMLファイルに対してバインディングクラスが自動生成されます
  • レイアウトファイルの名前が activity_main.xml であれば、自動生成されたクラスは ActivityMainBinding になります(View Binding の場合とおなじです)

通常のレイアウトをデータバインディングレイアウトに変換する

レイアウトファイルを Data Binding 用に書き換えないといけないんですね

そうなの。Android Studio ならレイアウトのコンバート機能が使えるの

  • データバインディングレイアウトファイル(XML)は通常のレイアウトファイル(XML)と少し異なります
  • <layout> ルートタグで始まり、続いてオプションの <data> 要素、View のルート要素が記述されます
  • この View 要素の部分は、通常のレイアウトファイルでルートになっている部分をそのまま使います

XMLファイルを手動で書き換える

  1. レイアウトのXMLファイルを開きます
  2. <layout> タグの中に root 要素をラップします
  3. ネームスペースの定義(xmlns:で始まる属性)を新しい root要素へ移動します
  4. <layout> タグの中、root 要素の上に <data> </data> タグを追加します

Android Studio を使って自動で書き換える

Android Studio を使うと、データバインディングレイアウトへの書き換えを自動で行うことができます

  1. レイアウトファイルを開いて Code View でXMLで表示します
  2. root 要素(たとえば今回はScrollView)を右クリック > Show Context Actions > Convert to data binding layout と選択します
  3. レイアウトは自動で変換され、たとえばこんな風に書き換えられます
<layout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   xmlns:tools="http://schemas.android.com/tools">

   <data>

   </data>

   <ScrollView
       android:layout_width="match_parent"
       android:layout_height="match_parent">

       <androidx.constraintlayout.widget.ConstraintLayout
         ...
       </androidx.constraintlayout.widget.ConstraintLayout>
   </ScrollView>
</layout>

inflate する部分の書き換え

binding 変数の設定方法も View Binding とちょっと違うんです。気を付けてくださいね

  • アクティビティやフラグメントのクラスを開いて、binding 変数にレイアウトのインスタンスを設定する部分を書き換えます
  • フラグメントのレイアウトであれば、onCreateView() メソッドの最初で、binding 変数をインスタンス化(インフレート)している部分を Data Binding を使うように変更します

変更前(View Binding を使用)

binding = SampleFragmentBinding.inflate(inflater, container, false)

変更後(Data Binding を使用)

binding = DataBindingUtil.inflate(inflater, R.layout.sample_fragment, container, false)

ここまで頑張ると、とりあえず Data Binding が使用できるようになるの

レイアウト変数を設定する

Data Binding だと、レイアウトの中で変数が使えちゃうんです

  • 例として、Kotlin で作成した SampleViewModel クラスのインスタンス viewModel のデータに、レイアウトファイル(XML)からアクセスできるようにします
  • レイアウトファイル(XML)にレイアウト変数(プロパティ)を設定します
  • viewModel にあるデータにアクセスするのには、このレイアウト変数を使用します
  • レイアウト変数の初期化は Kotlin コードの中で行います

レイアウト変数の書き方

レイアウトの中で、変数の名前と型を宣言するの

  • フラグメントのレイアウト(sample_fragment.xml)の中で、<data> タグと <variable> という子タグの内側に、sampleViewModel という名前で、SampleViewModel 型のレイアウト変数(プロパティ)を宣言します
  • ViewModel の中にあるデータは、ここで設定したレイアウト変数を使ってレイアウトにバインドされます
<data>
   <variable
       name="sampleViewModel"
       type="com.example.packagename.SampleViewModel" />
</data>

type がクラスのときはパッケージ名から記述するんですね

  • SampleViewModel の type にはパッケージ名が含まれています
  • パッケージ名が、作成するアプリのクラスのパッケージ名と一致していることを確認します
  • sampleViewModel 変数の宣言の下、<data>タグの内側に、もうひとつ変数を追加してみます
  • 整数型で maxNoOfWords という名前の変数は次のように宣言します
<data>
   ...
   <variable
       name="maxNoOfWords"
       type="int" />
</data>

レイアウト変数を初期化する

レイアウト変数の初期化は、Kotlin コードの中でするんですね

  • レイアウト変数の初期化は Kotlin コード内で行います
  • フラグメントのクラス(SampleFragment)の中、onViewCreated() メソッドの最初で、レイアウト変数の sampleViewModel と maxNoOfWords を初期化します
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
   super.onViewCreated(view, savedInstanceState)

   binding.sampleViewModel = viewModel
   binding.maxNoOfWords = MAX_NO_OF_WORDS

   // 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
   ...
}

lifecycleOwner の設定も忘れずに

  • レイアウトへライフサイクルオーナーを渡すコードも必要です
  • viewModel は LiveData 型です。LiveData はオブザーバブル(値の変化を検知可能)でライフサイクルも考慮します
  • そのため、レイアウトへライフサイクルオーナーを渡すコードも記述します

ライフサイクルオーナーの設定

  • LiveData + オブザーバ による実装をするときもライフサイクルオーナーの指定をします
  • LiveData の オブザーバ へ、パラメータのひとつとしてviewLifecycleOwner を渡します

バインディング式でバインドする

レイアウト(XML)とクラスのプロパティ(Kotlin)をバインドしますね

ちゃんとバインドできれば UI の更新はレイアウトにお任せで楽ちんかも

  • バインディング式は Attribute(属性)プロパティ(たとえば android:text)の中に記述します
  • バインディング式の中でレイアウト変数を参照します
  • レイアウト変数はレイアウトファイル(データバインディング形式)の先頭に、<variable>タグで宣言されたものです
  • LiveData型の変数が変化すると、DB Library がバインディング式を実行します(それによってUI画面が更新されます)
  • Data Binding Library によって、このような変更検知(change-detection)が利用できるようになります。レイアウトが LiveData の更新通知を直接受け取るのでオブザーバ(observe)を記述する必要がありません

バインディング式の文法

バインディング式の中でレイアウト変数を使えば UI と Kotlinコード を連携できるの

  • バインディング式は @ ではじまり、中括弧 { } でラップされます。@{・・・} という形になります
  • 次の例では、TextView の text はレイアウト変数 sampleViewModel にバインドされているデータ(viewModel)の sampleData プロパティにセットされます
<TextView android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:text="@{sampleViewModel.sampleData}" />

バインディング式でリソースを利用する

strings.xml とかも参照できちゃいます

次のようにするとバインディング式の中でアプリのリソースを参照できます

レイアウトファイル(XML)

android:padding="@{@dimen/largePadding}"

上の例では、padding 属性に dimen.xml リソースファイルにある largePadding の値を割り当てています

リソースパラメータを利用する

他のリソースにレイアウト変数を埋め込んで利用するのも便利かも

リソースパラメータとしてレイアウト変数をわたすこともできます

レイアウトファイル(XML)

android:text="@{@string/example_resource(sampleViewModel.sampleData)}"

strings.xml

<string name="example_resource">SAMPLE TEXT: %s</string>
  • 上の例で、example_resource は %s プレースホルダー を持った文字列リソースです
  • バインディング式の中でリソースパラメータとして sampleViewModel.sampleData を渡しています
  • sampleViewModel はレイアウト変数です

リソースパラメータの使い方

XMLリソース を Kotlinコード で利用する場合

リソース中に %s のようなプレースホルダがある場合、リソースパラメータとして Kotlin コードの中で変数の値を代入できます

strings.xml

<string name="example_resource">SAMPLE TEXT: %s</string>

Kotlin コード

val s = "TEST"
binding.textView.text = getString(R.string.example_resource, s)

strings.xml に設定した %s に、変数 s の内容(”TEST”)が代入されます

XMLリソース を XMLレイアウト で利用する場合

  • データバインディングレイアウトで他のリソースを参照することができます
  • 参照先のリソースに %d のようなプレースホルダがある場合、リソースパラメータとしてレイアウト変数を利用することで、クラスの変数(プロパティ)の値を代入することができます

strings.xml

<string name="example_resource">%d of %d words</string>

XMLレイアウト(レイアウト変数を用意する)

<data>
    <variable
        name="sampleViewModel"
        type="com.example.packagename.SampleViewModel" />
    <variable
        name="maxNoOfWords"
        type="int" />
</data>

Kotlin コード(レイアウト変数の初期化)

binding.sampleViewModel = viewModel
binding.maxNoOfWords = MAX_NO_OF_WORDS

XMLレイアウトでのリソースの利用

android:text="@{@string/example_resource(sampleViewModel.sampleInt, maxNoOfWords)}"