깨알 개념/Android

[Android] Room - UPDATE 연습

interfacer_han 2024. 2. 26. 11:17

#1 이전 글

 

[Android] Room - 기초, INSERT와 DELETE 연습

#1 Room 소개 Room을 사용하여 로컬 데이터베이스에 데이터 저장 | Android Developers Room 라이브러리를 사용하여 더 쉽게 데이터를 유지하는 방법 알아보기 developer.android.com SQLite는 모바일 기기를 위한

kenel.tistory.com

이전 게시글의 완성된 앱을 기반으로, 미구현한 RecyclerView를 구현하고, Entity에 UPDATE문까지 적용시켜본다.

 

#2 개요

 

[Android] RecyclerView - 기초

#1 ListView vs RecyclerView화면에 요소(Item)을 100개 표현한다고 해보자. ListView나 GridView 등의 전통적인 Container Widget들은 이 100개의 아이템을 모두 불러온(load)다. 그리고 화면을 스크롤하면 미리 Load되

kenel.tistory.com

 

[Android] RecyclerView - Adapter에 인자(Argument) 전달

#1 이전 글 [Android] RecyclerView - 기초#1 ListView vs RecyclerView 화면에 요소(Item)을 100개 표현한다고 해보자. ListView나 GridView 등의 전통적인 Container Widget들은 이 100개의 아이템을 모두 불러온(load)다. 그

kenel.tistory.com

위 게시글들에 기반해 RecyclerView를 구현한다. 위 게시글들과 이 게시글의 주요한 차이점이 하나 있다. 바로, 여기선 Item를 담는 List가 LiveData라는 점이다.

 

#3 RecyclerView.ViewHolder의 View (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">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="12dp"
        android:orientation="vertical">

        <androidx.cardview.widget.CardView
            android:id="@+id/card_view"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:clickable="true"
            android:focusable="true"
            app:cardBackgroundColor="@color/design_default_color_primary"
            app:cardCornerRadius="12dp"
            app:cardElevation="12dp">

            <LinearLayout
                android:id="@+id/list_item_layout"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_margin="12dp"
                android:orientation="vertical">

                <TextView
                    android:id="@+id/name_text"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginBottom="12dp"
                    android:text="TextView"
                    android:textColor="@android:color/background_light"
                    android:textSize="28sp"
                    android:textStyle="bold" />

                <TextView
                    android:id="@+id/email_text"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="TextView"
                    android:textColor="@android:color/background_light"
                    android:textSize="20sp"
                    android:textStyle="bold" />
            </LinearLayout>
        </androidx.cardview.widget.CardView>
    </LinearLayout>
</layout>

res/layout 폴더에 list_item.xml 파일을 만들고 위와 같은 코드를 짠다. 이 xml 코드는 RecyclerView에 담기는 Item의 모양이다. 여기에도 Data Binding을 적용할 것이다. 따라서 <layout> 태그로 전체 코드를 감싼다.

 

#4 RecyclerView.Adapter와 RecyclerView.ViewHolder (MyRecyclerViewAdapter.kt)

// package com.example.roomupdate

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.recyclerview.widget.RecyclerView
import com.example.roomupdate.databinding.ListItemBinding
import com.example.roomupdate.db.User

class MyRecyclerViewAdapter(
    private val usersList: List<User>,
    private val clickListener: (User) -> Unit
) : RecyclerView.Adapter<MyViewHolder>() {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
        val layoutInflater = LayoutInflater.from(parent.context)
        val binding: ListItemBinding =
            DataBindingUtil.inflate(layoutInflater, R.layout.list_item, parent, false)
        return MyViewHolder(binding)
    }

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

    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        holder.bind(usersList[position], clickListener)
    }

}

class MyViewHolder(val binding: ListItemBinding) : RecyclerView.ViewHolder(binding.root) {

    fun bind(user: User, clickListener: (User) -> Unit) {
        binding.nameText.text = user.name
        binding.emailText.text = user.email
        binding.listItemLayout.setOnClickListener {
            clickListener(user)
        }
    }
}

본 프로젝트의 RecyclerView.ViewHolder는 RecyclerView.Adapter와 강하게 결합되어있다. 따라서 RecyclerView.Adapter 클래스를 담기 위한 파일을 별도로 만들지 않고, RecyclerView.ViewHolder 클래스를 담는 파일에 같이 넣는다.

 

#5 MainActivity.kt 수정

// package com.example.roomupdate

import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.roomupdate.databinding.ActivityMainBinding
import com.example.roomupdate.db.User
import com.example.roomupdate.db.UserDatabase
import com.example.roomupdate.db.UserRepository

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding
    private lateinit var userViewModel: UserViewModel

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

        val dao = UserDatabase.getInstance(application).userDAO
        val repository = UserRepository(dao)
        val factory = UserViewModelFactory(repository)
        userViewModel = ViewModelProvider(this, factory)[UserViewModel::class.java]

        binding.userViewModel = userViewModel
        binding.lifecycleOwner = this

        initRecyclerView()
    }

    fun initRecyclerView() {
        binding.userRecycler.layoutManager = LinearLayoutManager(this)
        displayUsersList()
    }

    private fun displayUsersList() {
        userViewModel.users.observe(this, Observer {
            Log.i("interfacer_han", it.toString())
            binding.userRecycler.adapter =
                MyRecyclerViewAdapter(it) { selectedItem: User -> listItemClicked(selectedItem) }
        })
    }

    private fun listItemClicked(subscriber: User) {
        Toast.makeText(this, "selected name is ${subscriber.name}", Toast.LENGTH_LONG).show()
        userViewModel.initUpdateAndDelete(subscriber)
    }
}

#2의 게시글에선 RecyclerView.layoutManager 속성 그리고 RecyclerView.adapter 속성만 할당해주면 끝이었지만, 본 게시글에선 좀 복잡해보인다. 하지만, 리사이클러뷰에 표시할 Item이 LiveData라서 adapter 속성에 값을 넣어주는 코드가 LiveData.observe() 내부에 있다는 것만 인지하면 크게 다르지 않음을 알 수 있다.

 

#6 UserViewModel.kt 수정

// package com.example.roomupdate

...

class UserViewModel(private val repository: UserRepository) : ViewModel() {

    ...

    fun initUpdateAndDelete(user: User) {
        isUpdateOrDelete = true
        inputtedUser.value = user
        renewButtonText()
    }

    // 버튼 클릭 시 동작
    fun saveOrUpdate() {
        if (isUpdateOrDelete) {
            // Update 작업
            update(inputtedUser.value!!)
            initSaveAndClearAll()
        } else {
            ...
        }
    }

    fun clearAllOrDelete() {
        if (isUpdateOrDelete) {
            // Delete 작업
            delete(inputtedUser.value!!)
            initSaveAndClearAll()
        } else {
            ...
        }
    }

    // Repository 동작과 관련된 메소드들
    ...

    fun update(user: User) = viewModelScope.launch(Dispatchers.IO) {
        repository.update(user)
    }

    fun delete(user: User) = viewModelScope.launch(Dispatchers.IO) {
        repository.delete(user)
    }

    ...
}

이전 게시글에서 TODO()로 적어두었던 부분을 위와 같이 바꾼다.

 

#7 작동 확인

RecyclerView의 Item이 잘 뜨는 모습이다. 각 Item을 클릭하면 버튼이 바뀌며, Update 및 Delete 동작도 잘 수행된다.

 

#8 완성된 앱

https://github.com/Kanmanemone/android-practice/tree/master/room/RoomUpdate