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

タスク:findViewById()を減らすためにデータバインディングを利用する

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

ビューが作成された後にそのビューを探すためにfindViewById()を使うたびに、Androidシステムはアプリ作動中にビューヒエラルキーを探し回ります。アプリが片手で数えられる程度のビューしか含んでいない場合はこれはそれほど問題ではありません。しかし、実際の製品としてのアプリはレイアウト上に多数のビューを持っていることはよくあり、ビューヒエラルキーも複雑な構造になっていることがあります。

テキストビューを含むスクロールビューを含むLinearLayoutについて考えてみましょう。
巨大で複雑なビューヒエラルキーによって、ユーザーからみてもアプリの動作が重くなっているのが明らかなほどにビューを見つけるのに時間がかかることもあります。ビューを変数にキャッシュしておくことで多少マシにはなりますが、ビューごとに、また名前空間ごとに変数を初期化する必要があります。多数のビュー、および複数のアクティビティがある場合、さらに変数の数も増えてしまいます。

これに対する解決策の一つはそれぞれのビューの参照を含むオブジェクトを作成することです。このオブジェクトはバインディングオブジェクトと呼ばれ、アプリ全体で使われることになります。この技術がデータバインディングです。
一度バインディングオブジェクトが作られてしまえば、バインディングオブジェクトを介して、ビューヒエラルキーを探索したりデータを探す必要なしにビューにアクセスすることができるようになります。

データバインディングの利点:

  • findViewById()を用いたコードより、コードが短く、読みやすくなるため、今後の管理もしやすくなる。
  • データとビューが明確に切り離されている。このデータバインディングのメリットについてはこの後の記事でより重要になります。
  • Androiシステムがそれぞれのビューを取得するために一度ビューヒエラルキーを探索するだけでよくなる。またアプリの起動時に行われ、動作時ではないため、ユーザビリティが向上する。
  • ビューアクセス時の型安全が保障される。(型安全とはコンパイラーがコンパイル時に型を検証し、ある変数に対して誤った型のデータを代入しようとしていた場合にはエラーをなげることです)

このタスクではデータバインディングのセットアップを行い、データバインディングを使ってfindViewById()をバインディングオブジェクトを呼び出しに置き換えます。

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

データバインディングを利用するためには、デフォルトでは利用できない設定になっているため、Gradleファイルのデータバインディングを有効する必要があります。これはデータバインディングがコンパイル時間を増加させ、アプリのスタートアップ時間に影響を与える可能性があるためです。

  1. 前回の記事のAboutMeアプリを持っていない場合は、GitHubのAboutMeDataBinding-Starter からコードをダウンロードしてください。Android Studioでそれを開いてください。
  2. build.gradle (Module: app)ファイルを開いてください。
  3. androidセクションの中の終わりの括弧の前にdataBidingセクションを追加し、enabledをtrueに設定してください。
dataBinding {
    enabled = true
}
  1. 上にSyncを促す文面がでるはずなので、クリックしてプロジェクトをSyncしてください。もし表示されない場合は、File > Sync Project with Gradle Filesを選択してください。

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

データバインディングを利用するためにはXMLレイアウトを<layout>タグで包む必要があります。これによってルートクラスがビューグループではなくなり、ビューグループやビューを含むレイアウトになります。そうすることでバインディングオブジェクトはレイアウトとそれに含まれるビューの情報を得ることができるようになります。

  1. activity_main.xmlファイルを開いてください。
  2. Textタブに切り替えてください。
  3. 一番外側にあった<LinearLayout>を包むようにさらにその外側を<layout></layout>で囲ってください。
<layout>
   <LinearLayout ... >
   ...
   </LinearLayout>
</layout>
  1. Code > Reformat codeを選択して、コードのインデントを修正してください。

    レイアウト用の名前空間の宣言は一番外側のタグの中で行われる必要があります。
  2. <LinearLayout>の中の名前空間の宣言部分を切り取って、<layout>タグの中に張り付けてください。<layout>タグは以下のようになります。また、<LinearLayout>タグはビューのプロパティのみを含んでいる状態になります。
<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">
  1. アプリをビルドして上記手順が正しく行われたことを確認してください。

ステップ3:Main Activityにバインディングオブジェクトを作る

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

  1. MainActivity.ktファイルを開いてください。
  2. onCreate()の前、クラス宣言のあとにバインディングオブジェクト用の変数を作ります。これは慣習的にbindindとよばれています。

    bindingの型であるActivityMainBindingクラスはmain activity用にコンパイラーによって作られるものです。名前はレイアウトファイルであるactivity_mainから派生したもので、それにBindingを足したActivityMainBindingです。
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_buttonであれば、バインディングオブジェクトの中ではdoneButtonとして、nickname_editであればnicknameEditとして作成されます。

  1. onCreate()の中のdone_buttonを見つけるためのfindViewById()をバインディングオブジェクトのボタンの参照に置き換えます。

    findViewById<button>(R.id.done_button)というコードをbinding.doneButtonに書き換えてください。

    変更後のonCreate()中のクリックリスナーをセットするためのコードは以下のようになります。
binding.doneButton.setOnClickListener {
   addNickname(it)
}
  1. 同様に、addNickname()内のfindViewById()の呼び出しを全てbindind.idViewの形で置き換えてください。以下の方法で行ってください。
  • findViewById()に伴うeditTextとnicknameTextView変数の宣言を削除してください。この際にエラーが表示されます。
  • 削除した変数の代わりに、バインディングオブジェクトの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
  • 機能的には何の変りもありませんが、オプションでaddNickname()関数のviewパラメーターを削除して、全てのviewだった部分をbindin.doneButtonに置き換えることもできます。統一性を持たせたい方は置き換えてみてください。
  1. nicknameTextはStringを要求しますが、nicknameEdit.textはEditableです。データバインディングを使う際には、EditableをStringに明示的に変換する必要があります。
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. アプリをビルドして起動してください。以前と同じ見た目、同じ機能のはずです。

Tip: 変更後にコンパイルエラーが表示された場合は、Build > Clean Projectを選択してください。これをすることで通常は作成されたファイルが更新されます。それでもエラーが解消されない場合は、File > Invalidate Caches/Restartを選択してさらに高精度のクリーンアップをしてください。

Tip: 以前にアプリ内のリソースを保存するためのResourcesオブジェクトについて学習しました。バインディングオブジェクトもこれと似たものとして考えることができます。しかしバインディングオブジェクトはこれよりさらに洗練されたものです。