깨알 개념/Android

[Android] Data Binding - 기초

interfacer_han 2024. 1. 11. 16:40

#1 데이터 바인딩 사용 전

#1-1 예시 앱

위과 같은 간단한 앱이 있다. Button을 누르면, EditText의 text가 바로 위에 있는 TextView의 text에 대입된다. 이 앱의 코드는 다음과 같다.
 

#1-2 activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<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">

    <TextView
        android:id="@+id/my_text_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="25dp"
        android:textSize="30dp"
        app:layout_constraintBottom_toTopOf="@id/my_container"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/my_container"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <EditText
            android:id="@+id/my_edit_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:ems="10"
            android:hint="Input any text to print"
            android:inputType="text"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toStartOf="@id/my_button"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <Button
            android:id="@+id/my_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Print"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toEndOf="@id/my_edit_text"
            app:layout_constraintTop_toTopOf="parent" />
    </androidx.constraintlayout.widget.ConstraintLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

 

#1-3 MainActivity.kt

// package com.example.databindingpractice

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
import android.widget.EditText
import android.widget.TextView

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val myTextView = findViewById<TextView>(R.id.my_text_view)
        val myEditText = findViewById<EditText>(R.id.my_edit_text)
        val myButton = findViewById<Button>(R.id.my_button)

        myButton.setOnClickListener{
            myTextView.text = myEditText.text
        }
    }
}

안드로이드를 처음 배울 때부터 사용했던 findViewById()가 있다. findViewById()는 view(보이는 화면)에 있는 레퍼런스(참조)를 확보하는 방법 중 가장 기초적인 방법이다. findViewById()는 런타임이 아니라 컴파일 시점에 view의 레퍼런스를 확보하기에, 적어도 런타임 중 view에 관한 에러는 나지 않게 보장한다는 장점이 있다. 하지만, TextView와 같은 위젯들의 갯수가 많아지면, 하나하나 findViewById()를 쓰며 뷰 계층에 접근하는 것이 번거로워진다. 동시에 코드의 가독성도 저하된다.

이 때, findViewById()의 장점을 유지하면서, 단점을 해소하는 방법이 있는데 바로 Binding object라는 객체를 사용하는 방법이다. Binding object는 각 layout에 들어있는 모든 view의 레퍼런스를 저장하는 객체다.
 

#2 데이터 바인딩의 기초적인 사용

#2-1 build.gradle.kts (Module :app)에서 데이터 바인딩 라이브러리 추가하기

plugins {
    ...
}

android {
    ...
    
    buildFeatures {
        dataBinding = true
    }
}

dependencies {
    ...
}

Binding object를 사용하기위해 먼저, Data Binding 라이브러리(androidx.databinding)를 다운로드해야 한다. 모듈 수준의 그래들 빌드 파일 build.gradle.kts (Module :app)에서 android {...} 안에, buildFeatures { dataBinding = true } 구문을 추가한다. 그래들 버전 4.0미만은 대신 dataBinding { enabled = true } 구문을 추가한다. 이러면 gradle이 알아서 androidx.databinding 라이브러리를 다운로드 한다. 구문을 추가하면 상단에 Sync를 하라는 안내 메시지가 뜨는데, 해당 메시지의 Sync 버튼을 누른다.
 

#2-2 activity_main.xml에 <layout> 태그 씌우기

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

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

        ...

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

<?xml version="1.0" encoding="utf-8"?>를 제외한 모든 부분을 <layout> 태그로 감싼다. <layout> 태그는 데이터 바인딩을 위한 레이아웃 파일임을 안드로이드 시스템에 알려준다. 이렇게하면, Data Binding 라이브러리가 자동으로 Binding class를 생성한다. 이제부터 Binding class에서 Binding object를 찍어낼 수 있다. 이 클래스의 이름은 abc_def_ghi.xml라면 AbcDeFGhiBinding으로 정해진다. 즉, _로 구분되는 단어들의 첫글자를 대문자로 바꾼 뒤에 "Binding"이 붙여진다.

xmlns 즉, xml 파일의 네임스페이스 선언(namespace declarations)은 xml 파일에서 가장 외곽(outmost)에 있는 태그의 속성으로 들어가 있어야 한다는 규칙이 있으므로, 원래 있던 자리에서 <layout> 태그로 옮겨준다.
 

#2-3 MainActivity.kt에서 Binding object 사용하기

// package com.example.databindingpractice

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.databinding.DataBindingUtil
import com.example.databindingpractice.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {
    private lateinit var binding : ActivityMainBinding
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
        
        binding.myButton.setOnClickListener{
            binding.myTextView.text = binding.myEditText.text
        }
    }
}

Activity에서 바인딩 클래스를 사용하는 모습이다. androidx.databinding.DataBindingUtil 클래스의 setContentView()로 초기화를 한다. 원래의 setContentView()는 androidx.appcompat.app.AppCompatActivity의 메소드였다. 하지만, 이름이 같다는 것에서 알 수 있듯 하는 일은 '비슷'할 것임을 유추할 수 있다. binding 객체에는 ActivityMainBinding 클래스의 위젯 id들 즉 activity_main.xml의 위젯 id 정보가 프로퍼티로서 담겨져 있다.

그 프로퍼티의 이름은 Binding 클래스의 이름 규칙과 비슷하게 자동 생성되어있다. xml에서 위젯의 android:id 속성이 "@+id/abc_def_ghi"로 설정되어있다면 abcDefGhi가 프로퍼티명이 된다.
 
결론적으로, findViewById로 레퍼런스를 하나하나 불러와서 변수 이름을 하나하나 붙이는 방식에 비해 소스 코드 작성 속도가 더 빨라졌으며, 가독성도 좋아졌다.
 

#2-4 apply 함수 사용

/*
binding.myButton.setOnClickListener{
    binding.myTextView.text = binding.myEditText.text
}
*/

binding.apply {
    myButton.setOnClickListener {
        myTextView.text = myEditText.text
    }
}

코틀린의 scope 함수들 중 하나인 apply를 활용하면 위과 같이 코드를 더욱 간소화시킬 수 있다.
 

#3 요약

Data Binding은 findViewById()의 일괄 설정이다.

 

#4 완성된 앱

https://github.com/Kanmanemone/android-practice/tree/master/data-binding/DataBindingBasics