監視可能なデータ オブジェクトを操作する

オブザーバビリティとは、データ内の変更を他のオブジェクトに通知するオブジェクトの機能です。データ バインディング ライブラリを使用すると、オブジェクト、フィールド、コレクションを監視できます。

データ バインディングには任意のオブジェクトを使用できますが、オブジェクトを変更しても UI は自動的に更新されません。データ バインディングを使用すると、データ オブジェクトが変更されたときに、リスナーと呼ばれる他のオブジェクトに通知機能を持たせることができます。監視可能なクラスには、フィールドコレクションオブジェクトの 3 種類があります。

これらの監視可能なデータ オブジェクトのいずれかが UI にバインドされ、データ オブジェクトのプロパティが変更されると、UI が自動的に更新されます。

監視可能なフィールド

クラスのプロパティ数が少ない場合は、Observable インターフェースを実装するクラスを作成しても無駄になる可能性があります。この場合、汎用の Observable クラスと次のプリミティブ固有のクラスを使用して、フィールドを監視できます。

監視可能なフィールドは、単一のフィールドを持つ自己完結型の監視可能なオブジェクトです。プリミティブ バージョンでは、アクセス オペレーション中のボックス化とボックス化解除を回避します。このメカニズムを使用するには、次の例に示すように、Java プログラミング言語で public final プロパティを作成するか、Kotlin で読み取り専用のプロパティを作成します。

Kotlin

class User {
    val firstName = ObservableField<String>()
    val lastName = ObservableField<String>()
    val age = ObservableInt()
}

Java

private static class User {
    public final ObservableField<String> firstName = new ObservableField<>();
    public final ObservableField<String> lastName = new ObservableField<>();
    public final ObservableInt age = new ObservableInt();
}

フィールド値にアクセスするには、set() および get() アクセサ メソッドを使用するか、Kotlin プロパティ構文を使用します。

Kotlin

user.firstName = "Google"
val age = user.age

Java

user.firstName.set("Google");
int age = user.age.get();

監視可能なコレクション

アプリによっては、データを保持するために動的な構造を使用することがあります。オブザーバブルなコレクションでは、キーを使用してこれらの構造にアクセスできます。次の例に示すように、キーが String などの参照型である場合、ObservableArrayMap クラスが役立ちます。

Kotlin

ObservableArrayMap<String, Any>().apply {
    put("firstName", "Google")
    put("lastName", "Inc.")
    put("age", 17)
}

Java

ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
user.put("firstName", "Google");
user.put("lastName", "Inc.");
user.put("age", 17);

次の例に示すように、レイアウトでは文字列キーを使用して地図を見つけることができます。

<data>
    <import type="android.databinding.ObservableMap"/>
    <variable name="user" type="ObservableMap&lt;String, Object&gt;"/>
</data>
…
<TextView
    android:text="@{user.lastName}"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"/>
<TextView
    android:text="@{String.valueOf(1 + (Integer)user.age)}"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"/>

次のように、キーが整数の場合は、ObservableArrayList クラスが役立ちます。

Kotlin

ObservableArrayList<Any>().apply {
    add("Google")
    add("Inc.")
    add(17)
}

Java

ObservableArrayList<Object> user = new ObservableArrayList<>();
user.add("Google");
user.add("Inc.");
user.add(17);

このレイアウトでは、次の例に示すように、インデックスを介してリストにアクセスできます。

<data>
    <import type="android.databinding.ObservableList"/>
    <import type="com.example.my.app.Fields"/>
    <variable name="user" type="ObservableList&lt;Object&gt;"/>
</data>
…
<TextView
    android:text='@{user[Fields.LAST_NAME]}'
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"/>
<TextView
    android:text='@{String.valueOf(1 + (Integer)user[Fields.AGE])}'
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"/>

監視可能なオブジェクト

Observable インターフェースを実装するクラスを使用すると、監視可能なオブジェクトからプロパティの変更について通知を受け取るリスナーを登録できます。

Observable インターフェースには、リスナーを追加および削除するメカニズムがありますが、通知を送信するタイミングはユーザーが決定します。データ バインディング ライブラリには、リスナーの登録メカニズムを実装した BaseObservable クラスが用意されているため、開発が容易になります。プロパティが変更されると、BaseObservable を実装するデータクラスが通知します。そのためには、次の例に示すように、Bindable アノテーションをゲッターに割り当て、セッター内で notifyPropertyChanged() メソッドを呼び出します。

Kotlin

class User : BaseObservable() {

    @get:Bindable
    var firstName: String = ""
        set(value) {
            field = value
            notifyPropertyChanged(BR.firstName)
        }

    @get:Bindable
    var lastName: String = ""
        set(value) {
            field = value
            notifyPropertyChanged(BR.lastName)
        }
}

Java

private static class User extends BaseObservable {
    private String firstName;
    private String lastName;

    @Bindable
    public String getFirstName() {
        return this.firstName;
    }

    @Bindable
    public String getLastName() {
        return this.lastName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
        notifyPropertyChanged(BR.firstName);
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
        notifyPropertyChanged(BR.lastName);
    }
}

データ バインディングは、データ バインディングに使用するリソースの ID を含むモジュール パッケージに BR という名前のクラスを生成します。Bindable アノテーションは、コンパイル時に BR クラスファイルにエントリを生成します。データクラスの基本クラスを変更できない場合は、PropertyChangeRegistry オブジェクトを使用して Observable インターフェースを実装し、リスナーを効率的に登録して通知できます。

ライフサイクル対応オブジェクト

アプリのレイアウトは、データの変更について UI に自動的に通知するデータ バインディング ソースにバインドすることもできます。これにより、バインディングがライフサイクルを認識し、UI が画面に表示されている場合にのみトリガーされます。

データ バインディングは、StateFlowLiveData をサポートしています。データ バインディングで LiveData を使用する方法について詳しくは、LiveData を使用してデータの変更を UI に通知するをご覧ください。

StateFlow を使用する

アプリでコルーチンで Kotlin を使用する場合は、StateFlow オブジェクトをデータ バインディング ソースとして使用できます。バインディング クラスで StateFlow オブジェクトを使用するには、ライフサイクル オーナーを指定して StateFlow オブジェクトのスコープを定義します。次の例では、バインディング クラスをインスタンス化した後、アクティビティをライフサイクル オーナーとして指定しています。

class ViewModelActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        // Inflate view and obtain an instance of the binding class.
        val binding: UserBinding = DataBindingUtil.setContentView(this, R.layout.user)

        // Specify the current activity as the lifecycle owner.
        binding.lifecycleOwner = this
    }
}

レイアウト ビューをアーキテクチャ コンポーネントにバインドするで説明されているように、データ バインディングは ViewModel オブジェクトとシームレスに連携します。次のように、StateFlowViewModel を併用できます。

class ScheduleViewModel : ViewModel() {

    private val _username = MutableStateFlow<String>("")
    val username: StateFlow<String> = _username

    init {
        viewModelScope.launch {
            _username.value = Repository.loadUserName()
        }
    }
}

次の例に示すように、レイアウトでは、バインディング式を使用して ViewModel オブジェクトのプロパティとメソッドを対応するビューに割り当てます。

<TextView
    android:id="@+id/name"
    android:text="@{viewmodel.username}" />

UI は、ユーザーの名前の値が変更されると自動的に更新されます。

StateFlow のサポートを無効にする

Kotlin と AndroidX を使用するアプリの場合、データ バインディングに StateFlow のサポートが自動的に含まれます。つまり、依存関係がまだ使用できない場合、コルーチンの依存関係が自動的にアプリに含まれることになります。

この機能を無効にするには、build.gradle ファイルに次の行を追加します。

Groovy

android {
    ...
    dataBinding {
        addKtx = false
    }
}

Kotlin

android {
    ...
    dataBinding {
        addKtx = false
    }
}

また、次の行を gradle.properties ファイルに追加して、プロジェクトで StateFlow をグローバルに無効にすることもできます。

Groovy

android.defaults.databinding.addKtx = false

Kotlin

android.defaults.databinding.addKtx = false

参考情報

データ バインディングの詳細については、以下の参考情報をご覧ください。

サンプル

Codelab

ブログ投稿