본문 바로가기
Mobile : Android/Jetpack

[Jetpack AAC] Data Binding 기본 #1

by 신숭이 2022. 4. 12.

 

[Jetpack AAC] Data Binding 기본 #1

 

 

 

 

Jetpack은 2018년에 첫 공개된 안드로이드 개발에 도움을 주는 패키지 묶음이다. 기존의 구글에서 제공하던 서포트 라이브러리를 모아 통합한 것으로, 그중 AAC(Android Architecture Component)는 안드로이드 MVVM(Model-View-ViewModel) 패턴의 시대를 연 장본인이다. 그로부터 약 4년이 지난 지금, MVVM은 이제 대세다. MVVM을 시작하기 위해선 뭐부터 시작해야 할까? 바로 데이터 바인딩(Data Binding) 이다. 

 

데이터 바인딩은 선언적 형식으로 UI와 데이터 소스를 연결하는 행위를 말한다. 이게 뭔 소린가? 그동안 UI 구성하고 데이터 소스 연결 잘해왔는데? 선언형 UI란 무엇일까. 일단 그동안 해온 것은 명령형 UI다. 명령형 UI의 가장 큰 문제점은 재사용성과 확장성이 매우 떨어진다는 것이다. 기존 안드로이드 프레임워크에서 명령형으로 UI를 만들기 위해선 개발자가 .kt(.java) 파일에서 UI 를 그리는 방법상태 및 로직을 정의하고, XML 파일에서 어떤 것을 그릴지 전부 코딩하였다. 그래서 개발을 하다 보면 (특히 XML 작업 및 뷰와 activity를 연결할때) 너무 반복적인 작업이 많고, "내가 지금 노가다를 하고 있군" 이런 느낌을 받을 때가 많았다.

 

그래서 등장한 것이 선언형 UI로 이러한 방식은 어떤것을 그릴 지만 정의해두면 프레임워크가 알아서 처리한다. 이런 방식은 XML 파일과 .kt 파일 두 개 아닌, 하나의 코드로 UI 를 그리는 것이 가능하게 하여 이러한 UI 작업이 대폭 간소화되었다. 그렇게 탄생한 것이 안드로이드의 Jetpack ComposeFlutter 이다. 선언형 UI에 대한 이야기는 여기서 더 할 필요는 없어 보인다. 사실 Data Binding은 완전히 선언형 UI라고 할 수 없다. 여전히 XML과 .kt 파일 두 곳에서 작업해야 하기 때문이다. 다만 많은 부분을 XML에서도 작업할 수 있게 되어 (XML 내 변수 선언 및 처리 등) Acitivty 에서 작업해야 할 코드의 양이 대폭 줄어들었다. 즉 Activity 에서 해야 할 일을 XML 단계에서 어느 정도 작성해보자!라는 것이 데이터 바인딩 (Data Binding) 이다.

그럼 완전한 선언형 UI도 아니고 이걸 왜 쓸까? 여기서는 뇌피셜이긴한데, 레거시 코드 때문인 것 같다. 각 기업에서 기존에 작업해놓은 수백만줄이 넘는 코드를 갑자기 Jetpack Compose 나 Flutter 로 완전 탈바꿈하는 것은 쉽지 않다. 따라서 어느 정도 선언형 편의성을 갖춘 과도기적 기술인 데이터 바인딩이 선언형 UI로 넘어가는 시기를 커버하는 것으로 보인다. 여담으로 선언형 UI인 Flutter는 위와 같은 상황을 layout.xml 은 Widget Class가 activity.java 는 State Class가 담당하여 하나의 파일에서 처리하도록 구성할 수 있다.

 

 

 

 

 

 

데이터 바인딩 (Data Binding) 사용

 

 

단순히 액티비티에서 뷰(View)만 좀 더 깔끔하게 참조해보자 한다면, 뷰 바인딩(View Binding) 만으로도 충분하다. 데이터 바인딩은 액티비티에서 XML의 객체를 가져다 쓰는 것은 기본이고, XML에서도 Acitivity의 객체를 가져다 써보자!이다. 즉 뷰 바인딩(View Binding)은 데이터 바인딩(Data Binding)의 부분집합!

https://full-stack.tistory.com/13

 

[Jetpack AAC] View Binding 정리

[Jetpack AAC] View Binding 정리 핵심 : findViewById 를 쓰지 않는다. 뷰 바인딩은 각 XML 레이아웃 파일에 해당하는 바인딩 클래스(Binding Class)를 자동 생성하여 뷰에 대한 직접 참조를 가능케 한다. 기존에.

full-stack.tistory.com

 

데이터 바인딩을 쓰고자 한다면, 우선 build.gradle 파일로 가서 android 영역에 다음과 같이 작성하자.

 

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

 

그리고 데이터 바인딩을 쓰고자 하는 레이아웃 파일의 루트를 다음과 같이 layout으로 한다.

 

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable name="MyData" type="com.rkqnt.databindingpractice.MainActivity" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        
        .. 중략

 

databinding =  enable 설정된 상태 다음과 같이 우클릭 및 툴팁에서 빠르게 바꿀 수 있다. (레거시에 유용)

 

여기서 <data> 태그가 데이터 바인딩의 핵심이다. 우리는 여기에 변수(<variable>)를 선언하고, 이 변수와 데이터 객체와 연결(바인딩)하여 활용한다. <variable name="MyData" type="com.rkqnt.databindingpractice.MainActivity" />

는 MyData 라는 변수를 MainActivity 타입으로 선언하는 것이다. 이제 액티비티를 보면,

 

class MainActivity : AppCompatActivity() {
    lateinit var binding: ActivityMainBinding
    var name = "Dong Han"     // XML에서 사용(바인딩, 대입)하기 위해선 public 선언되어야
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        //setContentView(R.layout.activity_main)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
        binding.myData = this
    }
}

 

액티비티에서는 위와 같이 binding 객체로 View 바인딩을 수행하여 View 객체를 참조할 수 있도록 한다.

기존에 setContentView 와 다르게 DataBindingUtil을 이용하면 된다. 그리고 연결하고자 하는 객체는 MainActivity 자체 이므로, binding 에서 정의한 변수 myData 에 this로 자신을 가리키게 한다. 그럼 myData는 이제 name이라는 데이터를 다음과 같이 사용할 수 있다.  주의할 점은 사용될 변수는 반드시 Public 으로 지정되어야 한다.

 

* 뷰 바인딩은 아래와 같이해도 된다

binding = ActivityMainBinding.inflate(layoutInflater) 
setContentView(binding.root)

 

 

        <TextView
            android:id="@+id/textView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{MyData.name}"/>

 

여기서 나는 MyData 라고 첫 글자를 대문자로 했는데? 왜 .kt에선 myData 냐 할 수 있다. 이건 안드로이드 프레임워크가 정한 규칙으로 XML에서 가져오는 변수명은 모두 첫글자 소문자로 되어있다. 그리고 코틀린 쓰자. 자바는 바인딩해도 지저분하다. (게터 세터 설정할 때 극혐 유발)

 

즉 데이터 바인딩은 화면에서 처리해야 할 데이터 처리 , 이벤트 처리는 모두 XML에서 하고 자바나 코틀린 파일에서는 데이터 작업 및 로직 중심의 코드만 작성하자는 것

 

 

 

 

 

 

 

 

모델 클래스 바인딩과 @{ }표현식

 

 

지금부터 기본적인 바인딩 몇 가지를 살펴보겠다. 모델 클래스 바인딩은 데이터 클래스를 XML 파일에서 사용할 수 있도록 바인딩하는 것이다. 다음과 같은 데이터 클래스가 있다고 하자.

 

// User.kt
data class User(
    var firstName:String,
    var lastName:String,
    var phone:String,
    var email:String="a@naver.com"
)

 

이제 XML에서도 해당 데이터 클래스를 타입으로 하는 변수를 선언할 수 있다.

 

    <data>
        <variable name="MyData" type="com.rkqnt.databindingpractice.MainActivity" />
        <variable name="User" type="com.rkqnt.databindingpractice.User" />
    </data>

 

위와 같이 준비가 끝나면 바인딩을 수행할 수 있다.

 

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
        binding.myData = this

        binding.user = User("DongHan","Park","0101010101")
    }

 

이제 아래와 같이 쓸 수 있다.

 

    <TextView
        android:id="@+id/textView3"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@{User.firstName}" />

 

데이터 바인딩을 하면 애뮬레이터에 돌려보지 않는 한 화면 미리보기가 불가능한 단점이 있다. 이는 tools:text="" 속성을 이용하면 어느 정도 커버가 가능하다.

 

        <TextView
            android:id="@+id/textView2"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{User.firstName}"
            tools:text="Park"/>

 

두 가지 예시를 통해 보면 뷰에 데이터 바인딩을 하는 것은 @{ 표현식 } 코드를 필요로 한다. 표현식에는 다양한 연산자와 키워드를 활용할 수 있다. 

 

 

연산자는 다음과 같이 사용할 수 있다.

 

        <TextView
            android:id="@+id/textView7"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="68dp"
            android:background='@{User.lastName=="Park" ? @color/red : @color/blue}'
            android:text="@{User.lastName}"
            android:textColor="@color/white" />

 

텍스트 뷰의 배경을 삼항 연산자로 결정해보았다. 이제 액티비티에서 이 작업을 할 필요가 없다. 여기서 주목할 점은 문자열은 "" 큰따옴표로 속성 값 입력하는 방식과 동일하기에, 작은따옴표와 큰 따옴표를 적절히 활용하며 사용하면 된다.

 

다음과 같이 %s와 같은 형식 문자를 이용해 문자열 리소스를 바인딩할 수도 있다. (string.xml)

 

<resources>
    <string name="strFormat">포맷으로 문자열 출력 1 : %s , 출력 2 : %s</string>
</resources>
<TextView
        android:id="@+id/textView7"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="68dp"
        android:background='@{User.lastName=="Park" ? @color/red : @color/blue}'
        android:text="@{@string/strFormat(User.firstName,User.lastName)}"
        android:textColor="@color/white" />

 

레이아웃에서 위와 같이 사용하면 된다. @string/strFormat(User.firstName,User.lastName) 

 

 

 

 

 

 

 

컬렉션 타입 데이터 바인딩

 

 

 

컬렉션도 동일한 방식으로 쓰인다. 다음과 같이 액티비티에 컬렉션이 선언되어 있고 액티비티 클래스가 myData 변수로 바인딩되어있다고 하자.

 

class MainActivity : AppCompatActivity() {
    lateinit var binding: ActivityMainBinding

    var intArray = arrayOf(1,2,3)
    var stringArray = arrayOf("Alpha","Beta")

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
        binding.myData = this
        
        ..

 

다음과 같이 쓸 수 있다. 주의할 점은 정수형(Integer) 데이터는 문자형으로 변환을 해야 한다. 변환은 Java에서 하던 것처럼 표현식@{}내에 작성하면 된다.

 

 <TextView
    android:id="@+id/textView4"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="@{MyData.stringArray[0]}" />

<TextView
    android:id="@+id/textView5"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="@{Integer.toString(MyData.intArray[0])}"

 

참고로 HashMap과 같은 컬렉션은 다음과 같이하면 된다. 작은따옴표 대신 &quot 를 써도 되는데 Fancy하지 않다.

 

<TextView
    android:id="@+id/textView5"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text='@{MyData.map.get("one")}'

 

"@{MyData.map.get('one')}" 으로 해도 된다.

 

 

 

 

 

 

 

글이 너무 길어져서 이벤트 바인딩(Event Binding) 부터는 다음 포스트에서 이어 작성하겠다.

 

 

 

댓글