대외활동/SOPT

SOPT 34th 안드로이드파트 미미나 - DataBinding의 두 얼굴

chattymin 2024. 5. 11. 22:56
728x90
반응형

안녕하세요. SOPT 34기 안드로이드 파트 OB 박동민입니다.

 

Android에서 말이 참 많은 기술이죠. DataBinding에 대해서 알아보는 시간을 가져볼겁니다.

DataBinding이 뭔지, 어떻게 쓰는지, 정말 안좋은 기술인지를 알아보고 ViewBinding과 DataBinding 중 선택해서 사용하신다면 더 좋은 코드를 작성할 수 있을 것 같습니다 :)

 

 

목차

  • DataBinding이란?
  • 어떻게 쓸까?
  • 심화 사용방법
  • 왜 DataBinding이 욕을 먹을까?
  • 이렇게 안좋은데 왜 가르쳐 줬어요?
  • 그래서 작성자는 쓰시나요?

 

 

DataBinding이란?

Databinding 라이브러리는 Android Architecture Components(AAC)의 기능으로, 프로그래매틱 방식이 아닌 선언적형식을 사용해서 UI구성요소를 데이터소스에 결합할 수 있도록 지원해준다.

 

기존의 방식으로는 Activity내부에서 아래와 같은 코드를 작성하며 UI와 데이터를 연결해줬다.

// 아무 Binding도 안하는 정말 옛날 방식
TextView textView = findViewById(R.id.text);
textView.setText("이제는 이러면 안돼요");

// 우리가 배운 ViewBinding 방식
val binding = ActivityTestBinding.inflate(layoutInflater)
binding.etvTestText.setText(viewModel.text)

 

 

하지만, DataBinding을 사용하게 된다면 위와같이 kotlin(java)을 호출하지 않아도 된다.

코드에서 ui에 data를 넣어주는 것이 아니라 xml에서 데이터를 직접 넣어줄수 있게 된다.

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

 

 

 

어떻게 쓸까?

먼저 buildFeatures에 추가해줘야 한다.

android {
    ...
    buildFeatures {
        dataBinding true
    }
}

 

 

 

DataBinding을 사용하기 위해서는 xml파일이 <layout> 루트 테그로 시작해야한다.

또한 사용하고자 하는 데이터를 <data> 테그로 감싸서 일종의 변수선언을 한 후 사용해야 한다.

<?xml version="1.0" encoding="utf-8"?>
<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="vm"
            type="org.sopt.dosopttemplate.presentation.auth.login.LoginViewModel" />

    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingHorizontal="30dp"
        android:paddingVertical="60dp"
        tools:context=".presentation.auth.login.LoginActivity">

        <TextView
            android:id="@+id/tv_login_title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="60dp"
            android:text="@{vm.testText}"
            android:textSize="36sp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
            
         </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

해당 변수를 @{}로 감싸서 원하는 곳에 넣는다면 해당 값이 binding되어 화면에 나타나게 된다.

 

이 과정에서 기존에 만든 프로젝트를 그럼 DataBinding으로 쓰려면 너무 귀찮다 싶을 수도 있다.

그런 여러분들을 위해서 안드로이드 스튜디오에서 기본적으로 xml을 바꿔주는 기능을 제공해주고있다.

만들어둔 xml파일에 마우스를 올려두거나, 커서가 들어가있다면 노란색 전구모양 아이콘이 나올것이다.

이걸 클릭 후 "Convert to data binding layout"을 누른다면 자동으로 변경되니 애용하자.

 

 

DataBinding을 사용하고자 하는 Activity에서도 DataBinding을 연결해줘야 한다.

class LoginActivity : AppCompatActivity() {
    private lateinit var binding: ActivityLoginBinding
    private val loginViewModel: LoginViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = DataBindingUtil.setContentView(this, R.layout.activity_login)
        binding.vm = loginViewModel
    }
}

 

 

 

 

 

심화 사용법

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

위에서 알려준 방법은 viewModel에 있는 값을 화면에 나타내주는 방법이다.

하지만, 사용자가 입력한 값을 viewModel에 있는 data에 넣어줘야 하는 경우가 있다.

 

예를 들어 사용자의 입력에 따라 즉각적으로 화면에 나타나야 하는 error문구를 컨트롤 해야하거나, 로그인 버튼을 눌렀을 때 viewModel에 저장된 id와 password와 같은 값들을 server로 보내줘야하는 경우 등이 있다.

 

이 값들을 dataBinding을 통해 화면과 viewModel에 있는 값을 일치시키는.

즉, viewModel에 있는 값을 화면에 나타내주고 화면의 값의 변화를 viewModel에 반영시켜주는 방법이 양방향 데이터 바인딩이다.

이 기능을 사용하기 위해서는 LiveData or Flow를 사용해야 한다.

 

 

DataBinding에서 LiveData를 사용하기 위해서는 반드시 lifecycleOwner를 연결해줘야한다.

class LoginActivity : AppCompatActivity() {
    private lateinit var binding: ActivityLoginBinding
    private val loginViewModel: LoginViewModel by viewModels { ViewModelFactory() }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = DataBindingUtil.setContentView(this, R.layout.activity_login)
        binding.lifecycleOwner = this // 요게 핵심!!
        binding.loginViewModel = loginViewModel
    }
}

 

 

ViewModel에서 MutableLiveData를 선언해준다.

class LoginViewModel(){
	val id = MutableLiveData(String())
    ...
}

 

 

 

그 후 xml에서 위에 알려준 방법 그대로 값을 가져오면 되는데, 이때 중요한 것은 @{}이 아닌, @={}을 사용해줘야 한다.

<com.google.android.material.textfield.TextInputEditText
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:afterTextChanged="@{(text) -> loginViewModel.checkValid()}"
                android:inputType="text"
                android:maxLines="1"
                android:text="@={loginViewModel.id}" />

 

사실 이렇게 하면 끝이다. 이러면 양방향 데이터바인딩 설정이 끝난다.

그런데 afterTextChanged가 이상하다. 저게 뭘까?

 

 

Lambda with DataBinding

코틀린으로 코드를 작성하다보면 심심치 않게 사용하는 것이 람다함수다.

람다는 xml의 DataBinding에서도 사용이 가능하다.

 

사용법은 사실 별 다른것 없고 기존에 람다함수를 사용하는 것 처럼 쓰면 된다.

class LoginViewModel() : ViewModel() {
    val id = MutableLiveData(String())
    val pw = MutableLiveData(String())
    
    private val _loginState = MutableLiveData<Boolean>()
    val loginState: LiveData<Boolean>
        get() = _loginState

	fun checkValid(text: String) {
        _loginState.value = (text.length in 6..10)
    }
    
    fun clickLoginBtn() {
        ...
    }
}

이러한 함수가 viewModel에 있다고 생각해보자.

 

해당 함수를 xml에서도 위 예시처럼 람다를 활용해 호출할 수 있다.

// TextInputEditText내부 함수 
android:afterTextChanged="@{(text) -> loginViewModel.checkValid()}"

// Login Button
<Button
            android:id="@+id/btn_login_navi_log_in"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:enabled="@{loginViewModel.loginState.dataValid}"
            android:onClick="@{() -> loginViewModel.clickLoginBtn()}"
            android:text="@string/btn_login_navi_log_in"
            app:layout_constraintBottom_toTopOf="@id/btn_login_navi_sign_up"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent" />

 

 

 

내부 연산자 활용

위 예시와 같이 버튼의 enabled의 경우 단순한 true/false로 나타낼 수 있다.

하지만, TextField의 error같은 경우는 어떻게 처리할 수 있을까?

 

TextField의 error는 text를 넣어준다면 에러문구를 출력해주고, null을 넣어주면 에러문구를 없애준다.

기본값이 null로 되어있어 아마 못봤을 수도 있다.

 

그럼 if문을 활용해서 loginState가 false일 때 error문구를 넣어주면 되지않나? 생각할 수 있다.

하지만 if문은 xml에 없다.

 

그렇기 때문에 연산자를 활용해줘야 한다.

<com.google.android.material.textfield.TextInputLayout
            android:id="@+id/etv_login_id"
            style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginBottom="8dp"
            android:hint="@string/etv_login_id"
            app:errorEnabled="@{!loginViewModel.loginState.idError}"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/tv_login_id_title"
            tools:error="@{loginViewModel.loginState.idError == true ? @string/ID_ERROR : null }">

            <com.google.android.material.textfield.TextInputEditText
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:afterTextChanged="@{(text) -> loginViewModel.checkValid()}"
                android:inputType="text"
                android:maxLines="1"
                android:text="@={loginViewModel.id}" />
        </com.google.android.material.textfield.TextInputLayout>

 

 

이 코드 중 우리가 주목해야할 부분은 아래와 같다.

tools:error="@{loginViewModel.loginState.idError == true ? @string/ID_ERROR : null }"

// 조건부 3항 연산자 -=> "조건 ? true일 때 : false일 때"

여러분들에게도 익숙한 3항연산자를 활용한다면 if문을 쓰는것 처럼 xml에서도 쓸 수 있다.

 

 

 

물론 3항연산자를 여러분들도 알고있을거라 생각한다. 하지만 xml에서도 쓸 수있구나~ 라는걸 보여주로 넣은 예시이다

이렇듯 다양한 연산자를 DataBinding에서 사용이 가능하다.

출처 : https://hik-coding.tistory.com/9

 

 

이 외에도 Null 병합연산자(??)를 사용할 수 있다.

이는 null포인터 에러를 막기위해 사용하는 것으로 kotlin의 ?:과 같은 역할을 한다.

<TextView
	android:layout_width="wrap_content"
	android:layout_height="wrap_content"
	android:text="@{vm.name ?? @string/default_name}"
/>

이렇게 가져온 값이 null일 경우 뒤에있는 값을 사용할 수 있게 해준다.

 

이 기능이 정말 중요하다

kotlin의 경우 언어 자체에서 Null Safety를 챙기기 위해 에러를 띄워주지만, xml에서는 그게 불가능하다.

그렇기 때문에 사용자가 반드시 Null안정성을 챙겨줘야 한다.

 

 

 

 

Collection 클래스 사용하기

사용하고자 하는 Collection과 데이터를 import테그를 사용해서 지정해준다.

<data>
        <import type="android.view.View" />

        <import type="com.going.domain.entity.NameState" />

        <variable
            name="viewModel"
            type="com.going.presentation.entertrip.createtrip.choosedate.CreateTripViewModel" />
            
            
            
        <import type="java.util.HashMap"/>
        <import type="com.going.domain.entity.User"/>

        <variable
            name="userMap"
            type="HashMap&lt;Integer, User>"
            />
</data>

이 때, < 는 이스케이프코드로 나타내줘야 한다.

이게 왜 그런지는 잘 모르겠다... 저렇게 안하니까 에러나서 찾아보다 저렇게 하니까 됐다.

 

 

 

리소스 사용하기

@{@string/value}처럼 원하는 리소스를 호출해서 사용할 수 있다.

출처 : https://hik-coding.tistory.com/9

 

 

 

BindingAdapter

DataBinding을 쓸 때 더욱 편하게 하기 위해 사용하는 방법이다.

 

우리가 함수에 매개변수를 넣어 사용하듯, xml에서도 BindingAdapter를 사용해서 변수를 넣듯 사용할 수 있다.

이렇게 해당 변수의 이름을 어노테이션을 통해 설정하고, 해당 변수가 적용되는 부분을 함수로 작성해주면 된다.

 

그러면 이렇게 xml에 원하는 값을 넣어줄 수 있다.

 

물론 DataBinding이 아니라 그냥 값을 넣어줘도 되지만, DataBinding과 함께 활용한다면 더욱 강력하게 쓸 수 있다.

 

 

 

 

 

 

왜 DataBinding이 욕을 먹을까?

위 설명만 보면 정말정말 편하고 심지어 선언형이라는 장점도 있어보인다.

근데 왜 DataBinding은 욕을 먹을까?

 

 

우선 단점을 보기전에 장점을 먼저 보자

코드가독성이 좋다

보일러플레이트를 상당히 줄여주고, MVVM패턴의 구현을 간소화 해준다.

 

예를 들어, 양방향 데이터 바인딩을 사용한다면 @={viewModel.id}이렇게 작성한다면 viewModel의 데이터와 UI의 데이터가 바로 즉각적으로 동기화된다. 하지만 ViewBinding을 사용할경우 editText의 aftertextchanged를 구현해 데이터와 UI의 데이터를 동기화시켜줘야한다.

 

이 외에도 setOnClickListenr, visible, enabled 이런 값들을 컨트롤 할 때도 viewBinding에서는 Activity에서 해당 값을 변경시키는 코드(명령형)를 작성해야 하지만 양방향 데이터 바인딩에서는 즉각적으로 가능(선언형)해진다.

 

향상된 타입 안정성

DataBinding은 컴파일시에 데이터 타입에 대해서 검사를 실행한다. 그렇기 때문에 런타임 오류의 가능성을 줄여 안정성을 높여준다.

 

LiveData(Flow)와의 쉬운 연결

ViewBinding의 경우 해당 값을 observe하는 코드를 Activity에 선언하고 하나씩 연결을 했다면 DataBinding에서는 손쉽게 연결이 가능해 UI구현이 쉬워진다.

 

 

 

 

 

그렇다면 어떤 단점이 있길래 별로 좋지 못한 평가를 받을까?

코드의 강한 결합 -> 관심사 분리 불가능

DataBinding을 사용할 경우 xml내부에 UI로직데이터 로직같이 들어가게 된다.

그렇게 된다면 아주 강한 결합을 가지게 되어 관심사 분리가 불가능해져 유지보수가 힘들어진다.

 

관심사 분리가 되어있지 않다면 어떻게 될까?

UI와 Data로직이 같이 있게 된다면 오류를 발견하기 힘들다. 한 파일에 다 섞여있다면 해당 원인을 찾는것 자체도 힘들어지고, 다른사람의 코드를 읽을 경우 이해하기 어려워진다.

 

 

테스트와 디버깅이 어려워짐

이 부분은 내가 직접적으로 해본적은 없다.

다만, 자료를 찾다보니 DataBinding은 generated code이기때문에 일반 코드와 비교했을 때 더욱 디버깅이 어렵다고 한다.

그래서 내장된 코드를 단위 테스트하는 것도 상대적으로 더 어렵다고 한다.

 

KAPT에 종속적

우리에게 익숙한 것은 Kotlin Annotation Processing Tool (KAPT)일 것이다. 하지만 KAPT는 유지보수모드다.

즉, 더이상 새로운 기능을 만들 계획이 없다.

 

이제는 Kotlin Symbol Processing API(KSP)를 사용하는 것이 권장된다.

 

하지만, DataBinding은 kapt에 의존적이다. 그러면 일부는 ksp를 쓰고, DataBinding과 관련된 부분은 kapt를 쓰면 되지않냐고 생각할 수도 있다. 하지만, 그렇게 하면 ksp의 성능 이점을 누릴 수 없다. ksp는 단독으로 사용해야 성능상 이점이 있다.

 

그러면 또 다른사람은 이렇게 말할것이다

"DataBinding이 ksp를 지원하면 되는거 아냐?"

그 질문에 대한 답변이 DataBinding의 다음 단점이다.

 

DataBinding 또한 유지보수모드

구글의 공식 입장이다.

 

DataBinding이 유지보수모드라는 것은 더이상 추가적인 기능개발을 하지 않는다는 것이다.

그렇다면 KSP를 지원하는 코드를 개발하지 않겠다는 뜻이다.

 

그리고 이 두 정보를 통해서 유추할 수 있는 것은 kapt가 지원중지, 즉 deprecated되면 DataBinding이 deprecated될 가능성이 있다.

 

 

 

 

이렇게 안좋은데 왜 가르쳐줬어요?

지금 적어둔 글만 본다면 DataBinding은 유지보수도 어렵고, 심지어 가까운 시일내에 deprecated될 수도 있는 기술이다. 

그런데 왜 사용법을 알려주며 써보라는 식의 글을 작성했을까?

 

 

단점이 있더라도 아주 강력하다.

보일러플레이트를 줄일 수 있고, 성능적 이점도 존재한다. 개인적으로는 코드 작업속도도 올라가고 참 편하다고 생각한다.

 

 

알고 안쓰는 것모르고 못쓰는 것은 다르다.

쓰지 않는 기술이라도 사용법을 알고 있냐 모르고있냐는 차이가 크다고 생각한다. 해당 기술을 알아가며 배우는것들이 다른 기술의 기반이 되어줄 거라 생각하고, 실제로 그렇게 느껴왔다.

 

 

DataBinding을 잘 보면 Compose와 참 비슷하다.

UI에 Data를 넣어두고 값이 변화면 자연스럽게 UI에 변화가 생긴다. 둘다 선언적인 UI를 사용하다보니 생긴 일이다.

지금 안드로이드를 공부하고 있다면 xml이 Compose보다 익숙할거다. 그렇다면 xml을 활용한 DataBinding을 보고 이해하다보면 Compose와의 공통점이 보일 것이고, Compose공부에도 도움이 될 것이다.

 

 

예전 코드라도 읽을 줄은 알아야 한다.

구글링을 할 때 내가 원하는 기술을 구현한 예제를 찾았다고 생각해보자.

근데 그 글이 DataBinding을 사용한 예제만 제공해주고 있다면, 근데 내가 DataBinding을 안배워서 모른다면.

얼마나 억울할까? 물론 요즘시대엔 AI보고 해석해달라면 해주긴 한다...

그래도 내가 안쓰더라도 쓸줄은 알아야 한다고 생각한다.

 

 

구글이 일하는 속도를 잘 생각해봐라.

그사람들... 정말 일하는속도 장난없다. 너무너무 느리다.

DataBinding이 deprecated되려면 몇년이 남았는지 알 수 없고 한참 더 걸릴 수도 있다.

그렇기 때문에 준비를 해두는 것은 좋은 일이다.

 

 

그리고 혹시 모르지 않은가?

우리가 취업했을 때 기존 코드가 DataBinding으로 되어있고, 그 코드를 ViewBinding 혹은 Compose로 Migration하는 작업을 하게 될지도?

 

 

 

 

 

 

그래서 작성자는 쓰시나요?

아니요. 전 안씁니다 ㅋㅋ

 

전 DataBinding을 좋아하고 정말 편하게 사용했었습니다. 하지만, DataBinding이 편했던 이유는 선언적 UI라는 부분때문이었지 xml에

연결하는 방식이 편했던 것은 아닙니다.

 

DataBinding은 Compose와는 다르게 Activity(fragment),  viewModel, xml 최소한 3가지가 필요해 여러파일을 읽는게 불편했습니다. 그리고 내가 익숙하지 않아서인지 DataBinding을 사용하면 코드에 UI와 비즈니스로직이 과하게 섞여있어 다른사람의 코드를 읽는데 리소스가 너무 많이 필요했다.

 

그래서 결국 같은 선언형 UI이지만, 더 익숙한 Kotlin을 활용한 Compose를 주로 사용합니다.

 

 

 

 

 

 

 

 

 

그러면 xml로 코드를 작성할때는 DataBinding 쓰시나요?

아뇨 ㅋㅋ ViewBinding씁니다.

 

물론 저도 DataBinding이 더 편합니다.

그래서 개인적으로 공부할때는 자주 사용했고, SOPT 33기 앱잼에서는 DataBinding을 애용하기도 했습니다.

 

 

하지만 팀 구성원 모두가 DataBinding을 사용한다는 전제하에 DataBinding을 사용해야한다고 생각합니다.

DataBinding을 사용하지 않는다면 일부는 ViewBinding, 일부는 DataBinding으로 되어있게됩니다.

그렇기 때문에 코드를 읽고 해석하는데 너무 많은 시간이 들어간다고 생각합니다.

사실 이건 DataBinding의 문제라기 보다는 이미 ViewBinding이 주류인 시장이라 그런 것 같습니다.

 

 

또한 저는 개인적으로 개발자는 최신 기술에 민감해야 한다고 생각합니다.

개발은 정말 트렌드가 빨라요. 그렇기 때문에 잘 쓰던 기술이 언제 기술이 사라질지 모르고, 언제 새로운 기술이 나올지 모릅니다.

그렇기 때문에 옛날 기술을 익히긴 익히되 사용하는건 최신기술을 사용하고자 노력합니다.

DataBinding이 ViewBinding보다 옛날에 나왔습니다!

 

 

 

 

 

 

어쩌다보니 글이 막판에 DataBinding 혐오가 된거같긴 한데 DataBinding도 정말 좋은 기술입니다!

한번정도는 써보고 ViewBinding과 DataBinding 중 취사선택 하시는 것을 추천드려요!!

728x90
반응형