iOSエンジニアのつぶやき

毎朝8:30に iOS 関連の技術について1つぶやいています。まれに釣りについてつぶやく可能性があります。

知識ゼロからの Kotlin Android アプリリリースへの軌跡 / Day8【Codelabs 2-4編】

学ぶこと

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

すること

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

アプリ概要

このコードラボでは、AboutMe アプリからデータバインディングを使用するようにアプリを変更します。完了すると、アプリはまったく同じに見えます。

AboutMe アプリの機能は次の通りです:

  • ユーザがアプリを開くと、アプリには名前、ニックネームを入力するためのフィールド、Done ボタン、star imgage、スクロール可能なテキストが表示されます。

  • ユーザはニックネームを入力して Done ボタンをタップできます。編集可能なフィールドとボタンは、入力されたニックネームを表示する TextView に置き換えられます。

前回のコードラボで作成したコードを使用するか、Github から AboutMeDetaBinding-Starter コードをダウンロードできます。

Data binding を使用して findViewById() を削除する

前回のコードラボで記述したコードは、findViewById() 関数を使用して View への参照を取得します。

View が作成または再作成後、findViewById() を使用して View を検索するたびに、Android システムは実行時に View 階層をたどって View を見つけます。アプリの View 数が少ない場合、これは問題ではありません。ただし、プロダクションアプリでは、レイアウトに数十の View が含まれる場合があり、最適なデザインであってもネストされた View があります。

TextView を含む ScrollView を含む Linear Layout を考えてください。大規模または深い View 階層の場合、View を見つけるのに多くの時間がかかり、ユーザのアプリの速度が著しく低下する可能性があります。 変数に View をキャッシュすると役立つ場合がありますが、各ネームスペースで、View ごとに変数を初期化する必要があります。多くの View と複数の Activity があるため、これもまた加算されます。

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

Data binding には次の利点があります:

  • コードは、findByView() を使用するコードよりも短く、読みやすく、保守が容易です。

  • データと View は明確に分離されています。このデータバインディングの利点は、このコースの後半でますます重要になります。

  • Android システムは、View 階層を一回だけトラバースして、各 View を取得します。これは、ユーザがアプリを操作している実行時ではなく、アプリの起動時に行われます。

  • View にアクセスするための type safety を取得します。(タイプセーフとは、コンパイラーがコンパイル中に型を検証し、変数に間違った型を割り当てようとするとエラーがスローされることを意味します。)

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

データバインディングを有効にする

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

  1. AboutMe アプリを開きます。

  2. build.gradle(Module: app)ファイルを開きます。

  3. android セクション内の右中括弧の前に、buildFeatures セクションを追加し、dataBindingtrue に設定します。

buildFeatures {
    dataBinding true
}
  1. プロンプトが表示されたら、プロジェクトを Sync します。メッセージが表示されない場合は、File > Sync Project with Gradle Files を選択します。

  2. アプリは実行できますが、変更は表示されません。

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

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

  1. activity_main.xml ファイルを開きます。

  2. Code タブに切り替えます。

  3. <LinearLayout> を囲む最も外側のタグとして <layotu></layout> を追加します。

<layout>
   <LinearLayout ... >
   ...
   </LinearLayout>
</layout>
  1. Code > Reformat code を選択して、コードのインデントを修正します。

layout の namespace 宣言は、最も外側のタグにある必要があります。

  1. <LinearLayout> から名前空間宣言を切り取り、<layout> タグに貼り付けます。開始の <layout> タグは次のように表示され、<LinearLayout> タグには view property のみが含まれている可能性があります。
<layout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto">
  1. アプリを実行して、これが正しく行われたことを確認します。

main activity で binding object を作成する

Binding object への参照を main activty に追加し、それを使用して view にアクセスできるようにします。

  1. MainActivity.kt ファイルを開きます。

  2. onCreate() の前のトップレベルで、Binding Object の変数を作成します。この変数は慣習的に binding と呼ばれます。

binding のタイプである ActivityMainBinding クラスは、この main activity 専用にコンパイラによって作成されます。名前は、レイアウトファイルの名前、つまり activity_main + Binding から派生します。

private lateinit var binding: ActivityMainBinding
  1. Android Studio でプロンプトが表示されたら、ActivityMainBinding をインポートします。プロンプトが表示されない場合は、ActivityMainBinding をクリックし、Option + Enter を押して、この欠落しているクラスをインポートします。(キーボードショートカットの詳細についてはこちら)

import ステートメントは、次のようになります。

import com.example.android.aboutme.databinding.ActivityMainBinding

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

  • Binding Object を作成します。

  • DataBindingUtil クラスの setContentView() 関数を使用して、activity_main.xml レイアウトを MainActivity に関連付けます。この setContentView() 関数は、view のデータバインディング設定も処理します。

  • onCreate() で、setContentView() 呼び出しを次のコード行に置き換えます。

binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
  1. DataBindingUtil をインポートします。
import androidx.databinding.DataBindingUtil

Binding Object を使用して、findViewById() へのすべての呼び出しを置き換えます

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

  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 で置き換えます。これは次の方法で行います:

  2. editText および nicknameTextView 変数の定義と、findViewById() の呼び出しを削除します。これは Error になります。

  3. binding オブジェクトから nicknameTextnicknameEdit および doneButton を取得して、エラーを修正します。

  4. 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 パラメータを削除し、view のすべての使用を更新して、この関数内で binding.doneButton を使用することができます。

  • nicknameText には String が必要で、nicknameEdit.textEditable です。データバインディングを使用する場合、Editable を明示的に String に変換する必要があります。

binding.nicknameText.text = binding.nicknameEdit.text.toString()
  1. グレー表示されたインポートを削除することができます。

  2. apply{} を使用して関数を Kotlinize します。

binding.apply {
   nicknameText.text = nicknameEdit.text.toString()
   nicknameEdit.visibility = View.GONE
   doneButton.visibility = View.GONE
   nicknameText.visibility = View.VISIBLE
}
  1. アプリをビルドして実行します。見た目と動作は以前とまったく同じです。

Tip: 変更後にコンパイラエラーが表示される場合は、Build > Clean Project を選択してから、Build > Rebuild Project を選択します。これを行うと、通常、生成されたファイルが更新されます。それ以外の場合は、File > Invalidate Caches/Restart を選択して、より完全なクリーンアップを行います。

Tip: これまでに、アプリ内のすべてのリソースへの参照を保持する Resources オブジェクトについて学びました。同様に、view を参照する時に Binding オブジェクトを考えることができます。ただし、Binding オブジェクトははるかに洗練されています。

データバインディングを使用してデータを表示する

データバインディングを利用して、データクラスを View で直接利用できるようにすることができます。この手法はコードを単純化し、より複雑なケースを処理するために非常に価値があります。

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

MyName データクラスを作成する

  1. Android Studiojava ディレクトリで、MyName.kt ファイルを開きます。このファイルがない場合は、新しい Kotlin ファイルを作成して、MyName.kt という名前をつけます。

  2. 名前とニックネームのデータクラスを定義します。デフォルト値として空の文字列を使用します。

data class MyName(var name: String = "", var nickname: String = "")

レイアウトにデータを追加する

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

  1. activity_main.xml を開いて Code タブに切り替えます。

  2. レイアウト上部の <layout> タグと <LinearLayout> タグの間に、<data></data> タグを挿入します。これは、View と Data を接続する場所です。

<data>
  
</data>

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

  1. <data> タグ内に <variable> タグを追加します。

  2. name パラメータを追加して、変数に "myName" という名前をつけます。Type パラメータを追加し、型を MyName データクラスの完全修飾名(package name + variable name) に設定します。

<variable
       name="myName"
       type="com.example.android.aboutme.MyName" />

これで、名前に string resource を使用する代わりに、myName 変数を参照できます。

Note: 使用している Android Studio のバージョンによっては、パッケージ名が異なる場合があります。アプリのパッケージ名が type 変数のパーケージ名と一致していることを確認してください。

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

@={} は、中括弧内で参照されるデータを取得するための directive です。

myName は、以前に定義した myName 変数を参照します。これは、myName データクラスを指し、クラスから name プロパティをフェッチします。

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

データを作成する

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

  1. MainActivity.kt ファイルを開きます。

  2. onCreate() の上で、慣例により、myName と呼ばる private 変数を作成します。変数に MyName データクラスのインスタンスを割り当て、名前を渡します。

private val myName: MyName = MyName("Aleks Haecky")
  1. onCreate() で、レイアウトファイルの myName 変数の値を、宣言した myName 変数の値に設定します。XML の変数に直接アクセスすることはできません。バインディングオブジェクトを介してアクセスする必要があります。
binding.myName = myName
  1. 変更後にバインディングオブジェクトを更新する必要があるため、エラーが表示される場合があります。アプリをビルドすれば、エラーはなくなるはずです。

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

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

  1. activity_main.xml を開きます。

  2. nickname_text text view で、text プロパティを追加します。以下に示すように、データクラスで nickname を参照します。

android:text="@={myName.nickname}"
  1. MainActivity で、nicknameText.text = nicknameEdit.text.toString()myName 変数にニックネームを設定するコードに置き換えます。
myName?.nickname = nicknameEdit.text.toString()

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

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

まとめ

データバインディングを使用して findViewById() の呼び出しを置き換える手順:

  1. build.gradle ファイルの android セクションでデータバインディングを有効にします。buildFeatures { dataBinding true }

  2. <layout>XML レイアウトのルート View として使用します。

  3. バインディング変数を定義します。private lateinit var binding: ActivityMainBinding

  4. MainActivityバインディングオブジェクトを作成し、setContentView を置き換えます。binding = DataBindingUtil.setContentView(this, R.layout.activity_main)

  5. findViewById() への呼び出しを、バインディングオブジェクトの View への参照に置き換えます。例: findViewById<Button>(R.id.done_button) => binding.doneButton (この例では、View の名前は、XML の View の id のキャメルケース表記で生成されます。)

View をデータにバインドする手順:

  1. データのデータクラスを作成します。

  2. <layout> タグ内に <data> ブロックを追加します。

  3. name と データクラスである方のタイプを使用して、<variable> を定義します。

<data>
   <variable
       name="myName"
       type="com.example.android.aboutme.MyName" />
</data>
  1. MainActivity で、データクラスのインスタンスを使用して変数を作成します。例: private val myName: MyName = MyName("Yamato Otaka)

  2. バインディングオブジェクトの変数を作成した変数に設定します。 binding.myName = myName

  3. XML で、View のコンテンツを <data> ブロックで定義した変数に設定します。ドット表記を使用して、データクラス内のデータにアクセスします。 android:text="@={myName.name}"

その他の記事

yamato8010.hatenablog.com

yamato8010.hatenablog.com

yamato8010.hatenablog.com