Android

데이터 바인딩 (2) : 이벤트 처리 / Observable

까망사과 2022. 11. 1. 02:00

이벤트 처리

데이터 바인딩을 사용하면 뷰에서 전달되는 이벤트를 처리하는 표현식을 작성할 수 있다. 이벤트 속성의 이름은 몇 가지 예외를 빼면 리스너 메서드의 이름을 따른다. 예를 들어 View.OnClickListeneronClick()에 대한 속성의 이름은 android:onClick이다.

 

데이터 바인딩으로 이벤트를 처리할 때는 다음 두 가지 메커니즘을 사용한다.

 

메서드 참조

바인딩 표현식에서 이벤트 발생 시 호출될 리스너 메서드를 참조한다. :: 연산자를 사용하여 참조할 메서드 이름을 지정한다.

이 표현식은 컴파일 타임에 처리되므로 해당하는 메서드가 없거나 서명을 잘못 사용하는 경우 컴파일 에러가 발생한다.

예를 들어 버튼을 눌렀을 때 MyHandler 클래스의 onButtonClick()을 호출하고자 한다면 다음과 같이 표현식을 사용할 수 있다. 

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable
            name="handler"
            type="com.example.MyHandler" />
    </data>
    <androidx.constraintlayout.widget.ConstraintLayout>
        ...
        <Button
            ...
            android:onClick="@{handler::onButtonClick}" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

 

리스너 바인딩

바인딩 표현식에 기존 메서드를 참조하는 것이 아니라 이벤트 발생 시 실행할 람다 함수를 작성한다. 표현식에서 콜백을 사용하면 필요한 리스너가 자동으로 생성되어 이벤트에 등록된다. 그리고 이벤트가 발생하면 해당 표현식이 처리된다. 이벤트의 반환 타입이 void가 아니라면 표현식도 해당 타입을 반환해야 한다.

 

예를 들어 다음처럼 Presenter 클래스의 onSaveClick(Task)에 클릭 이벤트를 바인딩할 수 있다.

class Presenter {
    fun onSaveClick(task: Task) { ... }
}
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable
            name="task"
            type="com.android.example.Task" />
        <variable
            name="presenter"
            type="com.android.example.Presenter" />
    </data>
    <LinearLayout ... >
        <Button
            ...
            android:onClick="@{() -> presenter.onSaveClick(task)}" />
    </LinearLayout>
</layout>

 


관찰 가능한 데이터

데이터가 관찰 가능하다(observable)는 것은 다른 객체에서 해당 데이터의 변경 사항을 알 수 있다는 것이다.

일반 데이터 타입을 사용하면 데이터가 변경되어도 UI가 자동으로 갱신되지 않지만 관찰 가능한 객체를 사용하면 그렇게 만들 수 있다.

 

Observable 필드

ObservableField를 사용하면 필드를 관찰 가능하게 할 수 있다.

원시(primitive) 타입의 경우 다음 클래스를 사용할 수도 있다.

  • ObservableBoolean
  • ObservableByte
  • ObservableChar
  • ObservableShort
  • ObservableInt
  • ObservableLong
  • ObservableFloat
  • ObservableDouble
  • ObservableParcelable
class User {
    val firstName = ObservableField<String>()
    val lastName = ObservableField<String>()
    val age = ObservableInt()
}

 

Observable 컬렉션

다음 클래스를 사용하면 관찰해야 하는 데이터를 컬렉션으로 모으고 키를 통해 액세스하여 사용할 수 있다.

  • ObservableArrayMap
    키가 String 같은 참조형 타입일 경우 유용하다.
    ObservableArrayMap<String, Any>().apply {
        put("firstName", "Google")
        put("lastName", "Inc.")
        put("age", 17)
    }
    <data>
        <import type="android.databinding.ObservableMap"/>
        <variable name="user" type="ObservableMap<String, Object>"/>
    </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
    키가 정수 타입일 경우 유용하다.
    ObservableArrayList<Any>().apply {
        add("Google")
        add("Inc.")
        add(17)
    }
    <data>
        <import type="android.databinding.ObservableList"/>
        <import type="com.example.my.app.Fields"/>
        <variable name="user" type="ObservableList<Object>"/>
    </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()를 호출하여 프로퍼티에 대한 변경 사항을 알려야 한다.

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)
        }
}

 

데이터 바인딩은 데이터 바인딩에 사용된 리소스의 ID를 포함하는 BR 클래스를 모듈 패키지 안에 생성한다. Bindable 어노테이션은 컴파일 도중에 BR 클래스 안에 항목을 생성한다. 데이터 클래스의 상위 클래스를 변경할 수 없으면 PropertyChangeRegistry 객체로 Observable 인터페이스를 구현하여 효율적으로 리스너를 등록하고 알릴 수 있다.