[Android] DataBinding

ErroredPasta·2022년 5월 6일
1

Android-DataBinding

목록 보기
1/4

DataBinding은 UI를 layout xml에서 declarative format을 이용하여 bind하도록 해주는 라이브러리 입니다. 즉, 아래와 같은 programmatically한 코드를

findViewById<TextView>(R.id.sample_text).apply {
    text = viewModel.userName
}

xml에 어떻게 View에 data를 bind할지 알려주면 되도록 바꿀 수 있습니다.

<TextView
    android:text="@{viewmodel.userName}" />

DataBinding 사용

build.gradle

ViewBinding과 마찬가지로 DataBinding을 사용하기 위해서는 module-level build.gradle파일에 아래와 같이 dataBinding option을 true로 설정해주어야 합니다.

android {
    ...
    buildFeatures {
        dataBinding true
    }
}

XML layout

DataBinding을 사용하기 위해서는 일반 layout 파일과 달리 root tag를 <layout>으로 설정하고 binding할 data 변수를 <data>를 이용하여 선언하여야 합니다.

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
   <data>
       <variable name="user" type="com.example.User"/>
   </data>
   <LinearLayout
       android:orientation="vertical"
       android:layout_width="match_parent"
       android:layout_height="match_parent">
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.firstName}"/>
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.lastName}"/>
   </LinearLayout>
</layout>

전체적인 layout을 <layout>를 이용하여 감싸주고, <data>내에서 <variable>을 이용하여 data binding에서 사용할 변수명과 변수의 type을 정해주어야 합니다. <data>내에서 참조해야할 클래스가 존재한다면 <import>를 이용하여 아래와 같이 import 해줄 수도 있습니다.

<data>
    <import type="java.util.List"/>
  
	<!-- binding expression 내에서 < 문자 대신 &lt;를 사용해야 한다. -->
    <variable name="list" type="List&lt;String>"/>
</data>

Binding expression

Binding expression은 Java와 비슷하며 data binding 변수를 이용하여 View에 어떻게 반영할지 선언할 수 있습니다. Binding expression 사용 시 주의할 점은 위에서도 언급했듯이 expression내에서 <를 사용할 때 &lt;로 사용하여야 합니다. 일반적인 기능은 위 링크를 참조해주시기 바랍니다.

Missing operations

다음의 연산들은 binding expression에서 사용할 수 없습니다.

  • this
  • super
  • new
  • 명시적 generic 호출

Null coalescing operator

Null coalescing operator은 Kotlin의 elvis operator와 동일하며 왼쪽 operand가 null이 아니면 왼쪽 operand의 값을 사용하고 null일 경우 오른쪽 operand의 값을 사용합니다.

android:text="@{user.displayName ?? user.lastName}"

위의 예제에서 user.displayName이 null이면 user.lastName을 대신 사용합니다.

Property references

클래스내의 property와 field, getter, ObservableField에 아래와 같이 접근할 수 있습니다.

android:text="@{user.lastName}"

Avoiding null pointer exceptions

자동으로 생성된 data binding 코드는 null 값을 확인하며 null pointer exception을 방지합니다. 예를 들어 @{user.name}에서 user가 String이고 null일 경우 user.name에 "null"이 할당됩니다.

View references

Binding expression은 data binding 변수뿐만이 아니라 layout내의 다른 View값을 참조할 수 있습니다. 참조는 View의 id로 가능합니다.

<EditText
    android:id="@+id/example_text"
    android:layout_height="wrap_content"
    android:layout_width="match_parent"/>
<TextView
    android:id="@+id/example_output"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@{exampleText.text}"/>

위의 예제에서 TextView가 EditText를 참조하여 EditText의 text값이 변경될 경우 같이 변경됩니다.

Collections

<data>
    <import type="android.util.SparseArray"/>
    <import type="java.util.Map"/>
    <import type="java.util.List"/>
    <variable name="list" type="List&lt;String>"/>
    <variable name="sparse" type="SparseArray&lt;String>"/>
    <variable name="map" type="Map&lt;String, String>"/>
    <variable name="index" type="int"/>
    <variable name="key" type="String"/>
</data>
…
android:text="@{list[index]}"
…
android:text="@{sparse[index]}"
…
android:text="@{map[key]}"

Array, List, SparseArray, Map과 같은 일반적인 collection들은 [] 연산자를 이용하여 접근이 가능합니다.

DataBinding 동작

DataBinding은 어떻게 동작할까요?

https://github.com/googlecodelabs/android-databinding
위 링크의 SolutionActivity를 이용하여 설명하겠습니다.

우선 layout xml파일을 읽어 abstract한 Binding 클래스와 구현체인 BindingImpl 클래스를 생성해냅니다. 즉, solution.xml을 읽어 SolutionBinding.javaSolutionBindingImpl.java를 생성하게 됩니다.

public abstract class SolutionBinding extends ViewDataBinding
public class SolutionBindingImpl extends SolutionBinding implements com.example.android.databinding.basicsample.generated.callback.OnClickListener.Listener

생성된 클래스의 상속 관계를 그림으로 나타내면 아래와 같습니다.

SolutionBinding은 BaseObservable을 상속받는 ViewDataBinding을 상속받습니다. Observer 패턴을 이용하여 data binding 변수가 변경되면 아래와 같은 일이 일어나게 됩니다.

  1. Data binding 변수가 변경됨
  2. notifyPropertyChanged와 BR에 정의된 field id를 이용하여 변수가 변경된 것을 알림
public void setViewmodel(@Nullable com.example.android.databinding.basicsample.data.SimpleViewModelSolution Viewmodel) {
    this.mViewmodel = Viewmodel;
    synchronized(this) {
        mDirtyFlags |= 0x10L;
    }
    notifyPropertyChanged(BR.viewmodel); // notify change
    super.requestRebind();
}
  1. requestRebind를 호출하여 view update를 요청
  2. 구현체 클래스에서 dirty flag를 보고 필요한 view만 update

Dirty Flag

구현체 클래스(SolutionBindingImpl.java) 내부에는 mDirtyFlags라는 정수형 field가 존재합니다. Dirty flag는 아래와 같이 어떤 observable한 변수가 변했는지 나타내는 flag입니다.

해당 변수가 변하게 되면 flag가 1로 설정이 되며 아래와 같이 dirty flag를 이용하여 view를 update합니다.

// when all or viewmodel, viewmodel.popularity changed
if ((dirtyFlags & 0x38L) != 0) {
    com.example.android.databinding.basicsample.util.BindingAdaptersKt.popularityIcon(this.imageView, viewmodelPopularityGetValue);
    com.example.android.databinding.basicsample.util.BindingAdaptersKt.tintPopularity(this.progressBar, viewmodelPopularityGetValue);
}

// when all or viewmodel, viewmodel.lastname changed
if ((dirtyFlags & 0x31L) != 0) {
   androidx.databinding.adapters.TextViewBindingAdapter.setText(this.lastname, viewmodelLastNameGetValue);
}

// when all changed 
if ((dirtyFlags & 0x20L) != 0) {
    this.likeButton.setOnClickListener(mCallback2);
}

// when all or viewmodel, viewmodel.likes changed
if ((dirtyFlags & 0x32L) != 0) {
    androidx.databinding.adapters.TextViewBindingAdapter.setText(this.likes, integerToStringViewmodelLikes);
    com.example.android.databinding.basicsample.util.BindingAdaptersKt.hideIfZero(this.progressBar, androidxDatabindingViewDataBindingSafeUnboxViewmodelLikesGetValue);
    com.example.android.databinding.basicsample.util.BindingAdaptersKt.setProgress(this.progressBar, androidxDatabindingViewDataBindingSafeUnboxViewmodelLikesGetValue, 100);
}

// when all or viewmodel, viewmodel.name changed
if ((dirtyFlags & 0x34L) != 0) {
    androidx.databinding.adapters.TextViewBindingAdapter.setText(this.name, viewmodelNameGetValue);
}

Reference

[1] "Data Binding Library," Android Developers, last modified Oct 27, 2021, accessed May 11, 2022, https://developer.android.com/topic/libraries/data-binding.

profile
Hola, Mundo

0개의 댓글