오늘도 더 나은 코드를 작성하였습니까?

two-way DataBinding(양방향 데이터 바인딩) 본문

Android Jetpack Architecture/DataBinding

two-way DataBinding(양방향 데이터 바인딩)

hik14 2020. 8. 10. 15:31

two-way DataBinding

 

일반적인 데이터 바인딩은 ViewModel에서 Observable 한 데이터의 변화에 있어 그것을 UI에 바로 적용하는 방식이지만

반대로 UI의 변화를 ViewModel의 데이터에 주입하는것.

 

속성 = " @={     }"

 

간단한 예제.

 

public class MainActivity extends AppCompatActivity {

    ActivityMainBinding mBinding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        ViewModel viewModel = new ViewModel();

        mBinding.setViewModel(viewModel);
        mBinding.setActivity(this);

    }


    public void onClickDone(View view){
        mBinding.getViewModel().text.set("초기화 된다");
    }
}
public class ViewModel extends BaseObservable {

    public final ObservableField<String> text = new ObservableField<>("");

}
<?xml version="1.0" encoding="utf-8"?>
<layout>
    <data>
        <variable
            name="viewModel"
            type="com.professionalandroid.apps.twowaydatabinding.ViewModel" />
        <variable
            name="activity"
            type="com.professionalandroid.apps.twowaydatabinding.MainActivity" />
    </data>

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context=".MainActivity">

        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Done"
            android:onClick="onClickDone"/>

        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="@string/hint"
            android:text="@={viewModel.text}"/>

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{viewModel.text}"/>

    </LinearLayout>
</layout>

 

사용자 정의 속성을 이용한 양방향 바인딩 (아직 잘 이해가 안 된다.)

 

데이터 바인딩 라이브러리는 양방향 바인딩이 주로 사용되는 양방향 바인딩 속성에 대한 구현체를 이미 제공하지만 사용자가 직접 정의한 속성(BindingAdapter)에 대해 양방향 바인딩을 사용하려면 @InverseBindingAdapter와 @InverseBindingMethod 애노테이션을 사용한다.

 

1. 초기 값을 설정하고 데이터가 변경될 때 업데이트하는 메서드에 @BindingAdapter를 사용하여 주석을 추가합니다.

@BindingAdapter("time")
    public static void setTime(MyView view, Time newValue) {
        // Important to break potential infinite loops.
        if (view.time != newValue) {
            view.time = newValue;
        }
    }

 

2. 뷰에서 값을 읽는 메서드에 @InverseBindingAdapter를 사용하여 주석을 추가합니다

@InverseBindingAdapter("time")
    public static Time getTime(MyView view) {
        return view.getTime();
    }

 

@BindingAdapter -  viewModel(Data)가 변경되었을 때 수행하고 싶은 로직을 작성할 수 있고

@InverseBindingAdapter - 레이아웃의 사용자 정의 속성 값이 변경되었을 때 viewModel에 레이아웃 변수에 변경사항을 전달하여 양방향 바인딩을 구현하게 할 수 있다.

 

속성의 변경 시기 또는 방식을 알기 위해 뷰에 리스너를 설정해야 된다. 리스너는 맞춤 뷰와 연결된 맞춤 리스너이거나 포커스 상실 또는 텍스트 변경과 같은 일반 이벤트일 수 있습니다. 다음과 같이 속성 변경과 관련된 리스너를 설정하는 메서드에 @BindingAdapter 주석을 추가합니다.

 

 @BindingAdapter("app:timeAttrChanged")
    public static void setListeners(
            MyView view, final InverseBindingListener attrChange) {
        // Set a listener for click, focus, touch, etc.
    }

참고: 모든 양방향 결합에서는 합성 이벤트 속성이 생성됩니다.

이 속성은 기본 속성과 이름이 동일하지만 접미사 "AttrChanged"가 있습니다. 합성 이벤트 속성을 사용하면 라이브러리가 @BindingAdapter를 통해 주석이 추가된 메서드를 생성하여 이벤트 리스너를 적절한 View 인스턴스에 연결할 수 있습니다.

 

양방향 바인딩 컨버터 사용하기

 

레아 이웃에 선언된 변수가 뷰에 양방향 바인딩되고, 화면에 나타나기 전에 특정 포맷으로 변경 또는 추가사항이 있는 상황이라면 컨버터를 사용한다.

 

예를 들어 EditText에 날짜를 표현한다 해보자.

<EditText
        android:id="@+id/birth_date"
        android:text="@={Converter.dateToString(viewmodel.birthDate)}"
    />

viewmodel.brithDate의 타입이 long타입이고 이를 우리가 읽고 이해하기 쉬운 문자열 형식의 날짜로 변경해야 된다.

 

양방향 바인딩 표현식이 사용된다면 반대로 문자열 --> long타입의 값으로 변경도 필요하다. 데이터 바인딩 라이브러리는 이러한 처리를 도와주는 @InverseMethod 애노테이션을 제공한다.

 

public class Converter {
        @InverseMethod("stringToDate")
        public static String dateToString(EditText view, long oldValue,
                long value) {
            // Converts long to String.
        }

        public static long stringToDate(EditText view, String oldValue,
                String value) {
            // Converts String to long.
        }
    }

양방향 데이터 결합을 사용하는 무한 루프

양방향 데이터 결합을 사용할 때 무한 루프가 발생하지 않도록 주의해야 합니다.

사용자가 속성을 변경하면 @InverseBindingAdapter를 사용하여 주석이 추가된 메서드가 호출되고 값이 backing 속성에 할당됩니다.

그러면 결과적으로 @BindingAdapter를 사용하여 주석이 추가된 메서드가 호출되고, 이것이 @InverseBindingAdapter를 사용하여 주석이 추가된 메서드를 또 호출하는 식으로 이어집니다.

 

따라서 @BindingAdapter를 사용하여 주석이 추가된 메서드의 새 값과 이전 값을 비교함으로써 발생 가능한 무한 루프를 끊는 것이 중요합니다.

 

사용자 입력 --> UI 변경--> ViewModel Data 변경 -->  UI 반영 --> ViewModel Data 변경...... 무한 루프.