본문 바로가기

안드로이드

[안드로이드] 예제:MVVM+AAC를 이용한 RecyclerView 5 - BindingAdpater

BindingAdapter

1~4까지의 문서를 통해 리사이클러뷰의 큰 뼈대가 완성되었다. 이번엔 리사이클러뷰 아이템 xml에 ImageView를 삽입하여 이미지를 데이터 바인딩하려고 한다. 이미지를 바인딩하기 위해서는 BindingAdapter가 필요하다. BindingAdapter란 xml의 특정한 속성 값을 세팅하기 위한 라이브러리이다. 예를들어 위에서 설명한 이미지뷰의 이미지 url 설정 등이 있다.


잘못된 코드

아래 코드를 보면 String 값을 그대로 imageSrc에 삽입하고 있다. 이는 바로 오류를 뿜을 것이다.

    <data>
        <variable
            name="todo"
            type="com.example.todo"/>
    </data>
    <ImageView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:src="@{todo.imageUrl}"
    />

정상적인 코드

BindingAdapter를 위한 object를 만들고 annotation으로 정의해주어야 한다. 이때 메서드의 첫번째 변수는 BindingAdpater를 적용할 view이고, 두번째부터 view를 세팅하기 위한 매개변수가 들어온다.

 

BindingAdpater.kt 추가

package com.example.sampleapp

import android.widget.ImageView
import androidx.databinding.BindingAdapter
import com.bumptech.glide.Glide

object BindingAdapter {
    @BindingAdapter("imageUrl")
    @JvmStatic
    fun loadImage(imageView: ImageView, url: String){
        Glide.with(imageView.context).load(url).error(R.drawable.ic_launcher_background).into(imageView)
    }
}

(이미지 로드 라이브러리로 Glide를 사용하였다.)

 

item_todo.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="todo"
            type="com.example.sampleapp.Todo" />
    </data>

    <androidx.cardview.widget.CardView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="4dp">

        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">
            <ImageView
                android:layout_width="50dp"
                android:layout_height="50dp"
                android:id="@+id/item_tv_initial"
                android:padding="4dp"
                android:layout_marginTop="16dp"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                android:layout_marginStart="16dp"
                android:gravity="center"
                app:layout_constraintBottom_toBottomOf="parent"
                android:layout_marginBottom="16dp"
                app:imageUrl="@{todo.imageUrl}"/>

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:id="@+id/item_tv_title"
                android:textSize="20dp"
                android:textStyle="bold"
                app:layout_constraintStart_toEndOf="@+id/item_tv_initial"
                android:layout_marginStart="16dp"
                tools:text="@{todo.title.toString()}"
                android:layout_marginTop="8dp"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintBottom_toTopOf="@+id/item_tv_descript"/>

            <TextView
                android:id="@+id/item_tv_descript"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="4dp"
                app:layout_constraintTop_toBottomOf="@+id/item_tv_title"
                app:layout_constraintStart_toStartOf="@+id/item_tv_title"
                android:layout_marginBottom="8dp"
                app:layout_constraintBottom_toBottomOf="parent"
                tools:text="@{todo.description.toString()}"/>
        </androidx.constraintlayout.widget.ConstraintLayout>
    </androidx.cardview.widget.CardView>
</layout>

 

Todo.kt 수정

@Entity
data class Todo(
    //autoGenerate는 null을 받으면 ID 값을 자동으로 할당해줌
    @PrimaryKey(autoGenerate = true)
    var id: Int?,

    @ColumnInfo(name ="title")
    var title: String,

    @ColumnInfo(name="description")
    var description: String
    
    //새로 추가된 컬럼
    @ColumnInfo(name="imageUrl")
    var imageUrl: String
    )
{
    constructor(): this(null,"","","")
}

참고로 원래는 Room 데이터베이스의 스키마가 바뀌게 된다면 데이터베이스 이전 일명 migration이 필요하다. 하지만 우리의 주 목적은 BindingAdapter 학습이기 때문에 이는 생략한다. 만약 이에 대한 내용을 알고 싶다면 안드로이드 개발자 문서에 Room 데이터베이스 이전을 검색하면 된다. 여튼 우리가 이전에 TodoDatabase.kt를 코딩했던 기억을 떠올리면 데이터베이스 인스턴스를 생성할때 .fallbackToDestructiveMigration() 라는 것을 추가했었는데, 이는 DB 스키마가 바뀔 경우 테이블에 존재하는 모든 데이터를 삭제하고 빈 테이블을 제공한다.

 

AddActivity.kt 수정

class AddActivity : AppCompatActivity() {
    private val todoViewModel: MainViewModel by viewModels()
    private var id: Int? = null


    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        add_button.setOnClickListener {
            if(add_edittext_title.text.isNotEmpty() && add_edittext_descript.text.isNotEmpty()){
            	//수정된 부분: 생성자가 변경됨에 의함
                val todo = Todo(id, add_edittext_title.text.toString(), add_edittext_descript.text.toString(),"")
                lifecycleScope.launch(Dispatchers.IO){todoViewModel.insert(todo)}
                finish()
            }else{
                Toast.makeText(this,"Please enter title and desc", Toast.LENGTH_LONG).show()
            }
        }
    }
    ...
}

위에서는 일단 간단히 빈 문자열을 추가해주었다. 따라서, glide.error(..)에 의해 ic_launcher_background 이미지가 세팅될 것이다. 다음 문서에서는 완성도를 위해 디바이스 내의 파일을 하나 선택하여 이미지를 세팅해보는 예제를 진행할 예정이다.