Help us understand the problem. What is going on with this article?

【和訳】Android Data Binding(Codelabs for the 2019 Android Dev Summit: Mountain View, October 23-24, 2019.)

Android Dev Summit 2019
Codelabs for the 2019 Android Dev Summit: Mountain View, October 23-24, 2019.

Android Data Bindingの日本語訳

1.はじめに

Data Binding Library

Data Binding LibraryはAndroid Jetpack libraryの一つであり、プログラムではなく宣言形式を使用して、XMLレイアウト内のUIコンポーネントをアプリのデータソースにバインドできます。
これにより、定型コードを削減できます。 このコードラボでは、次の方法を学習します。

  • 既存のアプリでデータバインディングを設定する
  • レイアウト式を使用
  • 監視可能なデータオブジェクトの使用
  • カスタムバインディングアダプターを作成

ビルドするもの

このコードラボでは、このアプリをデータバインディングに変換します。
image.png
このアプリには、いくつかの静的データといくつかの監視可能なデータを表示する1つの画面があり、データが変更されると、UIが自動的に更新されます。
データはViewModelによって提供されます。Model-View-ViewModelはデータバインディングで非常にうまく機能するプレゼンテーションレイヤーパターンです。以下に図を示します。
image.png
アーキテクチャコンポーネントライブラリのViewModelクラスにまだ慣れていない場合は、公式ドキュメントをご覧ください。要約すると、ビューにUIの状態(アクティビティ、フラグメントなど)を提供するクラスです。向きの変更に耐え、アプリの残りのレイヤーへのインターフェイスとして機能します。

必要なもの

Android Studio 3.4以降

2.データバインディングなしでアプリを試す

この手順では、コードラボ全体のコードをダウンロードしてから、簡単なサンプルアプリを実行します。

$ git clone https://github.com/googlecodelabs/android-databinding

または、リポジトリをzipファイルとしてダウンロードできます:

  • 1 Zipをダウンロード
  • 2 コードを解凍します
  • 3 Android Studio(バージョン3.4以降)でプロジェクトを開きます image.png

プロジェクトが開いたら、ツールバーのimage.pngをクリックしてアプリを実行します。
ビルドが完了し、アプリがデバイスまたはエミュレーターにデプロイされると、デフォルトのアクティビティが開き、次のようになります。
image.png

この画面にはいくつかのデータが表示され、ユーザーはボタンをクリックしてカウンターを増分し、プログレスバーを更新できます。SimpleViewModelを使用します。それを開いて見てみましょう。

Ctrl + Nを使用して、Android Studioでクラスをすばやく見つけます。 Ctrl + Shift + Nを使用して、名前でファイルを見つけます。
Macでは、Command + Oでクラスを見つけ、Command + Shift + Oでファイルを見つけます。

SimpleViewModelクラスは以下を公開します。

  • "name"と"lastName"
  • "likes"の数
  • 人気のレベルを説明する値

SimpleViewModelを使用すると、ユーザーはonLike()メソッドで"likes"の数を増やすことができます。
最も興味深い機能はありませんが、この演習にはSimpleViewModelで十分です。
一方、PlainOldActivityクラスにあるUI実装には、いくつかの問題があります。

  • findViewById()を複数回呼び出します。これは遅いだけでなく、コンパイル時にチェックされないため安全ではありません。 findViewById()に渡すIDが間違っていると、実行時にアプリがクラッシュします。
  • onCreate()で初期値を設定します。 自動的に設定される適切なデフォルトを設定する方がはるかに良いでしょう
  • XMLレイアウト宣言のButton要素で"android:onClick"属性を使用しますが、これも安全ではありません。onLike()メソッドがアクティビティに実装されていない(または名前が変更されている)場合、アプリは実行時にクラッシュします。
  • たくさんのコードがあります。アクティビティとフラグメントは非常に急速に成長する傾向があるため、できるだけ多くのコードをアクティビティとフラグメントから移動することをお勧めします。また、アクティビティとフラグメントのコードはテストと保守が困難です。

Data Binding Libraryを使用すると、アクティビティからロジックを再利用可能かつテストしやすい場所に移動することにより、これらの問題をすべて修正できます。

3.データバインディングを有効にして、レイアウトを変換します

このプロジェクトでは既にデータバインディングが有効になっていますが、自分のプロジェクトで使用する場合、最初のステップは、それを使用するモジュールでライブラリを有効にすることです。

build.gradle
android {
...
    dataBinding {
       enabled true
    }
}

次に、レイアウトをデータバインディングレイアウトに変換します。

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

  1. <layout>タグでレイアウトをラップします
  2. レイアウト変数を追加する(オプション)
  3. レイアウト式を追加する(オプション)

plain_activity.xmlを開きます。これは、ConstraintLayoutをルート要素とする通常のレイアウトです。
レイアウトをデータバインディングに変換するには、ルート要素を<layout>タグでラップする必要があります。また、名前空間定義(xmlns:で始まる属性)を新しいルート要素に移動する必要があります。
image.png
レイアウトは次のようになります。

plain_activity.xml
<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>
   <androidx.constraintlayout.widget.ConstraintLayout
           android:layout_width="match_parent"
           android:layout_height="match_parent">

       <TextView
...

<data>タグにはレイアウト変数が含まれます。
レイアウト変数は、レイアウト式を記述するために使用されます。レイアウト式は要素属性の値に配置され、@ {expression}形式を使用します。 ここではいくつかの例を示します。

// 複雑なレイアウト式のいくつかの例
android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'

レイアウト式言語は多くの機能を提供しますが、ビュー内に複雑なロジックをネストしないようにすることが最善です。 複雑な式は、レイアウトの読み取りと保守を難しくします。

レイアウト式を使用してレイアウトファイル内のコンポーネントをバインドすることにより、次のことができます。
- アプリのパフォーマンスを改善する
- メモリリークとNULLポインタ例外を防ぐのに役立ちます
- UIフレームワークの呼び出しを削除して、アクティビティのコードを合理化します

ここではいくつかの例を示します。

// viewmodelのnameプロパティをtext属性にバインドします
android:text="@{viewmodel.name}"
// viewmodelのnameVisibleプロパティをvisibility属性にバインドします
android:visibility="@{viewmodel.nameVisible}"
// ビューがクリックされたときに、viewmodelのonLike()メソッドを呼び出します
android:onClick="@{() -> viewmodel.onLike()}"

ここで言語の完全な説明をご覧ください。
それでは、いくつかのデータをバインドしましょう!

4.最初のレイアウト式を作成します

とりあえず、いくつかの静的データバインディングから始めましょう。
- <data>タグ内に2つの文字列レイアウト変数を作成します。

    <data>
        <variable name="name" type="String"/>
        <variable name="lastName" type="String"/>
    </data>
  • "android:id"がplain_nameのTextViewを探し、"android:text"属性にレイアウト式を追加します
        <TextView
                android:id="@+id/plain_name"
                android:text="@{name}" 
        ... />

レイアウト式は@記号で始まり、中括弧{}で囲まれます。
名前は文字列であるため、データバインディングはTextViewでその値を設定する方法を認識します。 後で、さまざまなレイアウト式のタイプと属性を処理する方法を学習します。

  • "android:id"がplain_lastNameのTextViewでも同じことを行います。
        <TextView
                android:id="@+id/plain_lastname"
                android:text="@{lastName}"
        ... />

これらの操作の結果は、plain_activity_solution_2.xmlにあります。
ここで、アクティビティを変更して、データバインディングレイアウトを正しく拡張する必要があります。

5.インフレーションを変更し、アクティビティからUI呼び出しを削除します

レイアウトの準備はできていますが、アクティビティのいくつかの変更が必要です。 PlainOldActivityを開きます。
データバインディングレイアウトを使用しているため、インフレーションは別の方法で行われます。
onCreateで、以下を置き換えます。

setContentView(R.layout.plain_activity)

val binding : PlainActivityBinding =
    DataBindingUtil.setContentView(this, R.layout.plain_activity)

この変数の目的は何ですか? <data>ブロックで宣言したレイアウト変数を設定するために必要になります。 バインディングクラスは、ライブラリによって自動的に生成されます。
生成されたクラスがどのように見えるかを確認するには、PlainActivitySolutionBindingを開いて、見て回ってください。

  • これで、変数値を設定できます。
    binding.name = "Your name"
    binding.lastName = "Your last name"

以上です。 ライブラリを使用してデータをバインドしました。
古いコードの削除を開始できます

  • updateName()メソッドを削除します。これは、新しいデータバインディングコードがIDを見つけてテキスト値を設定しているためです。
  • onCreate()のupdateName()呼び出しを削除します。

これらの操作の結果は、PlainOldActivitySolution2で確認できます。
これでアプリを実行できます。 Adaの名前があなたの名前に置き換わっていることがわかります。

6.ユーザーイベントの処理

これまで、ユーザーにデータを表示する方法を学習しましたが、データバインディングライブラリを使用して、ユーザーイベントを処理し、レイアウト変数でアクションを呼び出すこともできます。
イベント処理コードを変更する前に、レイアウトを少しクリーンアップできます。

  • 最初に、単一のViewModelの2つの変数を置き換えます。これは、プレゼンテーションのコードと状態を1か所に保持するため、ほとんどの場合に使用する方法です。
plain_activity.xml
    <data>
        <variable
                name="viewmodel"
                type="com.example.android.databinding.basicsample.data.SimpleViewModel"/>
    </data>

変数に直接アクセスする代わりに、viewmodelプロパティを呼び出します。

  • 両方のTextViewのレイアウト式を変更します。
plain_activity.xml
        <TextView
                android:id="@+id/plain_name"
                android:text="@{viewmodel.name}"
... />
        <TextView
                android:id="@+id/plain_lastname"
                android:text="@{viewmodel.lastName}"
... />

また、「Like」ボタンのクリックの処理方法を更新します。

  • "android:id"がlike_buttonのボタンを探して置き換えます
plain_activity.xml
android:onClick="onLike"

plain_activity.xml
android:onClick="@{() -> viewmodel.onLike()}"

前のonClick属性は、ビューがクリックされたときにアクティビティまたはフラグメントのonLike()メソッドが呼び出される安全でないメカニズムを使用していました。その正確な署名を持つメソッドが存在しない場合、アプリはクラッシュします。
新しい方法は、コンパイル時にチェックされ、ラムダ式を使用してビューモデルのonLike()メソッドを呼び出すため、はるかに安全です。

Android Studioの[ビルド]メニューで[プロジェクトの作成]をクリックして、データバインディングエラーを確認します。 問題が発生すると、アプリのビルドが妨げられ、ビルドログにエラーが表示されます。

これらの操作の結果は、plain_activity_solution_3.xmlにあります。

次に、不要なものをアクティビティから削除します。

1.リプレイス

    binding.name = "Your name"
    binding.lastName = "Your last name"

    binding.viewmodel = viewModel

2.バイパスされているため、アクティビティのonLike()メソッドを削除します。

これらの操作の結果は、PlainOldActivitySolution3にあります。
アプリを実行すると、ボタンは何もしないことがわかります。 これは、updateLikes()をもう呼び出していないためです。 次のセクションでは、それを適切に実装する方法を学びます。

7.データの監視

前のステップで、静的バインディングを作成しました。
ViewModelを開くと、nameとlastNameが単なる文字列であることがわかりますが、これらは変更されないため問題ありません。 ただし、LIKEはユーザーが変更します。

var likes =  0

この値が変更されたときにUIを明示的に更新する代わりに、監視可能にします。

データバインディングを使用すると、監視可能な値が変更されると、バインドされているUI要素が自動的に更新されます。

可観測性を実装する方法は複数あります。監視可能なクラス監視可能なフィールド、または優先的な方法であるLiveDataを使用できます。 その完全なドキュメントはこちらです。

ObservableFieldsはよりシンプルなので使用します。

リプレイス

    val name = "Grace"
    val lastName = "Hopper"
    var likes = 0
        private set // This is to prevent external modification of the variable.

新しいLiveDatasの場合

    private val _name = MutableLiveData("Ada")
    private val _lastName = MutableLiveData("Lovelace")
    private val _likes =  MutableLiveData(0)

    val name: LiveData<String> = _name
    val lastName: LiveData<String> = _lastName
    val likes: LiveData<Int> = _likes

次もリプレイス

    fun onLike() {
        likes++
    }

    /**
     * Returns popularity in buckets: [Popularity.NORMAL],
     * [Popularity.POPULAR] or [Popularity.STAR]
     */
    val popularity: Popularity
        get() {
            return when {
                likes > 9 -> Popularity.STAR
                likes > 4 -> Popularity.POPULAR
                else -> Popularity.NORMAL
            }
        }

    // popularity is exposed as LiveData using a Transformation instead of a @Bindable property.
    val popularity: LiveData<Popularity> = Transformations.map(_likes) {
        when {
            it > 9 -> Popularity.STAR
            it > 4 -> Popularity.POPULAR
            else -> Popularity.NORMAL
        }
    }

    fun onLike() {
        _likes.value = (_likes.value ?: 0) + 1
    }

ご覧のとおり、LiveDataの値はvalueプロパティで設定されており、変換を使用して1つのLiveDataを別のLiveDataに依存させることができます。 このメカニズムにより、ライブラリは値が変更されたときにUIを更新できます。
LiveDataはライフサイクルを認識するオブザーバブルなので、使用するライフサイクル所有者を指定する必要があります。 これはバインディングオブジェクトで行います。

PlainOldActivityを開き(PlainOldActivitySolution3のように見えるはずです)、バインディングオブジェクトでライフサイクルの所有者を設定します。

binding.lifecycleOwner = this

プロジェクトをリビルドすると、アクティビティがコンパイルされていないことがわかります。 アクティビティから「likes」に直接アクセスしていますが、これはもう必要ありません。

    private fun updateLikes() {
        findViewById<TextView>(R.id.likes).text = viewModel.likes.toString()
        findViewById<ProgressBar>(R.id.progressBar).progress =
            (viewModel.likes * 100 / 5).coerceAtMost(100)
...

PlainOldActivityを開き、アクティビティとその呼び出しのすべてのプライベートメソッドを削除します。 これで、アクティビティは簡単になりました。

class PlainOldActivity : AppCompatActivity() {

    // Obtain ViewModel from ViewModelProviders
    private val viewModel by lazy { ViewModelProviders.of(this).get(SimpleViewModel::class.java) }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val binding : PlainActivityBinding =
            DataBindingUtil.setContentView(this, R.layout.plain_activity)

        binding.lifecycleOwner = this

        binding.viewmodel = viewModel
    }
}

SolutionActivityでこれらの操作の結果を見つけることができます。
一般に、アクティビティからコードを移動することは、保守性とテスト容易性に優れています。
LIKEの数を示すTextViewを監視可能な整数にバインドしましょう。 plain_activity.xmlで

        <TextView
                android:id="@+id/likes"
                android:text="@{Integer.toString(viewmodel.likes)}"
...

ここでアプリを実行すると、LIKEの数が予想どおりに増加します。
image.png

これまでに行われたことを要約しましょう。
1. "name"と"last name"は、ビューモデルから文字列として公開されます。
2. ボタンのonClick属性は、ラムダ式を介してビューモデルにバインドされます。
3. LIKEの数は、監視可能な整数を介してビューモデルから公開され、テキストビューにバインドされるため、変更時に自動的に更新されます。

これまで、"android:onClick"や"android:text"などの属性を使用してきました。 次のセクションでは、他のプロパティについて学習し、独自のプロパティを作成します。

8.バインディングアダプターを使用してカスタム属性を作成する

文字列(または監視可能な文字列)を"android:text"属性にバインドすると、何が起こるのかは明らかですが、どのように起こっていますか?
Data Binding Libraryを使用すると、ほとんどすべてのUI呼び出しは、バインディングアダプターと呼ばれる静的メソッドで実行されます。
ライブラリには、大量のバインディングアダプタが用意されています。 こちらをご覧ください。 次に、android:text属性の例を示します。

    @BindingAdapter("android:text")
    public static void setText(TextView view, CharSequence text) {
        // Some checks removed for clarity

        view.setText(text);
    }

またはandroid:background one

    @BindingAdapter("android:background")
    public static void setBackground(View view, Drawable drawable) {
        if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) {
            view.setBackground(drawable);
        } else {
            view.setBackgroundDrawable(drawable);
        }
    }

データバインディングには魔法はありません。 コンパイル時にすべてが解決され、生成されたコードを読むことができます。
進行状況バーで作業しましょう。 私たちはそれをしたい:

  • LIKEがなければ見えない
  • 5つのLIKEでいっぱいになる
  • いっぱいになったら色を変更

image.png

このためのカスタムバインディングアダプターを作成します。
utilsパッケージのBindingAdapters.ktファイルを開きます。それらをどこで作成しても、ライブラリはそれらを見つけます。Kotlinでは、Kotlinファイルの最上位に関数を追加するか、クラスの拡張関数として静的メソッドを作成できます。

最初の要件、hideIfZeroのバインディングアダプターを探します。

    @BindingAdapter("app:hideIfZero")
    fun hideIfZero(view: View, number: Int) {
        view.visibility = if (number == 0) View.GONE else View.VISIBLE
    }

このバインディングアダプタ
- "app:hideIfZero"属性に適用されます。
- すべてのビューに適用できます(最初のパラメーターはビューであるため、このタイプを変更することで特定のクラスに制限できます)
- レイアウト式が返すものでなければならない整数を取ります。
- 数値がゼロの場合、View GONEを作成します。 それ以外の場合は可視。

plain_activityレイアウトで、進行状況バーを探し、hideIfZero属性を追加します。

    <ProgressBar
            android:id="@+id/progressBar"
            app:hideIfZero="@{viewmodel.likes}"
...

アプリを実行すると、ボタンを初めてクリックしたときにプログレスバーが表示されます。 ただし、値と色を変更する必要があります。
image.png

これらの手順の結果は、plain_activity_solution_4.xmlにあります。

9.複数のパラメーターを持つバインディングアダプターを作成する

進捗値については、最大値とLIKEの数をとるバインディングアダプターを使用します。BindingAdaptersファイルを開き、これを探します。

/**
 *  Sets the value of the progress bar so that 5 likes will fill it up.
 *
 *  Showcases Binding Adapters with multiple attributes. Note that this adapter is called
 *  whenever any of the attribute changes.
 */
@BindingAdapter(value = ["app:progressScaled", "android:max"], requireAll = true)
fun setProgress(progressBar: ProgressBar, likes: Int, max: Int) {
    progressBar.progress = (likes * max / 5).coerceAtMost(max)
}

属性のいずれかが欠落している場合、このバインディングアダプタは使用されません。 これはコンパイル時に発生します。 このメソッドは、3つのパラメーター(適用されるビューに加えて、アノテーションで定義された属性の数)を取ります。
requireAllパラメーターは、バインディングアダプターを使用するタイミングを定義します

  • trueの場合、すべての要素がXML定義に存在する必要があります。
  • falseの場合、欠落している属性はnull、ブール値の場合はfalse、プリミティブの場合は0になります。

次に、属性をXMLに追加します。

        <ProgressBar
                android:id="@+id/progressBar"
                app:hideIfZero="@{viewmodel.likes}"
                app:progressScaled="@{viewmodel.likes}"
                android:max="@{100}"
...

progressScaled属性をLIKEの数にバインドし、リテラル整数をmax属性に渡しているだけです。 @{}形式を追加しないと、データバインディングは正しいバインディングアダプターを見つけることができません。

これらの手順の結果は、plain_activity_solution_5.xmlにあります。
アプリを実行すると、プログレスバーが期待どおりにいっぱいになる様子がわかります。

10.バインディングアダプタの作成の練習

習うより慣れよう。作成

  • LIKEの値に応じてプログレスバーの色を調整し、対応する属性を追加するバインディングアダプター
  • 人気度に応じて異なるアイコンを表示するバインディングアダプタ
  • 黒のic_person_black_96dp
  • 薄いピンクのic_whatshot_black_96dp
  • 濃いピンクのic_whatshot_black_96dp

ソリューションは、BindingAdapters.ktファイル、SolutionActivityファイル、およびsolution.xmlレイアウトにあります。

image.png

11.きっと成功します!

おめでとうございます!コードラボを完了したので、データバインディングレイアウトの作成方法、それに変数と式を追加する方法、監視可能なデータを使用し、カスタムバインディングアダプターを介してカスタム属性でXMLレイアウトをより意味のあるものにします。
次に、より高度な使用法のサンプルと全体像のドキュメントを確認してください。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした