この 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 ファイルでデータ バインディングを有効にする必要があります。これは、データ バインディングによってコンパイル時間が長くなり、アプリの起動時間に影響する可能性があるためです。
- 前の Codelab の AboutMe アプリがない場合は、GitHub から AboutMeDataBinding-Starter コードを入手します。Android Studio で開きます。
build.gradle (Module: app)
ファイルを開きます。android
セクションの右波かっこの前に、dataBinding
セクションを追加し、enabled
をtrue
に設定します。
dataBinding {
enabled = true
}
- プロンプトが表示されたら、プロジェクトを同期します。プロンプトが表示されない場合は、[File] > [Sync Project with Gradle Files] を選択します。
- アプリを実行できますが、変更は表示されません。
ステップ 2: データ バインディングで使用できるようにレイアウト ファイルを変更する
データ バインディングを使用するには、XML レイアウトを <layout>
タグでラップする必要があります。これにより、ルートクラスはビューグループではなく、ビューグループとビューを含むレイアウトになります。バインディング オブジェクトは、レイアウトとそのビューを認識できるようになります。
activity_main.xml
ファイルを開きます。- [テキスト] タブに切り替えます。
<LinearLayout>
の周囲に最も外側のタグとして<layout></layout>
を追加します。
<layout>
<LinearLayout ... >
...
</LinearLayout>
</layout>
- [Code] > [Reformat code] を選択して、コードのインデントを修正します。
レイアウトの名前空間宣言は、最も外側のタグに記述する必要があります。
<LinearLayout>
から名前空間宣言を切り取り、<layout>
タグに貼り付けます。開始<layout>
タグは次のようになり、<LinearLayout>
タグにはビュー プロパティのみが含まれます。
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
- アプリをビルドして実行し、正しく設定されていることを確認します。
ステップ 3: メイン アクティビティでバインディング オブジェクトを作成する
バインディング オブジェクトへの参照をメイン アクティビティに追加して、ビューへのアクセスに使用できるようにします。
MainActivity.kt
ファイルを開きます。onCreate()
の前に、最上位でバインディング オブジェクトの変数を作成します。この変数は通常binding
と呼ばれます。binding
の型であるActivityMainBinding
クラスは、このメイン アクティビティ専用にコンパイラによって作成されます。この名前はレイアウト ファイルの名前(activity_main + Binding
)から導かれます。
private lateinit var binding: ActivityMainBinding
- Android Studio のプロンプトが表示された場合は、
ActivityMainBinding
をインポートします。プロンプトが表示されない場合は、ActivityMainBinding
をクリックしてAlt+Enter
(Mac の場合はOption+Enter
)を押して、この不足しているクラスをインポートします。(その他のキーボード ショートカットについては、キーボード ショートカットをご覧ください)。import
ステートメントは、次のようになります。
import com.example.android.aboutme.databinding.ActivityMainBinding
次に、現在の setContentView()
関数を、次の処理を行う命令に置き換えます。
- バインディング オブジェクトを作成します。
DataBindingUtil
クラスのsetContentView()
関数を使用して、activity_main
レイアウトをMainActivity
に関連付けます。このsetContentView()
関数は、ビューのデータ バインディングの設定も行います。
onCreate()
で、setContentView()
呼び出しを次のコード行に置き換えます。
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
DataBindingUtil
をインポートします。
import androidx.databinding.DataBindingUtil
ステップ 4: バインディング オブジェクトを使用して findViewById() の呼び出しをすべて置き換える
これで、findViewById()
へのすべての呼び出しを、バインディング オブジェクト内のビューへの参照に置き換えることができます。バインディング オブジェクトが作成されると、コンパイラはレイアウト内のビューの ID からバインディング オブジェクト内のビューの名前を生成し、キャメルケースに変換します。たとえば、バインディング オブジェクトでは done_button
は doneButton
に、nickname_edit
は nicknameEdit
に、nickname_text
は nicknameText
になります。
onCreate()
で、findViewById()
を使用してdone_button
を見つけるコードを、バインディング オブジェクト内のボタンを参照するコードに置き換えます。
次のコードを置き換えます。findViewById<Button>(R.id.
done_button
)
次のコードに置き換えます。binding.doneButton
onCreate()
でクリック リスナーを設定する完成したコードは次のようになります。
binding.doneButton.setOnClickListener {
addNickname(it)
}
addNickname()
関数内のfindViewById()
へのすべての呼び出しについても同様に行います。findViewById<
View
>(R.id.
id_view
)
のすべての出現箇所をbinding.
idView
に置き換えます。手順は次のとおりです。
editText
変数とnicknameTextView
変数の定義と、それらのfindViewById()
への呼び出しを削除します。エラーが発生します。- (削除された)変数ではなく
binding
オブジェクトからnicknameText
、nicknameEdit
、doneButton
ビューを取得して、エラーを修正します。 view.visibility
をbinding.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
の使用箇所をすべて更新できます。
nicknameText
にはString
が必要で、nicknameEdit.text
はEditable
です。データ バインディングを使用する場合は、Editable
をString
に明示的に変換する必要があります。
binding.nicknameText.text = binding.nicknameEdit.text.toString()
- グレー表示されたインポートは削除できます。
apply{}
を使用して関数を Kotlin 化します。
binding.apply {
nicknameText.text = nicknameEdit.text.toString()
nicknameEdit.visibility = View.GONE
doneButton.visibility = View.GONE
nicknameText.visibility = View.VISIBLE
}
- アプリをビルドして実行します。以前とまったく同じように表示され、動作するはずです。
データ バインディングを利用して、データクラスをビューで直接利用できるようにすることができます。この手法はコードを簡素化し、より複雑なケースを処理するうえで非常に有効です。
この例では、文字列リソースを使用して名前とニックネームを設定する代わりに、名前とニックネームのデータクラスを作成します。データ バインディングを使用して、データクラスをビューで使用できるようにします。
ステップ 1: MyName データクラスを作成する
- Android Studio の
java
ディレクトリで、MyName.kt
ファイルを開きます。このファイルがない場合は、新しい Kotlin ファイルを作成してMyName.kt
と呼びます。 - 名前とニックネームのデータクラスを定義します。デフォルト値として空の文字列を使用します。
data class MyName(var name: String = "", var nickname: String = "")
ステップ 2: レイアウトにデータを追加する
activity_main.xml
ファイルでは、現在、名前は文字列リソースの TextView
で設定されています。名前への参照をデータクラス内のデータへの参照に置き換える必要があります。
- [テキスト] タブで
activity_main.xml
を開きます。 - レイアウトの上部にある
<layout>
タグと<LinearLayout>
タグの間に<data></data>
タグを挿入します。ここで、ビューをデータに接続します。
<data>
</data>
データタグ内で、クラスへの参照を保持する名前付き変数を宣言できます。
<data>
タグ内に<variable>
タグを追加します。name
パラメータを追加して、変数に"myName"
という名前を付けます。type
パラメータを追加し、型をMyName
データクラスの完全修飾名(パッケージ名 + 変数名)に設定します。
<variable
name="myName"
type="com.example.android.aboutme.MyName" />
これで、名前の文字列リソースを使用する代わりに、myName
変数を参照できるようになりました。
android:text="@string/name"
を次のコードに置き換えます。
@={}
は、中かっこ内で参照されているデータを取得するディレクティブです。
myName
は、以前に定義した myName
変数を参照します。この変数は myName
データクラスをポイントし、クラスから name
プロパティを取得します。
android:text="@={myName.name}"
ステップ 3: データを作成する
これで、レイアウト ファイルでデータを参照できるようになりました。次に、実際のデータを作成します。
MainActivity.kt
ファイルを開きます。onCreate()
の上に、慣例に従ってmyName
という名前のプライベート変数を作成します。変数にMyName
データクラスのインスタンスを割り当て、名前を渡します。
private val myName: MyName = MyName("Aleks Haecky")
onCreate()
で、レイアウト ファイルのmyName
変数の値を、先ほど宣言したmyName
変数の値に設定します。XML で変数に直接アクセスすることはできません。バインディング オブジェクトを介してアクセスする必要があります。
binding.myName = myName
- 変更を加えた後はバインディング オブジェクトを更新する必要があるため、エラーが表示されることがあります。アプリをビルドすると、エラーは解消されます。
ステップ 4: TextView のニックネームにデータクラスを使用する
最後のステップは、TextView
のニックネームにもデータクラスを使用することです。
activity_main.xml
を開きます。nickname_text
テキストビューで、text
プロパティを追加します。以下に示すように、データクラスでnickname
を参照します。
android:text="@={myName.nickname}"
ActivityMain
で、nicknameText.text = nicknameEdit.text.toString()
を、myName
変数にニックネームを設定するコードに置き換えます。
myName?.nickname = nicknameEdit.text.toString()
ニックネームが設定されたら、コードで新しいデータを使用して UI を更新する必要があります。そのためには、すべてのバインディング式が無効になり、正しいデータで再作成されるようにする必要があります。
- ニックネームを設定した後で
invalidateAll()
を追加し、更新されたバインディング オブジェクトの値で UI が更新されるようにします。
binding.apply {
myName?.nickname = nicknameEdit.text.toString()
invalidateAll()
...
}
- アプリをビルドして実行します。以前とまったく同じように動作するはずです。
Android Studio プロジェクト: AboutMeDataBinding
データ バインディングを使用して findViewById()
の呼び出しを置き換える手順は次のとおりです。
build.gradle
ファイルの android セクションでデータ バインディングを有効にします。dataBinding { enabled = true }
- XML レイアウトでルートビューとして
<layout>
を使用します。 - バインディング変数
private lateinit var binding: ActivityMainBinding
を定義します。 MainActivity
でバインディング オブジェクトを作成し、setContentView
:binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
を置き換えます。findViewById()
の呼び出しを、バインディング オブジェクト内のビューへの参照に置き換えます。例:findViewById<Button>(R.id.done_button) ⇒ binding.doneBu
tton
(この例では、ビューの名前は XML のビューのid
からキャメルケースで生成されています)。
ビューをデータにバインドする手順は次のとおりです。
- データのデータクラスを作成します。
<layout>
タグ内に<data>
ブロックを追加します。- 名前とデータクラスである型を使用して
<variable>
を定義します。
<data>
<variable
name="myName"
type="com.example.android.aboutme.MyName" />
</data>
MainActivity
で、データクラスのインスタンスを含む変数を作成します。例:private val myName: MyName = MyName("Aleks Haecky")
- バインディング オブジェクトで、変数を先ほど作成した変数(
binding.myName = myName
)に設定します。
- 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}"
次のレッスンに進む:
このコースの他の Codelab へのリンクについては、Android Kotlin の基礎の Codelab のランディング ページをご覧ください。