ENAN

Developer, Artist, Traveler

공부/Android

[Android] AAC - Databinding

ENAN 2021. 5. 1. 22:44

사실 데이터바인딩은 안드로이드에서만 사용하는 개념은 아니다. 포괄적인 의미에서 데이터바인딩은 UI 요소와 데이터를 결합시켜 사용하는 것을 의미하고, 당연히 다른 언어와 프레임워크에서도 사용되는 개념이다. 아래에서는 Android Jetpack - AAC의 일부인 databinding library에 대해서만 설명할 것!

DataBinding이란?

이 말이 바로 와닿지는 않을 테니(저는 그랬습니다), 우선은 레이아웃 xml 파일에 data를 연결해서 사용하는 것 정도로 이해하고 넘어가 보자.

👇 선언적 형식은 뭘까 👇

더보기

선언적 형식이라는 말이 무슨 말일까? 를 이해하기 위해서는, findviewById를 이용했던 기존 방식(명령적)과 차이를 보면서 이해하면 좋을 것 같다.

- 기존 명령형 방식

<Button
        android:id="@+id/someButton"
/>
override fun onCreate(savedInstanceState: Bundle?) {
        //...
        val someButton = findViewById<Button>(R.id.someButton)
			  someButton.text = "someText"
				someButton.setOnClickListener { 
            viewModel.someFunction()
        }
    }

- 선언형 방식

<Button
        android:id="@+id/someButton"
        android:text="someText"
        android:onClick="@{viewModel.someFunction()}"
/>

보시다시피 명령형 방식에선 어떻게 UI가 구성되는지를 순차적인 명령을 통해서 지정한다. 반면 선언형 방식에서는 순서는 중요하지 않다. 속성에 해당하는 값이 무엇인지 선언만 하면 된다.

이 방식은 보일러플레이트 코드를 줄이고 코드 재사용이 쉽다는 장점이 있다(가독성은 덤). 선언형 vs 명령형에 대한 내용은 안드로이드 뿐만 아니라 모든 언어와 플랫폼에서 사용되는 개념이므로 이참에 자세히 알아두는 것도 좋을 것 같다.

그러니 좀 더 잘 설명된 아래 글을 읽어보면 좋을 듯!

 

명령형 프로그래밍 VS 선언형 프로그래밍

명령형 프로그래밍과 선언형 프로그래밍에 대한 비교를 어디선가 한 번쯤은 접해봤을 거라 생각합니다. 그리고 그 둘이 실제로 무엇을 의미하는지 검색을 해보셨다면 아마 아래와 같은 정의를

boxfoxs.tistory.com

왜 만들었을까?

databinding을 사용하기 전에는 xml에 고정된 값만 넣을 수 있었다. 만약 특정 데이터에 따라 뷰를 변경하려면 액티비티의 코드를 통해 뷰를 갱신해주어야 한다.

이 방식에는 단점이 몇 가지 있다. 먼저, findViewById 함수를 통해 뷰에 접근을 하게 되면, 내부적으로 레이아웃 파일 트리를 순회하며 해당하는 요소를 찾게 된다. 따라서 레이아웃 파일이 많아질 수록 성능적인 이슈가 발생하게 된다.

또한 불필요하게 작성해야 될 코드의 양이 많으며, 실수로 뷰에 잘못된 접근을 했을 때 NPE(Null Pointer Exception)이 발생할 수 있다. 즉, 이를 항상 신경쓰고 있어야 한다는 것이다.

이렇듯 기존 방식에는 여러 단점이 있었고, 이러한 문제들을 해결하기 위해 데이터바인딩이라는 개념이 등장하게 되었다.

(참고로, findViewById 없이도 뷰에 접근할 수 있던 kotlin android extension (kotlin synthetic)은 deprecated 되었다!)

장단점

장점

가독성에 대해서는 사람마다 다르게 느낄 수도 있다고 생각하지만, 기본적으로 선언형 방식이 훨씬 깔끔하게 읽혀지는 것 같다.

액티비티에서 뷰에 접근하는 보일러 플레이트 코드를 작성하지 않아도 된다.

뷰모델에 데이터나 함수 등을 정의해 두고 속성을 지정만 하면 되기 때문에 재사용이 쉽다.

양방향 바인딩을 활용하면 뷰 변경에 따라 데이터도 자동으로 갱신되고, 라이브데이터와 함께 사용하면 data가 변할 때마다 view가 자동으로 갱신되기 때문에 작성할 코드가 훨씬 줄어들고 뷰/데이터 갱신에 신경을 안써도 된다. 즉, 뷰와 데이터를 서로의 변경에 상관없이 완벽하게 일치시킬 수 있다.

단점

디버깅이 어렵다. xml은 기본적으로 디버깅이 안되기 때문에, 데이터가 제대로 넘어가지 않는 경우 이유를 확인하기 어렵다.

클래스 파일이 많이 생기고, 이에 따라 빌드 속도가 느려지며 앱 용량도 증가한다.

vs ViewBinding

viewBinding은 databinding을 단순히 view에 대한 참조를 얻기 위한 목적으로 사용하는 사람들을 위해 탄생했다.

databinding과 비슷한 역할과 특징을 갖지만, 차이점으로 xml 파일에 태그가 필요하지 않고 컴파일 속도가 빠르며 앱 용량이 좀 더 작다는 장점과 양방향 바인딩, binding adapter 등을 통한 동적 변경이 불가능하다는 단점을 갖고 있다.

사용법

 

데이터 결합 라이브러리  |  Android 개발자  |  Android Developers

데이터 결합 라이브러리 Android Jetpack의 구성요소. 데이터 결합 라이브러리는 프로그래매틱 방식이 아니라 선언적 형식으로 레이아웃의 UI 구성요소를 앱의 데이터 소스와 결합할 수 있는 지원

developer.android.com

기본적인 사용법은 developer 사이트에서 충분히 확인할 수 있다. 여기서는 viewModel, room, liveData와 함께 사용하는 법을 살펴보자.

 

👇 사용법(with other AAC components) 👇

더보기

1. 의존성 추가

android{
	buildFeatures {
        dataBinding true
  }
}

dependencies {
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
}

2. xml 파일 최상단에 layout, data 태그 추가

    : 레이아웃 태그를 추가하면 binding 클래스가 자동으로 생성된다. 클래스명은 xml파일명 기준으로 파스칼 케이스로 변환되고 뒤에 Binding이 붙은 이름으로 정해진다. (activity_main.xml → ActivityMainBinding)

<layout 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">

    <data>
				<!--바인딩 하고 싶은 데이터-->
        <variable
            name="viewModel"
            type="exam.yeonj.jetpackexample.MainViewModel" />
    </data>
</layout>

3. MainActivity의 onCreate 내부

// activity class

//setContentView(R.layout.activity_main) 대신 bindingUtil 사용
val binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
// lifecyclerOwner를 지정해 줘야 livedata를 관찰해서 화면 refresh 가능
binding.lifecycleOwner = this
binding.viewModel = mainViewModel

4. xml에서 viewModel의 LiveData 변수에 접근할 수 있도록 public 변수 할당

// viewModel class

var todos: LiveData<List<Todo>>
var newTodo: String? = null
init {
    todos = getAll()
}
fun getAll(): LiveData<List<Todo>> {
    return db.todoDao().getAll()
}
fun insert(todo:String){
		viewModelScope.launch(Dispatchers.IO) {
				db.todoDao().insert(Todo(todo))
		}
}

    : getAll() 함수는 room을 통해 내장 db에 저장된 Todo 리스트를 가져오는 함수이다. 이 때, todoDao().getAll() 함수 역시 LiveData를 반환해야 데이터를 제대로 Observing해서 갱신할 수 있다.

5. xml의 view에 databinding

    1) 단방향 databinding

      : viewModel.todos와 같이 LiveData 변수와 바인딩하면 데이터가 변경된 경우 자동으로 뷰가 갱신된다. 게다가 여기서는 room까지 연결했으므로 내장 db의 데이터가 변하면 자동으로 뷰까지 갱신된다는 것이다!

<TextView
    android:text="@{viewModel.todos.toString()}"
</TextView>

    2) 양방향 databinding

      : 양방향 바인딩을 하게 되면, editText에서 텍스트를 변경했을 때 자동으로 viewModel의 newTodo 변수의 데이터도 변한다.

<EditText
    android:text="@={viewModel.newTodo}"
</EditText>

    3) callback binding
      : onClick 시 실행될 함수를 람다식으로 지정할 수도 있다.

<Button
    android:onClick="@{() -> viewModel.insert(viewModel.newTodo)}"
</Button>

정리

databinding은 장단점이 있는 패러다임이지만 장점이 워낙 매력적이고, 무엇보다 view와 데이터 혹은 로직 사이의 의존성을 최소화할 수 있어 MVVM 패턴에 필수적이다.

아직 소개하지 않은 기능이 있는데, bindingAdapter를 사용하게 되면 recyclerview의 리스트 갱신 같은 복잡한 결합도 선언적으로 가능하게 할 수 있다. 다음에는 bindingAdapter를 사용하는 이유, 방법 등에 대해 글을 써 보겠습니다!


p.s 최근 베타 버전으로 업그레이드 된 컴포즈 라이브러리 또한 선언형 UI 작성 toolkit이다. 지금은 xml과 데이터바인딩을 주로 사용한다고 하더라도, 언젠가 자연스럽게 컴포즈로 대체되지 않을까 싶다.

'공부 > Android' 카테고리의 다른 글

[Android] AAC - ViewModel ( + vs MVVM의 ViewModel)  (3) 2021.03.31
[Android] AAC - LiveData  (0) 2021.03.12
Realm vs Room  (0) 2021.02.28
[Android] AAC - Room  (0) 2021.02.02
Coroutine과 Retrofit 함께 활용하기 -2) 레트로핏 편  (2) 2020.06.13