밍맹의 생각날 때 적는 블로그

[Android/Kotlin] DataBinding(데이터바인딩) 1 - 기초 본문

안드로이드

[Android/Kotlin] DataBinding(데이터바인딩) 1 - 기초

mingmaeng 2020. 1. 24. 12:45

혼자서 데이터 바인딩을 학습했다. 중간에 헤맸던 부분이 너무 많아서 이제야 포스팅한다...

지금까지 학습한 부분을 예제를 통해서 알아보자.


데이터바인딩이란?

 우리는 안드로이드 어플의 한 화면을 만들 때, 뷰를 생성하고 그 뷰에 적절한 값들을 넣어준다.

평소처럼 개발을 한다고 생각하면 이런 식으로 액티비티에 뷰를 바인딩시켜주는 작업을 한다.

val button : Button = findViewById(R.id.btnSample)

한 두 개라면 괜찮지만 한 화면의 뷰가 10개 이상 넘어간다고 했을 경우 굉장히 막막하다....

코틀린은 extenstion을 이용해 편하게 작업이 가능하긴 하나, 이 또한 모든 바인딩을 액티비티에서 해주므로 내가 현재 공부하려는 MVVM패턴에서는 적절치 못하다.

그래서 데이터 바인딩을 사용해 주는 것이고, 데이터바인딩을 간단하게 얘기하면 다음과 같다.

  1. findViewById()를 사용하지 않아도 된다. 자동으로 xml에서 만든 View들을 만들어준다.
  2. RecyclerView 사용 시 각각의 item들을 set 해주지 않아도 알아서 xml에서 처리할 수 있다.
  3. Observable을  이용해 실시간으로 데이터를 바꿔줄 수 있다.

그럼 이제 어떻게 사용해야 하는지 알아보자.


기본 세팅

 데이터바인딩을 사용하기 위해서는 먼저 gradle 세팅을 해줘야 한다. build.gradle(Module : app)으로 들어가 다음과

같이 작성해 준다.

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

Java로 할 경우 다음과 같이 넣어주기만 해도 잘 작동하지만 Kotlin은 추가적인 세팅이 필요하다.

다음과 같은 코드를 추가해준다.

apply plugin: 'kotlin-kapt'
...
dependencies {
	...
    kapt 'com.android.databinding:compiler:3.5.3'
	...
}

여기서 3.5.3은 본인의 gradle 버전에 맞게 작성해주면 된다.

맨 첫번째 줄의 3.5.3을 입력해 준 것이다.


사용법

데이터 바인딩은 xml에서 직접 데이터를 바인딩하는 작업이다. 그러기 위해선 xml을 조금 손 봐줄 필요가 있다.

activity_main.xml

<?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>
    ...
    </data>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <Button
            android:id="@+id/btnSample"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="button"
         />

    </LinearLayout>
</layout>

 

기존 xml 태그 최상위에 <layout>이라는 태그로 전체를 감싸준다. 그리고 <data> 태그 안에는 xml에서 사용할 변수들을
<variable> 태그를 이용해 작성해주면 된다.

 

MainActivity.kt

private lateinit var binding: ActivityMainBinding
...

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

MainActivity에서도 변화가 있다. 기존에 존재했던 setContentView가 다른 걸로 대체되어있다.

xml의 이름 기준으로 ~~~ Binding이라는 클래스가 자동으로 만들어진다.

이름은 파스칼 표기법을 기준으로 하며 'activity_main.xml' 이기 때문에 ActivityMainBinding이라는 클래스가

만들어진 것이다.

xml의 변수를 참조하고 싶을 경우에는 'binding. 뷰 이름'으로 불러오면 된다.


버튼 클릭 이벤트

간단한 예제로 버튼 클릭 이벤트를 구현해보자. 위에 작성한 xml을 그대로 사용하겠다.

먼저 MainActivity에서 버튼 클릭 시 호출할 함수를 만든다.

MainActivity.kt

 fun btnClick(view : View){
        Toast.makeText(this,"Button Click",Toast.LENGTH_SHORT).show()
        //이 안에 토스트 메세지가 아니더라도 원하는 함수를 써도 된다.
    }

버튼이 잘 클릭된다는 것을 보여주기 위해서 필자는 토스트 메시지를 출력했다.

액티비티에서 함수를 작성했다면 xml에서 추가 작업을 해주면 된다.

 

기존 같은 경우에는 액티비티에서 직접 리스너를 달아주었을 것이다. 코틀린은 익스텐션을 이용해 리스너를 달아주었다.

 btnSample.setOnClickListener { 
            //클릭 시 이벤트
        }

데이터 바인딩에서는 단순 클릭 함수만 만들어 놓고, 이 함수를 쓰는지에 대해서는 xml에서 설정해준다.

activity_main.xml

...
<data>
        <variable
            name="activity"
            type="org.techtown.databinding.MainActivity" />
</data>
...

<data> 태그 안에 <variable>로 activity라는 이름의 변수를 설정해 주었다. type은 해당 경로를 나타낸다.

 

  ...
  <Button
            android:id="@+id/btnSample"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="button"
            android:onClick="@{activity::btnClick}"/>
 ...

기존에 존재했던 btnSample이라는 이름의 버튼에 다음과 같이 onClick 속성을 추가해준다.

여기서 주의해야 할 점은 "@{변수 이름 :: 함수 이름}"이다. 함수 이름 같은 경우는 실제 액티비티에 존재하는 함수 이름과 일치해야 하며, 반드시 :: 을 써야 한다.

 

마지막으로 액티비티에서 해당 변수에 어떠한 값을 넣어줄 것인지 작성을 해줘야 한다.

MainAcitivity.kt

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.activity = this@MainActivity
    }
    ...

binding.activity를 이용해서 해당 변수에 MainActivity를 넣어준다. MVVM패턴에서는 ViewModel을 이용해

클릭 이벤트에 맞는 동작을 수행시키면 된다. (고건 좀 더 나중 이야기....)

 


RecyclerView에 DataBinding 사용하기

버튼 밑에 리사이클러뷰

RecyclerView에서 각각의 아이템을 데이터 바인딩을 이용해 사용하려고 한다. 예제를 천천히 보면서 따라 해 보길 바란다.

 

ProfileData.kt

data class ProfileData(
    var name : String,
    var age : Int
)

프로필 이름과 나이를 저장하는 data class를 만든다. 간단한 예제이기 때문에 지금은 이 두 개의 변수만 설정한다.

 

activity_main.xml

<?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="activity"
            type="org.techtown.databinding.MainActivity" />


    </data>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <Button
            android:id="@+id/btnSample"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="button"
            android:onClick="@{activity::btnClick}"/>

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/mainRcv"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

    </LinearLayout>


</layout>

위에서 실습한 xml에 추가로 리사이클 러뷰를 만들어준다.

 

rcv_list_item.xml

<?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="user"
            type="org.techtown.databinding.ProfileData" />

    </data>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:gravity="center_vertical">


        <TextView
            android:id="@+id/item_name"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            tools:text="Title"
            android:textSize="30sp"
            android:layout_weight="1"
            android:gravity="center_vertical"
            android:paddingLeft="10dp"
            android:text="@{user.name}"/>

        <TextView
            android:id="@+id/item_age"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            tools:text="Age"
            android:textSize="20sp"
            android:gravity="center_vertical"
            android:padding="10dp"
            android:text="@{Integer.toString(user.age)}"/>

    </LinearLayout>

</layout>

아이템을 실제로 보여주는 화면인 rcv_list_item.xml을 만들어준다. user라는 이름의 변수로 <data> 태그 안에 만들어 주고, 값들이 들어갈 텍스트뷰에 맞춰 ProfileData에 존재하는 변수를 넣어준다.

 

이제 뷰 홀더를 만들어주는데, 기존 뷰 홀더와 어떠한 차이가 있는지 확인해보자.

 

기존 ViewHolder

class BaseVH(view : View) : RecyclerView.ViewHolder(view){
        val name : TextView = view.findViewById(R.id.item_name)
        val age : TextView = view.findViewById(R.id.item_age)

        fun onbind(data : ProfileData){
            name.text = data.name
            age.text = data.age.toString()
        }

}

기존의 작성하던 뷰 홀더였다면 이런 식으로 코드를 작성해서 뷰에 값들을 넣어주었을 것이다.

그러나 데이터 바인딩을 사용하게 되면 다음과 같이 바뀐다.

 

데이터 바인딩을 사용한 ViewHolder

 class ProfileVH(val binding : RcvListItemBinding) : RecyclerView.ViewHolder(binding.root){
        fun onBind(data : ProfileData){
            binding.user = data
        }

    }

굉장히 코드가 짧아진다. RcvListItemBinding은 파스칼 표기법으로 바뀐 rcv_list_item.xml의 클래스다.

user라는 변수에 어댑터를 통해 전달돼 온 ProfileData를 넣어준다. 그리고 View타입을 받는 것이 아니라

바인딩된 클래스를 받는 것도 하나의 차이점이다.

 

다음은 어댑터를 만들어 줄 차례이다. 어댑터에서는 onCreatViewHolder에서 차이점이 생긴다.

 

기본 Adapter

class BaseAdapter (val context : Context) : RecyclerView.Adapter<BaseAdapter.BaseVH>(){

    var data = listOf<ProfileData>()
    ...
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseAdapter.BaseVH {
        val view  = LayoutInflater.from(context)
            .inflate(R.layout.rcv_list_item, parent, false)
        return BaseVH(view)
    }
    ...
}

기존과 같이 어댑터를 작성했다면 onCreateViewHolder는 다음과 같았을 것이다.

 

ProfileAdapter.kt

class ProfileAdapter (private val context : Context) : RecyclerView.Adapter<ProfileAdapter.ProfileVH>(){

    var data = listOf<ProfileData>()

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProfileVH {
      val binding = RcvListItemBinding.inflate(
          LayoutInflater.from(context), parent, false)

        return ProfileVH(binding)
    }
    
    ...
}

데이터 바인딩을 사용하면 뷰 홀더에 View를 보내주는 것이 아닌 바인딩 클래스를 보내줘야 하므로 코드도 그에 맞춰 바뀌게 된다.

 

데이터 바인딩을 사용한 어댑터의 코드 전문을 보면서 천천히 살펴보자.

package org.techtown.databinding

import android.content.Context
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import org.techtown.databinding.databinding.RcvListItemBinding

class ProfileAdapter (private val context : Context) : RecyclerView.Adapter<ProfileAdapter.ProfileVH>() 
{

    var data = listOf<ProfileData>()

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProfileVH {
      val binding = RcvListItemBinding.inflate(
          LayoutInflater.from(context), parent, false)

        return ProfileVH(binding)
    }

    override fun getItemCount(): Int = data.size

    override fun onBindViewHolder(holder: ProfileVH, position: Int) {
        holder.onBind(data[position])
    }

    class ProfileVH(val binding : RcvListItemBinding) : RecyclerView.ViewHolder(binding.root){
        fun onBind(data : ProfileData){
            binding.user = data
        }

    
}

 

마지막으로 MainActivity에서 다음과 같이 설정해주면 끝이다.

MainActivity.kt

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

    override fun onCreate(savedInstanceState: Bundle?) {
    	...
        setRcv()
        ...
    }
    
    ...
    
	fun setRcv(){
        val profileAdapter = ProfileAdapter(this)
        binding.mainRcv.layoutManager = LinearLayoutManager(this)
        binding.mainRcv.adapter = profileAdapter
        profileAdapter.data = listOf(
            ProfileData(name = "Kang", age = 26),
            ProfileData(name = "Kim", age = 25)
        )
        profileAdapter.notifyDataSetChanged()
    }
}

지금은 직접 데이터를 넣어주었으나 나중에 통신을 하게 되면 다른 방식으로 데이터를 넣어주게 될 것이다.

 

다음은 MainActivity 코드 전문이다.

 

package org.techtown.databinding

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import android.widget.Button
import android.widget.Toast
import androidx.databinding.DataBindingUtil
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.activity_main.*
import org.techtown.databinding.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.activity = this@MainActivity
        setRcv()
    }

    fun btnClick(view : View){
        Toast.makeText(this,"Button Click",Toast.LENGTH_SHORT).show()
    }

    fun setRcv(){
        val profileAdapter = ProfileAdapter(this)
        binding.mainRcv.layoutManager = LinearLayoutManager(this)
        binding.mainRcv.adapter = profileAdapter
        profileAdapter.data = listOf(
            ProfileData(name = "Kang", age = 26),
            ProfileData(name = "Kim", age = 25)
        )
        profileAdapter.notifyDataSetChanged()
    }

}

MVVM패턴을 적용하기 위해서 데이터 바인딩을 사용해야 한다. 그 작업을 하기 위한 첫 발을 뗐는데, 사실 이건 기초 중의 기초이고 조금 더 공부를 할 필요가 있다.

다음에는 Observable을 이용한 실시간 변화하는 데이터를 바인딩하는 것과, BindingAdapter에 대해서 알아보겠다.

Comments