リスナ登録
エンティティを利用することで、画面へのデータ反映がまとめて行えるようになりました。ただ、いまだに問題として残っているのがリスナ登録です。ビューバインディングやデータバインディングを利用することで、ボタンなどの画面部品へのアクセスが容易になったとはいえ、クリックリスナ登録コードはこれまで通りsetOnClickListener()メソッドを利用しています。データバインディングでは、このクリックリスナ登録も効率よくできます。最後にその方法を紹介しておきます。
イベント処理を記述したクラス
データバインディングを利用してリスナを登録する場合は、イベント処理、すなわち、これまでリスナクラス内のメソッドに記述していた処理を、ひとつのメソッドとして別クラスに記述します。例えば、前節のサンプルのボタンがタップされた時の処理をonBtnGenerateClick()メソッドとして実装するならば、このメソッドが定義されたクラスは、Javaではリスト6、Kotlinではリスト7のようになります。
public class MainHandlers { MainViewModel _mainViewModel; // (1) ActivityMainBinding _activityMainBinding; // (2) public MainHandlers(MainViewModel mainViewModel, ActivityMainBinding activityMainBinding) { // (3) _mainViewModel = mainViewModel; _activityMainBinding = activityMainBinding; } public void onBtnGenerateClick(View view) { // (4) _mainViewModel.generateNewRectangle(); // (5) _activityMainBinding.setRectangle(_mainViewModel.getRectangle()); // (6) } }
class MainHandlers(private val _mainViewModel: MainViewModel, private val _activityMainBinding: ActivityMainBinding) { // (1)〜(3) fun onBtnGenerateClick(view: View) { // (4) _mainViewModel.generateNewRectangle() // (5) _activityMainBinding.rectangle = _mainViewModel.rectangle // (6) } }
リスト6、リスト7ともに、イベント処理を記述したクラスをMainHandlersとしています。そして、これまでButtonClickListenerクラスのonClick()メソッド内に記述していた処理をそのまま記述したメソッドとして、onBtnGenerateClick()を定義しています。それが、(4)です。その際、onClick()メソッドのシグネチャと同じく、引数としてViewを定義しています。
メソッド内の処理は、(5)でViewModel内に用意した新たなRectangleオブジェクトを生成するメソッドを実行し、そのようにして新たに生成されたRectangleオブジェクトを(6)でバインディングオブジェクトにセットしています。
ただし、このようにViewModelオブジェクトとバインディングオブジェクトをイベント処理メソッド内部で利用するため、(1)と(2)のように、MainHandlersクラスのフィールドとしてこれらのオブジェクトを用意し、(3)のコンストラクタでオブジェクトそのものをアクティビティからもらうようにしておきます(Kotlinではこれがクラス宣言の1行にまとめられています)。
レイアウトxmlでのイベント処理クラスの利用
前項で作成したMainHandlersクラスをレイアウトxmlで利用する場合は、リスト8のコードとなります。
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" …> <data> : <variable name="mainHandlers" type="com.….MainHandlers" /> (1) </data> <androidx.constraintlayout.widget.ConstraintLayout …> : <Button android:id="@+id/btnGenerate" : android:onClick="@{mainHandlers::onBtnGenerateClick}"/> (2) : </layout>
エンティティなどと同様に、イベント処理クラスも、リスト8の(1)のようにvariableタグに変数として定義できます。そして、その変数mainHandlers内のメソッドをclickイベントと結びつけるコードが(2)です。
クリックイベントを設定する属性であるonClickに「@{変数名::メソッド名}」を記述するだけです。あとは、アクティビティでイベント処理クラス(MainHandlers)をnewして、バインディングオブジェクトにセットするだけでリスナ設定が完了します。
イベント処理メソッドのシグネチャ
ただし、この方法によるリスナ設定には、ひとつ制約があります。それは、イベント処理メソッドのシグネチャを本来のリスナクラスのメソッドと全く同じにする必要があることです。そのため、リスト6やリスト7のonBtnGenerateClick()の引数として、View型のviewを定義しています。
一方で、onBtnGenerateClick()内では、このviewは利用されていません。本来のリスナクラス内のメソッドのように、インターフェースで定義されたものならば使っていなくても引数は定義しておく必要があります。しかし、onBtnGenerateClick()のように独自に定義できるメソッドで不要な引数を定義するのは、できれば避けたいです。
データバインディングでは、実は、この引数は、必ずしも本来のリスナクラスのメソッドシグネチャに一致させなくてもよく、以下のシグネチャでもかまいません。
public void onBtnGenerateClick() { : }
ただし、その場合は、レイアウトxmlファイル内での@{ }の記述が次のように変わります。
android:onClick="@{(view) -> mainHandlers.onBtnGenerateClick()}"
@{変数名::メソッド名}の代わりにラムダ式を記述します。その構文は以下の通りです。
[構文]イベント処理メソッドの登録
@{(本来の引数) -> イベント処理オブジェクト変数.イベント処理メソッド(引数, …)}
ラムダ式の引数には、本来のリスナクラスのメソッドシグネチャに合わせた引数を記述します。->の右側にはイベント処理オブジェクトを定義した変数に.(ドット)アクセスでイベント処理メソッドの呼び出しコードを記述します。その際、もし引数が必要ならば渡すことも可能です。例えば、onBtnGenerateClick()メソッドシグネチャが、リスト6やリスト7と同じくviewが引数として定義されているならば、以下のようなコードになります。
android:onClick="@{(view) -> mainHandlers.onBtnGenerateClick(view)}"
このように、ラムダ式を使ったイベント処理メソッドでのリスナ設定の方が、より柔軟なコードが記述できます。
まとめ
Android Jetpackについて紹介していく本連載の第7回は、いかがでしたでしょうか。
今回は、前回の続きとして、データバインディングを掘り下げ、式言語、エンティティの利用、リスナ登録を紹介しました。次回は、さらにデータバインディングを掘り下げ、監視オブジェクトの利用や双方向データバインディングを紹介していきます。