본문 바로가기

안드로이드

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

구현 과정

1. 프로젝트 구성

2. Room 영속성 라이브러리 관련 클래스 구현

3. Repository 구현

4. ViewModel 구현

5. RecyclerViewAdapter 구현

6. MainActivity 구현

7. AddActivity 구현


MainViewModel.kt : ViewModel

MainViewModel은 repository 객체를 생성하여 관찰한다.

class MainViewModel(application: Application): AndroidViewModel(application) {
    private val repository = Repository(application)
    private val todos = repository.getAll()


    fun getAll() : LiveData<List<Todo>>{
        return repository.getAll()
    }

    fun insert(todo: Todo){
        repository.insert(todo)
    }
    fun delete(todo: Todo){
        repository.delete(todo)
    }
}

RecyclerViewAdapter 구현

먼저 RecyclerView의 item 레이아웃을 생성한다.

 

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">
            <TextView
                android:layout_width="50dp"
                android:layout_height="wrap_content"
                android:id="@+id/item_tv_initial"
                android:textSize="30dp"
                android:padding="4dp"
                android:background="@android:color/darker_gray"
                android:layout_marginTop="16dp"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                android:layout_marginStart="16dp"
                tools:text="H"
                android:gravity="center"
                app:layout_constraintBottom_toBottomOf="parent"
                android:layout_marginBottom="16dp"/>

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

위 코드를 자세히 보면 TextView의 tools:text의 요소에 @{todo.필드명.toString()}이 삽입된 모습을 볼 수 있다. 이는 databinding을 활용하여 adapter 내에서 item_tv_title.text="Text"의 작업과 같은 반복을 줄여주며 유지보수가 쉬워진다. 그리고 이러한 데이터 바인딩을 위해 CardView 밖을 layout 태그로 감싸고 data 태그를 추가하여 그 내부에 todo 변수를 만들었다. 데이터바인딩을 위해서 xml에서는 이러한 코드가 필요하다.

 

TodoAdpater.kt : RecyclerViewAdpater

위에서 정의한 xml을 위해 어답터를 아래와 같이 작성하였다.

class TodoAdapter(val todoItemClick: (Todo) -> Unit, val todoItemLongClick: (Todo) -> Unit):
    RecyclerView.Adapter<TodoAdapter.ViewHolder> () {
    private var todos: List<Todo> = listOf()

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val binding = ItemTodoBinding.inflate(LayoutInflater.from(parent.context),parent, false)
        return ViewHolder(binding)
    }

    override fun getItemCount(): Int {
        return todos.size
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.bind(todos[position])
    }

    inner class ViewHolder(val binding: ItemTodoBinding):RecyclerView.ViewHolder(binding.root){
        fun bind(todo: Todo) {
            binding.todo = todo

            binding.root.setOnClickListener {
                todoItemClick(todo)
            }
            binding.root.setOnLongClickListener {
                todoItemLongClick(todo)
                true
            }
        }
    }
    fun setTodos(todos: List<Todo>) {
        this.todos = todos
        notifyDataSetChanged()

    }


}

위를 보면 거의 대부분의 코드가 데이터바인딩을 사용하지 않은 어답터와 비슷하나 onCreateViewHolder와 ViewHolder의 코드를 보면 기존의 구현방식과 차이가 있다. 기존에는 onCreateViewHolder에서 item_todo.xml을 inflate하여 view를 반환하지만, 위에선 ItemTodoBinding이라는 객체를 반환한다. 이는 item_todo.xml에 대한 파스칼 표기법에 의해 생성된 클래스이다. 이어서 내부 클래스인 ViewHolder는 이러한 반환된 ItemTodoBinding 객체를 받아 세팅한다. (참고로 이 어답터에서는 클릭, 롱클릭 액션을 MainActivity에서 넘겨받아 ViewHolder에서 구현된다.)

 

참고로 아래는 데이터 바인딩을 하지 않았을 경우의 코드이다.

class TodoAdapter(val todoItemClick: (Todo) -> Unit, val todoItemLongClick: (Todo) -> Unit):
    RecyclerView.Adapter<TodoAdapter.ViewHolder> () {
    private var todos: List<Todo> = listOf()

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.item_todo,parent, false)
        return ViewHolder(view)
    }

    override fun getItemCount(): Int {
        return todos.size
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.bind(todos[position])
    }

    inner class ViewHolder(itemView: View):RecyclerView.ViewHolder(itemView){
        fun bind(todo: Todo){
            itemView.item_tv_name.text = todo.title
            itemView.item_tv_descript.text = todo.description

            itemView.setOnClickListener {
                todoItemClick(todo)
            }
            itemView.setOnLongClickListener {
                todoItemLongClick(todo)
                true
            }
        }
    }
    fun setTodos(todos: List<Todo>) {
        this.todos = todos
        notifyDataSetChanged()

    }

}