Two-way binding
안드로이드 앱 개발 시 데이터바인딩을 사용하면 뷰에 데이터를 넣어주는 과정을 간소화 할 수 있습니다. 반대로 뷰에서 데이터를 가져와야 할 때도 Two-way binding 을 이용하면 뷰의 데이터가 변경 될 때마다 데이터를 가져올 수 있습니다.
Two-way binding 을 사용하는 방법은 매우 간단합니다. 기존 xml 에서 Two-way binding 이 가능한 뷰의 속성에 “=” 만 추가해주면 됩니다.
android:text="@{vm.name}" android:text="@={vm.name}"
이렇게 하면 viewModel 에서 editText 의 text 가 변경될 때마다 데이터를 가져올 수 있게 되는데요. 간단한 예제를 통해 어떻게 Two-way binding 이 작동하는지 확인해보겠습니다.
위와 같이 코드를 작성하고 빌드하면 ActivityMainBindingImpl 클래스가 생성됩니다.
내부로 들어가서 EditText 에 직접적으로 관여하는 부분만 발췌해봤습니다.
ViewModel 이 초기화 되는 시점에 setTextWatcher 함수가 실행됩니다. 이후에 EditText 에 값이 변경되면, TextWatcher 안의 InverseBindingListener 가 동작하게 되고, 작성했던 MainViewModel 의 name 필드에 데이터가 저장됩니다.
이러한 동작이 가능한 이유는 EditText 를 위해 TextViewBindingAdapter 클래스가 기본적으로 제공되어 있기 때문입니다.
TextViewBindingAdapter
androidx.databinding.adatpers 패키지 내부에 들어있는 클래스입니다. “android:text” 속성에 관여되는 부분을 살펴보도록 하겠습니다.
우선 “android:text” 속성의 BindingAdapter 함수 입니다. view.setText(text); 함수 호출시 무한루프를 방지하기 위해 기존 텍스트와의 동일여부를 검사하도록 되어 있습니다.
현재 view 의 text 를 가져오기 위한 InverseBindingAdapter 입니다. xml 속성으로 “android:text” 를 가리키고 있습니다. event 는 변경에 대한 트리거 역할을 하는 속성입니다. “android:textAttrChanged” 로 선언되어 있습니다.
EditText 의 변화를 감지할 TextWatcher 를 넣어주는 BindingAdapter 함수입니다.
함수에 선언되어 있는 인자들 중 BeforeTextChanged, OnTextChanged, AfterChanged 는 모두 viewModel 에서 별도로 text 를 받아서 처리할 수 있도록 해줍니다.
InverseBindingListener 는 아까 보았던 InverseBindingAdapter 의 event 에 대응하는 값으로 설정해주어야 합니다. 그래서 bindingAdapter 의 value 에 “android:textAttrChanged” 가 포함되어 있습니다.
requireAll 은 모든 value 에 대응하는 인자가 필수인지 아닌지 여부를 나타냅니다.
false 이기에 나머지 인자를 넣지 않아도 동작하도록 되어 있습니다.
InverseBindingListener 인터페이스의 내부에는 데이터의 변경을 알려주기 위한 onChange() 함수만 선언되어 있습니다. 이 리스너는 ActivityMainBindingImpl 에서도 봤듯이 바인딩 클래스가 생성될때 같이 구현체가 생성됩니다.
따라서 textWatcher 가 변화를 감지하면 InverseBindingListener 가 text 를 가져와서 viewModel 의 라이브데이터에 값이 들어가는 것이었습니다.
이 외에도 구글에서 기본적으로 제공하는 바인딩어댑터가 공식 홈페이지에 소개되어 있습니다.
Custom Two-way Binding
마지막으로 직접 Two-way binding 을 위한 함수를 만들어보도록 하겠습니다.
홈, 대시보드, 노티피케이션 메뉴를 포함하는 바텀네비게이션뷰 화면입니다.
예시를 위해 바텀네비게이션뷰의 아이템 메뉴를 클릭했을 때 동작하는 Two-way binding adapter 를 만들어보겠습니다.
우선 선택된 아이템을 변경하기 위한 BindingAdapter 를 생성하였습니다.
바텀 네비게이션 뷰에서 현재 선택된 아이템의 id 를 얻기 위한 InverseBindingAdapter 를 추가했습니다.
바텀네비게이션뷰에 선택된 아이템의 변화를 감지하는 BindingAdapter 함수를 만들었습니다.
아이템이 변경될때 화면을 이동시키기 위해 OnAfterNavigationItemChangeListener 를 생성하여 인자로 받도록 하였습니다.
사용 예시 코드는 다음과 같습니다.
아이템이 변경될때 changeNavigationItem 함수가 호출되어 Event 가 발행됩니다.
또한 뷰모델에서 selectedItemId 의 값을 변경하면 바텀네비게이션뷰에 현재 선택된 메뉴를 변경할 수 있습니다.
액티비티 코드에서는 viewModel 의 라이브데이터를 구독하여 이벤트가 발행될 때 프래그먼트를 변경해주도록 구현하였습니다.