Android Jetpack 은 개발자가 고품질 앱을 쉽게 개발할 수 있도록 지원하는 라이브러리, 도구, 가이드의 모음 입니다. Jetpack 은 androidx.* 패키지 라이브러리로 지원되며, 크게 4가지 구성요소(기초, 아키텍처, 동작, UI)를 가집니다.

이번 포스트에서 다룰 주제는 아키텍처 구성요소 중 Data Binding 입니다.

DataBinding 을 사용함으로써 View(xml 파일) 은 코틀린 코드와 아래와 같은 방법으로 직접 연결될 수 있습니다.

  • Variable 참조
    • View(xml 파일) 에서 코틀린 코드의 데이터를 직접 참조할 수 있습니다.
  • 2-way data binding
    • View 에서 변경된 데이터를 코틀린 코드가 참조할 수 있도록 설정할 수 있습니다. (EditText 의 입력값 등… 이번 예제에서는 이 케이스는 빠져있습니다.)
  • Listener 바인딩
    • 버튼 Click 과 같은 event 를 처리할 리스너를 View 에서 지정할 수 있습니다.
  • 표현식 삽입

위와 같은 DataBinding 방법에 따라 따라서 Activity가 비대해지거나 boilerplate 코드가 늘어나는 것을 막고, MVVM 패턴을 효율적으로 구현할 수 있는 도구가 될 수 있습니다.

.

.

DataBinding 예제

DataBinding 구현 예제는 GitHub – DataBindingExample 에서 보실 수 있습니다.

이번 예제에서는 앞서 LiveData 에서 사용했던 예제와 같은 기능을 구현합니다. 아래처럼 버튼을 클릭하면 클릭 횟수를 1씩 증가해서 출력하도록 되어 있습니다.

또한 ViewModel / LiveData 예제에서 사용했던 예제를 기반했기 때문에 ViewModel – LiveData – DataBinding 이 함께 동작하도록 구현했습니다. LiveData 예제와 코드를 비교해보시면 더욱 이해가 편할 것입니다.

.

LiveData 및 DataBinding 사용을 위해 app 모듈의 gradle 파일에 아래 내용 추가가 필요합니다.

apply plugin: 'kotlin-kapt'

android {
    ......
    dataBinding {
        enabled = true
    }
}

소스를 보면 2개의 코틀린 파일이 있습니다.

  • MainActivity.kt
    • Activity 에서 사용할 view(xml)를 binding
    • Lifecycle owner 설정
    • ViewModel 생성 및 초기화
    • Binding 객체와 view model 연결
class MainActivity : AppCompatActivity() {
    private lateinit var viewModel : MainViewModel
    private lateinit var binding : ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // setContentView(R.layout.activity_main)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
        binding.apply {
            lifecycleOwner = this@MainActivity
        }

        // create basic view model
        viewModel = ViewModelProvider(this).get(MainViewModel::class.java)

        // initialize view model
        viewModel.init()

        // set view model to binding object
        binding.vm = viewModel
    }
}
  • MainViewModel.kt
    • MutableLiveData 생성
    • init() 초기화 함수
    • clickButton() 클릭 이벤트 처리 함수
    • ViewModel – onCleared() 구현
class MainViewModel : ViewModel() {
    val TAG = "MainViewModel"
    private var count = 0
    val countText : MutableLiveData<String> = MutableLiveData()

    fun init() {
        countText.value = "click count : $count"
    }

    fun clickButton() {
        countText.value = "click count : ${++count}"
    }

    override fun onCleared() {
        Log.d(TAG, "## MainViewModel - onCleared() called!!")
        Log.d(TAG, "## count = $count")
        super.onCleared()
    }
}

.

DataBinding 을 사용하기 위해서는 layout 파일에도 적절한 처리를 해야 합니다.

  • activity_main.xml
<layout>
    <data>
        <import type="android.view.View" />
        <variable
            name="vm"
            type="com.example.databindingexample.MainViewModel" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout 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"
        tools:context=".MainActivity">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical"
            android:gravity="center_horizontal">
            <TextView
                android:id="@+id/text_desc1"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@{vm.countText}"
                android:textSize="20sp"/>
            <Button
                android:id="@+id/button_class"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Increase count"
                android:onClick="@{(view) -> vm.clickButton()}"/>
        </LinearLayout>

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

XML 레이아웃 파일에서 DataBinding 을 사용하기 위해서는 레이아웃 전체를 <layout> 태그로 감싸야합니다. 그리고 layout 태그 바로 아래에 data 태그를 이용해서 XML 에서 참조할 클래스(여기서는 MainViewModel)를 선언해줘야 합니다.

    <data>
        <import type="android.view.View" />
        <variable
            name="vm"
            type="com.example.databindingexample.MainViewModel" />
    </data>

클릭 횟수 문자열을 출력할 TextView 는 ViewModel 에 선언해놓은 LiveData 변수를 참조하도록 설정합니다. 이렇게 함으로써 LiveData 형 countText 변수의 값이 변경되면 자동으로 TextView 문자열이 변경됩니다. @{vm.counText}

            <TextView
                android:id="@+id/text_desc1"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@{vm.countText}"
                android:textSize="20sp"/>

추가로 버튼이 클릭되면 ViewModel 의 clickButton() 함수가 호출되도록 리스너를 설정했습니다. 람다 표현식 형태로 호출할 수 있습니다. @{(view) -> vm.clickButton()}

            <Button
                android:id="@+id/button_class"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Increase count"
                android:onClick="@{(view) -> vm.clickButton()}"/>

이제 버튼을 누르면 ViewModel 의 clickButton() 이 호출되면서 LiveData 로 선언된 countText 문자열을 변경합니다. 그러면 다시 LiveData를 참조하는 TextView 의 문자열이 함께 변경됩니다.

.

View 에서의 처리는 모두 끝났으므로 이제 Activity 에서 View 와 ViewModel, DataBinding 이 상호 연결되도록 설정해주면 됩니다.

Activity 에서는 먼저 기존에 사용하던 setContentView() 대신 DataBindingUtil 을 이용해서 레이아웃을 inflate 합니다. 이때 DataBinding 객체의 lifecycleOwner 를 설정해줘야 합니다. 그렇지 않으면 LiveData 에서 lifecycleOwner를 참조할 수 없어 View 에서 LiveData 의 값을 참조하지 못합니다. (만약 LiveData 를 사용하지 않는다면 binding.apply{} 를 생략)

import com.example.databindingexample.databinding.ActivityMainBinding

    private lateinit var binding : ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        // setContentView(R.layout.activity_main)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
        binding.apply {
            lifecycleOwner = this@MainActivity
        }

DataBinding 객체가 ActivityMainBinding 으로 선언되어 있음을 주의할 필요가 있습니다. ActivityMainBinding 클래스는 레이아웃의 명칭 activity_main.xml 에서 카멜 케이스로 변경한 뒤, Binding 을 붙여 자동 생성됩니다.

.

DataBindingUtil 을 이용한 layout inflate 작업과 DataBinding 객체 생성이 끝나면 ViewModel 초기화 후 ViewModel – DataBinding 연결을 해주면 됩니다.

        // create basic view model
        viewModel = ViewModelProvider(this).get(MainViewModel::class.java)

        // initialize view model
        viewModel.init()

        // set view model to binding object
        binding.vm = viewModel

이후부터는 activity 를 거치지 않고 View 와 ViewModel 이 상호작용을 할 수 있습니다.

DataBinding 활용

DataBinding을 활용하면 View(xml 파일)가 원하는 코드에 바로 연결될 수 있으므로 Activity 에서 View를 처리하기 위해 생성했던 boilerplate 코드와 로직을 상당히 줄여줄 수 있습니다.

특히 MVVM 패턴 구현에 활용하면 View – View model 간의 의존성을 낮춰줄 수 있으므로 보다 효율적인 앱 구조를 구현할 수 있습니다.

.

참고

.

.