Android Kotlin の基礎 02.4: データ バインディングの基本

この Codelab は、Android Kotlin の基礎コースの一部です。このコースを最大限に活用するには、Codelab を順番に進めることをおすすめします。コースのすべての Codelab は、Android Kotlin の基礎の Codelab のランディング ページに一覧表示されています。

はじめに

このコースの前の Codelab では、findViewById() 関数を使用してビューへの参照を取得しました。アプリに複雑なビュー階層がある場合、Android はルートから目的のビューが見つかるまでビュー階層をトラバースするため、findViewById() はコストが高く、アプリの速度が低下します。幸いなことに、もっと良い方法があります。

ビューにデータを設定するために、文字列リソースを使用してアクティビティからデータを設定しました。ビューがデータについて知っていれば、より効率的になります。幸いなことに、これも可能です。

この Codelab では、データ バインディングを使用して findViewById() を不要にする方法を学びます。また、データ バインディングを使用してビューから直接データにアクセスする方法も学習します。

前提となる知識

以下について把握しておく必要があります。

  • アクティビティの概要と、onCreate() でレイアウトを使用してアクティビティを設定する方法。
  • テキストビューを作成し、テキストビューに表示するテキストを設定します。
  • findViewById() を使用してビューへの参照を取得する。
  • ビューの基本的な XML レイアウトの作成と編集。

学習内容

  • データ バインディング ライブラリを使用して findViewById() の非効率的な呼び出しを排除する方法。
  • XML からアプリデータに直接アクセスする方法。

演習内容

  • findViewById() の代わりにデータ バインディングを使用し、レイアウト XML ファイルから直接データにアクセスするようにアプリを変更します。

この Codelab では、AboutMe アプリから始めて、データ バインディングを使用するようにアプリを変更します。完成したアプリの外観は次のようになります。

AboutMe アプリの機能は次のとおりです。

  • ユーザーがアプリを開くと、名前、ニックネームを入力するフィールド、[完了] ボタン、星の画像、スクロール可能なテキストが表示されます。
  • ユーザーはニックネームを入力して、[完了] ボタンをタップできます。編集可能なフィールドとボタンが、入力されたニックネームを表示するテキスト ビューに置き換えられます。


前の Codelab で作成したコードを使用するか、GitHub から AboutMeDataBinding-Starter コードをダウンロードしてください。

以前の Codelab で作成したコードでは、findViewById() 関数を使用してビューへの参照を取得しています。

ビューの作成または再作成後に findViewById() を使用してビューを検索するたびに、Android システムは実行時にビュー階層をトラバースしてビューを検索します。アプリのビューが少ない場合は問題ありません。ただし、本番環境のアプリでは、レイアウトに数十ものビューが含まれている場合があり、最適な設計であっても、ネストされたビューが存在します。

テキストビューを含むスクロール ビューを含む線形レイアウトを考えてみましょう。ビュー階層が大きくて深い場合、ビューの検索に時間がかかり、ユーザーにとってアプリの速度が著しく低下することがあります。変数のビューをキャッシュに保存すると便利ですが、各ビューの変数を各名前空間で初期化する必要があります。ビューやアクティビティが多い場合も、同様に処理時間が長くなります。

1 つの解決策は、各ビューへの参照を含むオブジェクトを作成することです。このオブジェクト(Binding オブジェクト)は、アプリ全体で使用できます。この手法は、データ バインディングと呼ばれます。アプリのバインディング オブジェクトが作成されると、ビュー階層をトラバースしたり、データを検索したりすることなく、バインディング オブジェクトを介してビューやその他のデータにアクセスできます。

データ バインディングには次の利点があります。

  • コードが短く、読みやすく、findByView() を使用するコードよりも保守しやすくなります。
  • データとビューが明確に分離されています。データ バインディングのこのメリットは、このコースの後半でますます重要になります。
  • Android システムは、各ビューを取得するためにビュー階層を 1 回だけトラバースします。これは、ユーザーがアプリを操作している実行時ではなく、アプリの起動時に行われます。
  • ビューへのアクセスで型安全性が確保されます。(型安全性とは、コンパイル中にコンパイラによって型が検証され、変数に誤った型を割り当てようとした場合にエラーがスローされることを意味します)。

このタスクでは、データ バインディングを設定し、データ バインディングを使用して findViewById() の呼び出しをバインディング オブジェクトの呼び出しに置き換えます。

ステップ 1: データ バインディングを有効にする

データ バインディングを使用するには、デフォルトでは有効になっていないため、Gradle ファイルでデータ バインディングを有効にする必要があります。これは、データ バインディングによってコンパイル時間が長くなり、アプリの起動時間に影響する可能性があるためです。

  1. 前の Codelab の AboutMe アプリがない場合は、GitHub から AboutMeDataBinding-Starter コードを入手します。Android Studio で開きます。
  2. build.gradle (Module: app) ファイルを開きます。
  3. android セクションの右波かっこの前に、dataBinding セクションを追加し、enabledtrue に設定します。
dataBinding {
    enabled = true
}
  1. プロンプトが表示されたら、プロジェクトを同期します。プロンプトが表示されない場合は、[File] > [Sync Project with Gradle Files] を選択します。
  2. アプリを実行できますが、変更は表示されません。

ステップ 2: データ バインディングで使用できるようにレイアウト ファイルを変更する

データ バインディングを使用するには、XML レイアウトを <layout> タグでラップする必要があります。これにより、ルートクラスはビューグループではなく、ビューグループとビューを含むレイアウトになります。バインディング オブジェクトは、レイアウトとそのビューを認識できるようになります。

  1. activity_main.xml ファイルを開きます。
  2. [テキスト] タブに切り替えます。
  3. <LinearLayout> の周囲に最も外側のタグとして <layout></layout> を追加します。
<layout>
   <LinearLayout ... >
   ...
   </LinearLayout>
</layout>
  1. [Code] > [Reformat code] を選択して、コードのインデントを修正します。

    レイアウトの名前空間宣言は、最も外側のタグに記述する必要があります。
  1. <LinearLayout> から名前空間宣言を切り取り、<layout> タグに貼り付けます。開始 <layout> タグは次のようになり、<LinearLayout> タグにはビュー プロパティのみが含まれます。
<layout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto">
  1. アプリをビルドして実行し、正しく設定されていることを確認します。

ステップ 3: メイン アクティビティでバインディング オブジェクトを作成する

バインディング オブジェクトへの参照をメイン アクティビティに追加して、ビューへのアクセスに使用できるようにします。

  1. MainActivity.kt ファイルを開きます。
  2. onCreate() の前に、最上位でバインディング オブジェクトの変数を作成します。この変数は通常 binding と呼ばれます。

    binding の型である ActivityMainBinding クラスは、このメイン アクティビティ専用にコンパイラによって作成されます。この名前はレイアウト ファイルの名前(activity_main + Binding)から導かれます。
private lateinit var binding: ActivityMainBinding
  1. Android Studio のプロンプトが表示された場合は、ActivityMainBinding をインポートします。プロンプトが表示されない場合は、ActivityMainBinding をクリックして Alt+Enter(Mac の場合は Option+Enter)を押して、この不足しているクラスをインポートします。(その他のキーボード ショートカットについては、キーボード ショートカットをご覧ください)。

    import ステートメントは、次のようになります。
import com.example.android.aboutme.databinding.ActivityMainBinding

次に、現在の setContentView() 関数を、次の処理を行う命令に置き換えます。

  • バインディング オブジェクトを作成します。
  • DataBindingUtil クラスの setContentView() 関数を使用して、activity_main レイアウトを MainActivity に関連付けます。この setContentView() 関数は、ビューのデータ バインディングの設定も行います。
  1. onCreate() で、setContentView() 呼び出しを次のコード行に置き換えます。
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
  1. DataBindingUtil をインポートします。
import androidx.databinding.DataBindingUtil

ステップ 4: バインディング オブジェクトを使用して findViewById() の呼び出しをすべて置き換える

これで、findViewById() へのすべての呼び出しを、バインディング オブジェクト内のビューへの参照に置き換えることができます。バインディング オブジェクトが作成されると、コンパイラはレイアウト内のビューの ID からバインディング オブジェクト内のビューの名前を生成し、キャメルケースに変換します。たとえば、バインディング オブジェクトでは done_buttondoneButton に、nickname_editnicknameEdit に、nickname_textnicknameText になります。

  1. onCreate() で、findViewById() を使用して done_button を見つけるコードを、バインディング オブジェクト内のボタンを参照するコードに置き換えます。

    次のコードを置き換えます。findViewById<Button>(R.id.done_button)
    次のコードに置き換えます。binding.doneButton

    onCreate() でクリック リスナーを設定する完成したコードは次のようになります。
binding.doneButton.setOnClickListener {
   addNickname(it)
}
  1. addNickname() 関数内の findViewById() へのすべての呼び出しについても同様に行います。
    findViewById<View>(R.id.id_view) のすべての出現箇所を binding.idView に置き換えます。手順は次のとおりです。
  • editText 変数と nicknameTextView 変数の定義と、それらの findViewById() への呼び出しを削除します。エラーが発生します。
  • (削除された)変数ではなく binding オブジェクトから nicknameTextnicknameEditdoneButton ビューを取得して、エラーを修正します。
  • view.visibilitybinding.doneButton.visibility に置き換えます。 渡された view の代わりに binding.doneButton を使用すると、コードの一貫性が高まります。

    結果は次のコードになります。
binding.nicknameText.text = binding.nicknameEdit.text
binding.nicknameEdit.visibility = View.GONE
binding.doneButton.visibility = View.GONE
binding.nicknameText.visibility = View.VISIBLE
  • 機能に変更はありません。必要に応じて、view パラメータを削除し、この関数内で binding.doneButton を使用するように view の使用箇所をすべて更新できます。
  1. nicknameText には String が必要で、nicknameEdit.textEditable です。データ バインディングを使用する場合は、EditableString に明示的に変換する必要があります。
binding.nicknameText.text = binding.nicknameEdit.text.toString()
  1. グレー表示されたインポートは削除できます。
  2. apply{} を使用して関数を Kotlin 化します。
binding.apply {
   nicknameText.text = nicknameEdit.text.toString()
   nicknameEdit.visibility = View.GONE
   doneButton.visibility = View.GONE
   nicknameText.visibility = View.VISIBLE
}
  1. アプリをビルドして実行します。以前とまったく同じように表示され、動作するはずです。

データ バインディングを利用して、データクラスをビューで直接利用できるようにすることができます。この手法はコードを簡素化し、より複雑なケースを処理するうえで非常に有効です。

この例では、文字列リソースを使用して名前とニックネームを設定する代わりに、名前とニックネームのデータクラスを作成します。データ バインディングを使用して、データクラスをビューで使用できるようにします。

ステップ 1: MyName データクラスを作成する

  1. Android Studio の java ディレクトリで、MyName.kt ファイルを開きます。このファイルがない場合は、新しい Kotlin ファイルを作成して MyName.kt と呼びます。
  2. 名前とニックネームのデータクラスを定義します。デフォルト値として空の文字列を使用します。
data class MyName(var name: String = "", var nickname: String = "")

ステップ 2: レイアウトにデータを追加する

activity_main.xml ファイルでは、現在、名前は文字列リソースの TextView で設定されています。名前への参照をデータクラス内のデータへの参照に置き換える必要があります。

  1. [テキスト] タブで activity_main.xml を開きます。
  2. レイアウトの上部にある <layout> タグと <LinearLayout> タグの間に <data></data> タグを挿入します。ここで、ビューをデータに接続します。
<data>
  
</data>

データタグ内で、クラスへの参照を保持する名前付き変数を宣言できます。

  1. <data> タグ内に <variable> タグを追加します。
  2. name パラメータを追加して、変数に "myName" という名前を付けます。type パラメータを追加し、型を MyName データクラスの完全修飾名(パッケージ名 + 変数名)に設定します。
<variable
       name="myName"
       type="com.example.android.aboutme.MyName" />

これで、名前の文字列リソースを使用する代わりに、myName 変数を参照できるようになりました。

  1. android:text="@string/name" を次のコードに置き換えます。

@={} は、中かっこ内で参照されているデータを取得するディレクティブです。

myName は、以前に定義した myName 変数を参照します。この変数は myName データクラスをポイントし、クラスから name プロパティを取得します。

android:text="@={myName.name}"

ステップ 3: データを作成する

これで、レイアウト ファイルでデータを参照できるようになりました。次に、実際のデータを作成します。

  1. MainActivity.kt ファイルを開きます。
  2. onCreate() の上に、慣例に従って myName という名前のプライベート変数を作成します。変数に MyName データクラスのインスタンスを割り当て、名前を渡します。
private val myName: MyName = MyName("Aleks Haecky")
  1. onCreate() で、レイアウト ファイルの myName 変数の値を、先ほど宣言した myName 変数の値に設定します。XML で変数に直接アクセスすることはできません。バインディング オブジェクトを介してアクセスする必要があります。
binding.myName = myName
  1. 変更を加えた後はバインディング オブジェクトを更新する必要があるため、エラーが表示されることがあります。アプリをビルドすると、エラーは解消されます。

ステップ 4: TextView のニックネームにデータクラスを使用する

最後のステップは、TextView のニックネームにもデータクラスを使用することです。

  1. activity_main.xml を開きます。
  2. nickname_text テキストビューで、text プロパティを追加します。以下に示すように、データクラスで nickname を参照します。
android:text="@={myName.nickname}"
  1. ActivityMain で、
    nicknameText.text = nicknameEdit.text.toString()
    を、myName 変数にニックネームを設定するコードに置き換えます。
myName?.nickname = nicknameEdit.text.toString()

ニックネームが設定されたら、コードで新しいデータを使用して UI を更新する必要があります。そのためには、すべてのバインディング式が無効になり、正しいデータで再作成されるようにする必要があります。

  1. ニックネームを設定した後で invalidateAll() を追加し、更新されたバインディング オブジェクトの値で UI が更新されるようにします。
binding.apply {
   myName?.nickname = nicknameEdit.text.toString()
   invalidateAll()
   ...
}
  1. アプリをビルドして実行します。以前とまったく同じように動作するはずです。

Android Studio プロジェクト: AboutMeDataBinding

データ バインディングを使用して findViewById() の呼び出しを置き換える手順は次のとおりです。

  1. build.gradle ファイルの android セクションでデータ バインディングを有効にします。
    dataBinding { enabled = true }
  2. XML レイアウトでルートビューとして <layout> を使用します。
  3. バインディング変数
    private lateinit var binding: ActivityMainBinding を定義します。
  4. MainActivity でバインディング オブジェクトを作成し、setContentView:
    binding = DataBindingUtil.setContentView(this, R.layout.activity_main) を置き換えます。
  5. findViewById() の呼び出しを、バインディング オブジェクト内のビューへの参照に置き換えます。例:
    findViewById<Button>(R.id.done_button) ⇒ binding.doneButton
    (この例では、ビューの名前は XML のビューの id からキャメルケースで生成されています)。

ビューをデータにバインドする手順は次のとおりです。

  1. データのデータクラスを作成します。
  2. <layout> タグ内に <data> ブロックを追加します。
  3. 名前とデータクラスである型を使用して <variable> を定義します。
<data>
   <variable
       name="myName"
       type="com.example.android.aboutme.MyName" />
</data>
  1. MainActivity で、データクラスのインスタンスを含む変数を作成します。例:
    private val myName: MyName = MyName("Aleks Haecky")
  1. バインディング オブジェクトで、変数を先ほど作成した変数(
    binding.myName = myName)に設定します。
  1. XML で、ビューのコンテンツを <data> ブロックで定義した変数に設定します。ドット表記を使用して、データクラス内のデータにアクセスします。
    android:text="@={myName.name}"

Udacity コース:

Android デベロッパー ドキュメント:

このセクションでは、インストラクター主導のコースの一環として、この Codelab に取り組んでいる生徒向けに考えられる宿題をいくつか示します。インストラクターは、以下のようなことを行えます。

  • 必要に応じて宿題を与える
  • 宿題の提出方法を生徒に伝える
  • 宿題を採点する

インストラクターは、これらの提案を必要なだけ使用し、必要に応じて他の宿題も自由に与えることができます。

この Codelab に独力で取り組む場合は、これらの宿題を自由に使用して知識をテストしてください。

以下の質問に回答してください

問題 1

findViewById() への明示的呼び出しと暗黙的呼び出しを最小限に抑える理由

  • findViewById() は、呼び出されるたびにビュー階層を走査します。
  • findViewById() はメインスレッドまたは UI スレッドで実行されます。
  • このような呼び出しはユーザー インターフェースの速度を下げる可能性があるため。
  • アプリがクラッシュする可能性が低くなるため。

問題 2

データ バインディングについて説明してください。

たとえば、データ バインディングについて次のように説明できます。

  • データ バインディングの大きなアイデアは、コンパイル時に 2 つの離れた情報を接続/マッピング/バインドするオブジェクトを作成し、実行時にデータを検索する必要がないようにすることです。
  • これらのバインディングをユーザーに提供するオブジェクトをバインディング オブジェクトと呼びます。
  • バインディング オブジェクトはコンパイラによって作成されます。

問題 3

次のうち、データ バインディングのメリットでないものはどれですか?

  • コードをより短く、読みやすく、保守しやすくできるため。
  • データとビューが明確に分離されています。
  • Android システムは、ビュー階層を 1 回だけトラバースして各ビューを取得します。
  • findViewById() を呼び出すと、コンパイラ エラーが発生します。
  • ビューにアクセスするための型安全性

問題 4

<layout> タグの機能は何ですか?

  • レイアウトのルートビューをラップします。
  • バインディングはレイアウト内のすべてのビューに対して作成されます。
  • データ バインディングを使用する XML レイアウトの最上位ビューを指定します。
  • <layout> 内で <data> タグを使用して、変数をデータクラスにバインドできます。

問題 5

XML レイアウト内のバインドされたデータを参照する正しい方法は次のうちどれですか。

  • android:text="@={myDataClass.property}"
  • android:text="@={myDataClass}"
  • android:text="@={myDataClass.property.toString()}"
  • android:text="@={myDataClass.bound_data.property}"

次のレッスンに進む: 3.1: フラグメントを作成する

このコースの他の Codelab へのリンクについては、Android Kotlin の基礎の Codelab のランディング ページをご覧ください。