双方向データバインディング
前節で紹介した監視オブジェクトを利用すると、双方向データバインディングを実現できます。
双方向データバインディング用サンプル
ここで、サンプルを、双方向データバインディング用に変更します。新しいサンプルの画面は、図2のようなものです。
ここまでのサンプルでは、縦の長さと横の長さは、それぞれ乱数で発生させるものでした。その表示欄を入力欄に変更し、ボタンをタップすると乱数で発生させた値を表示させます。一方、入力欄の値も直接変更でき、もちろん、変更した値で面積が自動計算されるようになります。このような場合、前節で利用したMainViewModelをバインドした上で、そのMainViewModel内で保持したRectangleを監視オブジェクトとするパターンをもちろん利用します。
ただし、そのRectangleに入力された縦の長さや横の長さが画面に入力されたと同時にエンティティに格納し、さらには、格納されたデータをもとに新たに計算された面積を自動的に表示する必要があります。つまり、ViewModelで保持されたデータと画面の入力欄のデータを双方向で連動させる必要があります。データバインディングでは、この双方向のデータバインディングにも対応しています。
レイアウトxmlでの双方向データバインディングの設定
このような双方向データバインディングを実現するには、レイアウトxmlの記述を、リスト6のようにします。
<?xml version="1.0" encoding="utf-8"?> <layout : <EditText android:id="@+id/etHeight" : android:text="@={mainViewModel.rectangle.heightStr}"/> (1) : <EditText android:id="@+id/etWidth" : android:text="@={mainViewModel.rectangle.widthStr}"/> (2) : </androidx.constraintlayout.widget.ConstraintLayout> </layout>
リスト6では省略していますが、MainViewModelを直接バインドするものとします。そして、その中のRectangleオブジェクト、つまり、mainViewModel.rectangleのheightStrプロパティやwidthStrプロパティと双方向データバインディングを行う場合、(1)や(2)のように、@={}の形式でそれらのプロパティを記述します。=が増えているところがポイントです。これだけで、縦の長さなら、etHeightのEditTextのデータとmainViewModel.rectangle.heightStrの値が連動するようになります。横の長さに関しても同様です。
エンティティにはセッタとゲッタの両方を定義
ただし、MainViewModelで保持しているRectangleオブジェクトが、リスト2のままならば、@={}を記述しても双方向データバインディングとはなりません。そもそも、レイアウトxmlファイル上でエラー表示となります。その原因は、リスト2のRectangleクラスには、heightStr、および、widthStrのセッタが存在しないからです。これは、もちろん、Javaではリスト7の、Kotlinではリスト8のセッタをRectangleクラスに追記することで解決します。これらのコードとしては、特に解説は不要でしょう。
public class Rectangle extends BaseObservable { : @Bindable public void setHeightStr(String heightStr) { if(!heightStr.equals("")) { int height = Integer.valueOf(heightStr); setHeight(height); } } @Bindable public void setWidthStr(String widthStr) { if(!widthStr.equals("")) { int width = Integer.valueOf(widthStr); setWidth(width); } } }
public class Rectangle extends BaseObservable { : @Bindable fun setHeightStr(value: String) { if(value != "") { val valueInt = value.toInt() height = valueInt } } @Bindable fun setWidthStr(value: String) { if(value != "") { val valueInt = value.toInt() width = valueInt } } }
LiveDataの利用
最後に、監視オブジェクトの代わりにLiveDataが利用できることも紹介しておきましょう。
ViewModelのフィールドをMutableLiveDataにする
LiveDataを利用する場合は、MainViewModelのフィールドを、リスト9の(1)のように、MutableLiveDataとし、その内部で保持するデータ型、すなわち、ジェネリクスとしてRectangleを指定します。このRectangleクラスは、前回のリスト2と同内容で問題ありませんし、finalを外してミュータブルエンティティとしてもかまいません。
public class MainViewModel extends ViewModel { MutableLiveData<Rectangle> _liveRectangle = new MutableLiveData<>(); // (1) public void generateNewHeight() { : _liveRectangle.setValue(rectangle); // (2) } : }
Kotlinコードでは、リスト10のようになります。
class MainViewModel : ViewModel() { val liveRectangle: MutableLiveData<Rectangle> = MutableLiveData() // (1) fun generateNewHeight() { : liveRectangle.value = rectangle // (2) } : }
あとは、このMainViewModelに_liveRectangleのゲッタを記述しておくだけで、レイアウトxmlでは、以下の記述でフィールドのLiveData内のRectangleのheightStrプロパティにアクセスできます。
@{mainViewModel.liveRectangle.heightStr}
LiveDataを利用する場合の注意点
ただし、LiveDataを監視オブジェクトの代わりとして利用する場合には注意が必要です。まず、LiveDataはその性質上、リスト9やリスト10の(2)のように、LiveDataのsetValue()メソッド(Javaコード)、あるいは、valueプロパティ(Kotlinコード)で新たなデータをセットしない限り、画面上の表示が変更されません。
また、アクティビティでバンディングオブジェクトに対してライフサイクルオーナーを登録する必要があります。これは、Javaでは以下のようなコードとなります。
activityMainBinding.setLifecycleOwner(MainActivity.this);
また、Kotlinでは、以下のようなコードとなります。
activityMainBinding.lifecycleOwner = this@MainActivity
まとめ
Android Jetpackについて紹介していく本連載の第8回は、いかがでしたでしょうか。
今回は、3回にわたって紹介するデータバインディングの最後として、監視オブジェクト、双方向データバインディング、LiveDataの利用を紹介しました。
次回は、WorkManagerを紹介します。