[안드로이드] 데이터 결합(Data Binding)

0

Android

목록 보기
6/16
post-thumbnail
post-custom-banner

개요

데이터 바인딩은 레이아웃의 뷰를 앱 코드에 저장된 데이터와 연결하는 간단한 방법을 제공하는 라이브러리입니다.

예를 들어, EditText 뷰를 ViewModel의 LiveData 와 연결한다고 가정해봅시다.

여기서 단방향 바인딩을 사용하게 된다면, LiveData가 변경되는 즉시 EditText의 값도 자동으로 해당 값으로 바뀌게 됩니다.

만약 양방향 바인딩을 사용하게 된다면, EditText에 작성된 값이 자동으로 LiveData의 값을 갱신할 수도 있습니다.

여기서 뷰모델 데이터를 UI뷰에서 참조할 수 있는 방법을 단방향 바인딩이고, 반대로 UI뷰의 데이터를 뷰모델의 데이터가 참조하고 갱신할 수 있는 방법이 양방향 바인딩입니다.

EditText 뿐만 아니라, Button 위젯과 같은 뷰에서 데이터 바인딩을 사용하게 된다면, setOnClickListener 를 작성하지 않고, UI 컨트롤러의 함수에 직접적으로 바인딩할 수도 있습니다.

사용 방법

프로젝트 빌드 구성

프로젝트에서 데이터 바인딩을 사용하려면, app 수준의 build.gradle에 다음과 같은 항목을 작성하여 데이터 바인딩을 사용할 수 있도록 구성해야합니다.

android {
	.
    .
    buildFeatures {
    	dataBinding true
    }
}

데이터 바인딩 레이아웃 파일

앱의 UI는 XML 레이아웃 파일에 포함됩니다. 데이터 다인딩을 사용하려면 기존 XML레이아웃 파일을 데이터 바인딩 레이아웃 파일로 변환해야 합니다.

변환하기 전의 XML레이아웃 파일을 보면 다음과 같습니다.


<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".ui.main.MainFragment">

        <EditText
            android:id="@+id/dollerText"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="89dp"
            android:ems="10"
            android:inputType="number"
            android:minHeight="48dp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

    </androidx.constraintlayout.widget.ConstraintLayout>

이곳에서는 ConstraintLayout이 루트 뷰가 됩니다. 그러나 데이터 바인딩을 사용하려면 다음 코드와 같이 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:id="@+id/main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".ui.main.MainFragment">


        <EditText
            android:id="@+id/dollerText"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="89dp"
            android:ems="10"
            android:inputType="number"
            android:minHeight="48dp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

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

레이아웃 파일 data 요소

레이아웃 파일의 뷰는 프로젝트 내부의 클래스(예를 들어 ViewModel)와 바인딩 되어야 하고, 이 클래스 이름을 바인딩 레이아웃에 선언해주어야 합니다.

다음 코드는 레이아웃 파일에 ViewModel 클래스를 선언해주는 코드입니다.

<?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="myViewModel"
            type="com.example.viewmodelexample.ui.main.MainViewModel" />
    </data>
----------------------------------------------
    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".ui.main.MainFragment">


        <EditText
            android:id="@+id/dollerText"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="89dp"
            android:ems="10"
            android:inputType="number"
            android:minHeight="48dp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

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

여기서 추가적으로 사용자에게 보여 주기 전의 값에 대해 호출될 필요가 있는 클래스는 다음과 같이 import할 수 있습니다.

<data>
  	<import type="com.example.viewmodelexample.MyFormattingTools" />
    <variable
        name="myViewModel"
        type="com.example.viewmodelexample.ui.main.MainViewModel" />
</data>

바인딩 클래스

data 요소에서 참소하는 각 클래스에 대응되는 바인딩클래스는 안드로이드 스튜디오에서 자동으로 생성해줍니다.

이 클래스 이름은 ViewDataBinding 클래스의 서브클래스이며, 이는 레이아웃 파일 이름을 대문자로 바꾸고, 제일 뒤에 Binding을 붙입니다. 예를 들어, activicy_main 이라는 파일이 있다면, 이것의 바인딩 클래스는 MainActivityBinding이 되는 것이죠.

이제 이 바인딩 클래스를 액티비티에서 인스턴스화 해주어야합니다.

기존에는 onCreate()안에 다음과 같은 코드로 레이아웃 파일을 인플레이트 했습니다.

setContentView(R.layout.activity_main)

만약 액티비티에서 바인딩 클래스를 인스턴스화 할때는, 위 코드 대신 초기화 코드를 다음과 같이 입력해주어야합니다.

lateinit var binding: ActivityMainBinding

binding = DataBindUtil.inflate(
	inflater, R.layout.activity_main, contatiner, false)

이제 ViewModel을 인스턴스화 한 다음, 앞서 레이아웃 파일에 선언했던 변수에 ViewModel 인스턴스를 저장해 주면 됩니다. 다음과 같은 코드로 말이죠.

var viewModel: MainViewModel = 
	ViewModelProvider(this).get(MainViewModel::class.java)
binding.setVariable(myViewModel, viewModel)

바인딩 표현식(단방향)

바인딩 표현식으로 ViewModel에 저장된 어떤 데이터 값이 어떤 형식으로 TextView에 나타나는지 정의할 수 있다.

바인딩 표현식은 @로 시작하여 중괄호 안에 표현식을 넣어서 표현합니다.

<TextView
android:id="@+id/resultText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
          
android:text='@{myViewModel.result}'
          
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"            
app:layout_constraintTop_toTopOf="parent" />

safeUnBox

만약 float타입의 값을 text뷰에 띄우고 싶다면, 표준 자바 언어의 String 클래스를 사용하여 다음과 같이 나타낼 수 있습니다.

android:text="@{String.valueOf(myViewModel.result)}"

그후 실행을 하게 된다면, 안드로이드 콘솔에 다음과 같은 경고 문구가 뜹니다.

warning: myViewModel.result.getValue() is a boxed field but needs to be un-boxed to execute String.valueOf(viewModel.result.getValue()).

메세지를 해석해보면 ViewModel에서 가져오는 값은 boxed 타입인데 String.valueOf() 함수를 사용하려면 unboxed 타입으로 넣어야 한다는 말입니다..

자바에서는 int와 같은 기본타입과, Integer과 같은 객체 타입이 있습니다. 여기서 unboxed 타입은 기본타입을 뜻하는 것이고, boxed 타입은 객체타입을 지칭하고 있습니다.

그렇기 때문에 String.valueOf() 함수는 unboxed타입을 인자로 받기 때문에 안에 들어가는 값을 safeUnbox() 함수를 이용하여 안전하게 기본타입값으로 추출해야합니다.

다음 코드와 같이 말이죠.

android:text="@{String.valueOf(safeUnbox(myViewModel.result))}

바인딩 표현식(양방향)

단방향 표현식과 반대되는 개념으로, 지금까지 배운 단방향은 대응되는 데이터를 읽어들여 TextView에 표시했다면 양방향은 반대로 TextView에있는 내용을 대응되는 데이터에 갱신해주는 것입니다.

사용 방법은 단방향 표현식과 유사합니다. 단방향 표현식에서 '@'를 '@='로 바꿔주시면 됩니다.

android:text="@={myViewModel.result}"

이벤트와 리스너 바인딩

데이터 바인딩으로 뷰의 이벤트에 대한 응답으로 함수를 호출할 수 있습니다.

android:onClick="@{uiController::convertCurrency}"

위 코드는 uiController 변수가 가르키는 뷰에서 convertCurrency 함수를 호출하는 것입니다.

다음은 리스너 바인딩이 있습니다. 이는 인자(argument)를 전달하면서 함수를 호출할 수 있습니다.

android:onClick="@{() -> myViewModel.method(viewModel.result, 10)}"
post-custom-banner

0개의 댓글