๐Ÿ’ป ๊ฐœ๋ฐœ/Android

[Android] MVVM ํŒจํ„ด ์ ์šฉ๊ธฐ - 2

๊ณ ๋„๊ณ ๋„ 2022. 5. 14. 21:18
 

GitHub - k906506/MVVM: ๐Ÿ”จ MVVM์ด ๋ญ์—์š”?

๐Ÿ”จ MVVM์ด ๋ญ์—์š”? Contribute to k906506/MVVM development by creating an account on GitHub.

github.com

 

MVVM ๋‘ ๋ฒˆ์งธ ์‹œ๊ฐ„์ด๋‹ค. ๊ฐ‘์ž‘์Šค๋Ÿฝ๊ฒŒ ์ฐพ์•„์˜จ ์ด์œ ๋Š” ์กธํ”„๋ฅผ ์ง„ํ–‰ํ•˜๋‹ค๊ฐ€ RecyclerView ๋ฅผ Room ์„ ์‚ฌ์šฉํ•ด์„œ MVVM ํŒจํ„ด ์œผ๋กœ ๊ตฌํ˜„ํ–ˆ๋Š”๋ฐ ์ •์ƒ์ ์œผ๋กœ ์ž‘๋™ํ•˜์ง€ ์•Š์•„์„œ RecyclerView + Room + MVVM ์„ ์ •๋ฆฌํ•˜๊ณ  ์ฝ”๋“œ๋ฅผ ๋‹ค์‹œ ๋ณด๋ ค๊ณ  ํ•œ๋‹ค.

์šฐ์„  MVVM ์„ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ์œ„์˜ ๊ทธ๋ฆผ์„ ์ดํ•ดํ•˜๊ณ  ๋„˜์–ด๊ฐ€๋Š” ๊ฒƒ์ด ์ข‹๋‹ค. ๊ทธ๋ฆผ์—์„œ ์ฃผ์˜๊นŠ๊ฒŒ ๋ด์•ผํ•  ๊ฒƒ์€ ํ™”์‚ดํ‘œ์˜ ๋ฐฉํ–ฅ ์ด๋‹ค. ๋ชจ๋“  ํ™”์‚ดํ‘œ๊ฐ€ ๋‹จ๋ฐฉํ–ฅ์œผ๋กœ ์—ฐ๊ฒฐ ๋˜์–ด์žˆ๊ณ  ์ƒ์œ„ ์š”์†Œ๋Š” ํ•˜์œ„ ์š”์†Œ๋ฅผ ์ฐธ์กฐํ•œ๋‹ค. ์ฐธ์กฐํ•  ๋•Œ ๋ฐ”๋กœ ์•„๋ž˜์— ์žˆ๋Š” ์š”์†Œ๋งŒ ์ฐธ์กฐ ๊ฐ€๋Šฅํ•˜๋‹ค. ๊ฐ€๋ น Activity ์™€ Fragment ๊ฐ€ ๋ฐ”๋กœ ์•„๋ž˜ ์žˆ๋Š” ViewModel ์ด ์•„๋‹Œ Respository ๋ฅผ ์ฐธ์กฐํ•˜๋ฉด ์•ˆ๋œ๋‹ค. ์ด ๊ฐœ๋…์„ ๋จธ๋ฆฌ์— ์ƒˆ๊ฒจ๋‘๊ณ  ๊ฐ„๋‹จํ•œ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๊ตฌํ˜„ํ•ด๋ณด์ž.

 

์šฐ์„  ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ „์ฒด์ ์ธ ๊ตฌ์กฐ์™€ ๊ตฌํ˜„ ๋ชจ์Šต์€ ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

์ „์ฒด์ ์ธ ๊ตฌ์กฐ
๊ตฌํ˜„ ๋ชจ์Šต

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

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

        <EditText
            android:id="@+id/editTextView"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_margin="12dp"
            android:ellipsize="end"
            android:hint="์ƒํ’ˆ ์ •๋ณด๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”."
            android:inputType="text"
            android:lines="1"
            android:maxLines="1"
            app:layout_constraintBottom_toTopOf="@+id/btn_add"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <Button
            android:id="@+id/btn_add"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_margin="12dp"
            android:text="์ถ”๊ฐ€"
            app:layout_constraintBottom_toTopOf="@+id/recyclerView"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/editTextView" />

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recyclerView"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:layout_margin="12dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/btn_add" />

    </androidx.constraintlayout.widget.ConstraintLayout>


</layout>

์šฐ์„  ๋ ˆ์ด์•„์›ƒ ๋ถ€ํ„ฐ ๊ตฌํ˜„ํ–ˆ๋‹ค. ์ƒ๋‹จ์— EditTextView ๋ฐ”๋กœ ์•„๋ž˜์— ๋“ฑ๋ก ๋ฒ„ํŠผ ํ•˜๋‹จ์—๋Š” ๋“ฑ๋ก๋œ ์ƒํ’ˆ์„ ๋ณด์—ฌ์ฃผ๋Š” RecyclerView ๋ฅผ ๋ฐฐ์น˜ํ–ˆ๋‹ค.

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

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:layout_margin="12dp">

        <androidx.cardview.widget.CardView
            android:id="@+id/itemCardView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:backgroundTint="#C5E1A5"
            app:cardCornerRadius="20dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent">

            <androidx.constraintlayout.widget.ConstraintLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent">

                <TextView
                    android:id="@+id/itemTextView"
                    android:layout_width="0dp"
                    android:layout_height="0dp"
                    android:layout_margin="12dp"
                    android:ellipsize="end"
                    android:gravity="center"
                    android:lines="1"
                    android:maxLines="1"
                    android:textSize="18sp"
                    android:textStyle="bold"
                    app:layout_constraintBottom_toBottomOf="parent"
                    app:layout_constraintEnd_toStartOf="@+id/btn_remove"
                    app:layout_constraintStart_toStartOf="parent"
                    app:layout_constraintTop_toTopOf="parent"
                    tools:text="๋ฏธ๋ฆฌ๋ณด๊ธฐ์šฉ ํ…์ŠคํŠธ์ž…๋‹ˆ๋‹ค." />

                <ImageButton
                    android:id="@+id/btn_remove"
                    android:layout_width="50dp"
                    android:layout_height="50dp"
                    android:layout_margin="12dp"
                    android:background="@drawable/ic_baseline_delete_forever_24"
                    app:layout_constraintBottom_toBottomOf="parent"
                    app:layout_constraintEnd_toEndOf="parent"
                    app:layout_constraintStart_toEndOf="@+id/itemTextView"
                    app:layout_constraintTop_toTopOf="parent" />


            </androidx.constraintlayout.widget.ConstraintLayout>


        </androidx.cardview.widget.CardView>


    </androidx.constraintlayout.widget.ConstraintLayout>

</layout>

RecyclerView ์—์„œ ๋ณด์—ฌ์ค„ ์•„์ดํ…œ์— ๋Œ€ํ•œ ๋ ˆ์ด์•„์›ƒ ์„ ๊ตฌํ˜„ํ–ˆ๋‹ค.

 

์ฝ”๋“œ๋ฅผ ์ ์šฉํ•˜๋ฉด ์•„๋ž˜์™€ ๊ฐ™์€ UI๋ฅผ ๊ฐ€์ง„๋‹ค.

๋Œ€์ถฉ ์ด๋Ÿฐ ํ˜•ํƒœ

Product

package com.example.mvvm_recyclerview_room.database

import androidx.room.Entity
import androidx.room.PrimaryKey

@Entity
data class Product(
    @PrimaryKey(autoGenerate = true) val id: Int?,
    val title: String
)

์ผ๋ฐ˜์ ์ธ DataClass ์— Entity ๋ผ๋Š” ์–ด๋…ธํ…Œ์ด์…˜ ์„ ์ถ”๊ฐ€ํ–ˆ๋‹ค. ๋˜ํ•œ PrimaryKey ๋กœ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด id ๋ฅผ ์ง€์ •ํ•ด์คฌ๊ณ  ์ด ๋ถ€๋ถ„์€ ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ์ž๋™์œผ๋กœ ์‹œ์Šคํ…œ์—์„œ ์ž…๋ ฅ๋  ์ˆ˜ ์žˆ๋„๋ก autoGenerate = true ์†์„ฑ์„ ๋ถ€์—ฌํ–ˆ๋‹ค.

ProductDao

package com.example.mvvm_recyclerview_room.database

import androidx.lifecycle.LiveData
import androidx.room.*

@Dao
interface ProductDao {
    @Insert
    suspend fun insert(product: Product)

    @Update
    suspend fun update(product: Product)

    @Delete
    suspend fun delete(product: Product)

    @Query("SELECT * FROM product")
    fun getAll(): LiveData<List<Product>>
}

suspend ๋ผ๋Š” ์ฒ˜์Œ ๋ณด๋Š” ํ‚ค์›Œ๋“œ๊ฐ€ ๋“ฑ์žฅํ–ˆ๋‹ค. suspend ๋Š” ์ผ์‹œ ์ค‘๋‹จ์— ์‚ฌ์šฉํ•˜๋Š” ํ•จ์ˆ˜๋กœ ํ•ด๋‹น ๋ฉ”์†Œ๋“œ๊ฐ€ ํ˜ธ์ถœ๋  ๋•Œ ์ผ์‹œ ์ค‘๋‹จ๋œ๋‹ค. ๋”ฐ๋ผ์„œ ์ฝ”๋ฃจํ‹ด ๋ธ”๋Ÿญ ๋‚ด๋ถ€ ์ด๋‚˜ ๋‹ค๋ฅธ ์ผ์‹œ ์ค‘๋‹จ ํ•จ์ˆ˜ ๋‚ด๋ถ€ ์—์„œ ์‹คํ–‰๋˜์–ด์ ธ์•ผ๋งŒ ํ•œ๋‹ค.

ProductDatabase

package com.example.mvvm_recyclerview_room.database

import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase

@Database(entities = [Product::class], version = 1)
abstract class ProductDatabase : RoomDatabase() {
    abstract fun productDao(): ProductDao

    companion object {
        private var instance: ProductDatabase? = null

        @Synchronized
        fun getInstance(context: Context): ProductDatabase? {
            if (instance == null) {
                synchronized(ProductDatabase::class) {
                    instance = Room.databaseBuilder(
                        context.applicationContext,
                        ProductDatabase::class.java,
                        "product-database"
                    ).build()
                }
            }
            return instance
        }
    }
}

์œ„์—์„œ ๊ตฌํ˜„ํ•œ Entity ๋ฅผ ๊ฐ€์ง€๊ณ  ์‹ค์ œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ฅผ ๊ตฌํ˜„ํ•œ๋‹ค. ์‹ฑ๊ธ€ํ†ค ํŒจํ„ด์œผ๋กœ ์ƒ์„ฑํ–ˆ๋‹ค.

Repository && RepositoryImpl

package com.example.mvvm_recyclerview_room.repository

import androidx.lifecycle.LiveData
import com.example.mvvm_recyclerview_room.database.Product

interface ProductRepository {
    suspend fun insert(product: Product)

    suspend fun update(product: Product)

    suspend fun delete(product: Product)

    fun getAll(): LiveData<List<Product>>
}
package com.example.mvvm_recyclerview_room.repository

import android.app.Application
import androidx.lifecycle.LiveData
import com.example.mvvm_recyclerview_room.database.Product
import com.example.mvvm_recyclerview_room.database.ProductDatabase

class ProductRepositoryImpl(application: Application) : ProductRepository {
    private val productDao by lazy {
        ProductDatabase.getInstance(application)!!.productDao()
    }

    override suspend fun insert(product: Product) {
        productDao.insert(product)
    }

    override suspend fun update(product: Product) {
        productDao.update(product)
    }

    override suspend fun delete(product: Product) {
        productDao.delete(product)
    }

    override fun getAll(): LiveData<List<Product>> {
        return productDao.getAll()

    }
}

Activity - ViewModel - Respository - Data ๋ผ๋Š” ํฐ ํ‹€์—์„œ ๋ดค์„ ๋•Œ Interface ์—ญํ• ์„ ์ˆ˜ํ–‰ํ•œ๋‹ค. Respository ๋กœ ์ธํ•ด์„œ ViewModel ์ด ์ง์ ‘์ ์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๊ด€๋ฆฌํ•  ํ•„์š”๊ฐ€ ์—†์–ด์ง„๋‹ค. ์œ„์—์„œ ๊ตฌํ˜„ํ•œ Dao ๊ฐ€ suspend ์ด๋ฏ€๋กœ ์ด๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๋ฉ”์†Œ๋“œ ์—ญ์‹œ suspend ๋กœ ๊ตฌํ˜„ํ•œ๋‹ค.

ProductViewModel

package com.example.mvvm_recyclerview_room.viewmodel

import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import com.example.mvvm_recyclerview_room.database.Product
import com.example.mvvm_recyclerview_room.repository.ProductRepositoryImpl

class ProductViewModel(application: Application) : AndroidViewModel(application) {
    private val repository = ProductRepositoryImpl(application)
    private val products = repository.getAll()

    suspend fun insert(product: Product) {
        repository.insert(product)
    }

    suspend fun update(product: Product) {
        repository.update(product)
    }

    suspend fun delete(product: Product) {
        repository.delete(product)
    }

    fun getAll(): LiveData<List<Product>> {
        return products
    }
}

repository ๋ฅผ ํ†ตํ•ด ๋ฐ์ดํ„ฐ์— ๋Œ€ํ•œ CRUD ์„ ์ง„ํ–‰ํ•œ๋‹ค. ์—ญ์‹œ repository ์˜ suspend ๋ฅผ ํ˜ธ์ถœํ•˜๋ฏ€๋กœ suspend ๋กœ ๊ตฌํ˜„ํ•œ๋‹ค.

BaseActivity

package com.example.mvvm_recyclerview_room.base

import android.os.Bundle
import androidx.annotation.LayoutRes
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import androidx.databinding.ViewDataBinding

abstract class BaseActivity<T : ViewDataBinding>(@LayoutRes private val layoutResId: Int) :
    AppCompatActivity() {
    private var _binding: T? = null
    val binding get() = _binding ?: error("Not Initialized")

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        _binding = DataBindingUtil.setContentView(this@BaseActivity, layoutResId)
        binding.lifecycleOwner = this@BaseActivity
    }
}

์ž‘์—…ํ•˜๊ธฐ ์•ž์„œ BaseActivity ๋ฅผ ๊ตฌํ˜„ํ–ˆ๋‹ค. ๋ฌผ๋ก  ๊ตณ์ด? ๊ตฌํ˜„ํ•  ํ•„์š” ์—†๋Š” ํด๋ž˜์Šค์ด๋‹ค. ๊ทธ๋ƒฅ ๋ฐ”๋กœ AppCompatActivity ๋ฅผ ์ƒ์†๋ฐ›์•„์„œ ๊ตฌํ˜„ํ•ด๋„ ๋œ๋‹ค. ํ•˜์ง€๋งŒ ๋‹ค๋ฅธ ํ”„๋กœ์ ํŠธ์—์„œ Activity ๋‚˜ Fragment์—์„œ ๊ฒน์น˜๋Š” ์ฝ”๋“œ๋“ค์„ Base ๋กœ ๋ถ„๋ฆฌํ•˜๊ณ  ์ž‘์—…ํ•˜๋‹ˆ๊นŒ ์ค‘๋ณต๋˜๋Š” ์ฝ”๋“œ๊ฐ€ ํ™•์‹คํžˆ ์ค„์–ด์„œ ํŽธํ–ˆ๋‹ค. ์ง€๊ธˆ ์ด ํ”„๋กœ์ ํŠธ๋Š” Single Activity ์ด์ง€๋งŒ ์†์— ์ตํžˆ๊ณ ์ž BaseActivity ๋กœ ๋ถ„๋ฆฌํ–ˆ๋‹ค.

 

BaseActivity ์— ๋ฌด์—‡์„ ๊ตฌํ˜„ํ• ์ง€๋Š” ๊ฐœ๋ฐœ์ž์˜ ์ž…๋ง›์ธ ๊ฒƒ ๊ฐ™๋‹ค. ๊ทธ๋ž˜๋„ ๊ธฐ๋ณธ์ ์œผ๋กœ ์žˆ์–ด์•ผํ•˜๋Š” ๊ฒƒ์€ ์—ฐ๊ฒฐํ•ด ์ค„ DataBinding ๊ณผ onCreate ์ด๋‹ค. DataBinding ์—ญ์‹œ ์ง์ ‘์ ์œผ๋กœ ์—ฐ๊ฒฐํ•ด์ฃผ์ง€ ์•Š๊ณ  private ๋กœ ๊ตฌํ˜„ํ•˜๊ณ  ์™ธ๋ถ€์—์„œ ์ฐธ์กฐํ•  ๋–„๋Š” ๋ณ„๋„์˜ ๋ณ€์ˆ˜๋กœ ์ ‘๊ทผํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ๊ตฌํ˜„ํ–ˆ๋‹ค.

MainActivity

package com.example.mvvm_recyclerview_room

import android.os.Bundle
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.mvvm_recyclerview_room.adapter.ProductAdapter
import com.example.mvvm_recyclerview_room.base.BaseActivity
import com.example.mvvm_recyclerview_room.database.Product
import com.example.mvvm_recyclerview_room.databinding.ActivityMainBinding
import com.example.mvvm_recyclerview_room.viewmodel.ProductViewModel
import kotlinx.coroutines.*

class MainActivity : BaseActivity<ActivityMainBinding>(R.layout.activity_main) {
    private val viewModel by lazy {
        ViewModelProvider(this)[ProductViewModel::class.java]
    }

    private val adapter by lazy {
        ProductAdapter(itemClickListener = { product ->
            deleteProduct(product)
        })
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        initViews()
        bindViews()
        subscribeObserver()
    }

    private fun initViews() = with(binding) {
        recyclerView.layoutManager = LinearLayoutManager(this@MainActivity)
        recyclerView.adapter = adapter
    }

    private fun bindViews() = with(binding) {
        btnAdd.setOnClickListener {
            insertProduct(editTextView.text.toString())
        }
    }

    private fun subscribeObserver() {
        viewModel.getAll().observe(this) { adapter.submitList(it) }
    }

    private fun insertProduct(title: String) {
        CoroutineScope(Dispatchers.IO).launch {
            viewModel.insert(Product(id = null, title = title))
        }
    }

    private fun deleteProduct(product: Product) {
        CoroutineScope(Dispatchers.IO).launch {
            viewModel.delete(product)
        }
    }

}

abstract ๋กœ ๊ตฌํ˜„ํ•œ BaseActivity ๋ฅผ ์ƒ์†๋ฐ›๊ณ  ViewModel ์€ ViewModelProvider ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ตฌํ˜„ํ•œ๋‹ค. (ViewModelProviders๋Š” Deprecated ๋˜์—ˆ๋‹ค.) RecyclerView ์™€ ์—ฐ๊ฒฐํ•ด ์ค„ Adapter๋„ ๊ตฌํ˜„ํ•˜๊ณ  LayoutManager ๋ฅผ ์ง€์ •ํ•ด์ค€๋‹ค.

 

subcribeObserver ์™€ insertProduct deleteProduct ๊ฐ€ ๋ˆˆ์— ๋ˆ๋‹ค. subcribeObserver ๋Š” viewModel.getAll() ์„ ๊ด€์ฐฐํ•˜๊ณ  ์žˆ๋‹ค๊ฐ€ ๋ณ€ํ™”๊ฐ€ ์ผ์–ด๋‚˜๋ฉด RecyclerView ์— ํ‘œ์‹œํ•  ์•„์ดํ…œ(๋ฆฌ์ŠคํŠธ)๋ฅผ ๊ฐฑ์‹ ํ•ด์ฃผ๋Š” ๋ฉ”์†Œ๋“œ์ธ adapter.submistList ๋ฅผ ์‹คํ–‰ํ•œ๋‹ค.

 

์„ค๋ช…์„ ์œ„ํ•ด ์ž ๊น ProductAdapter ๋กœ ์ด๋™ํ•ด๋ณด์ž.

ProductAdapter

package com.example.mvvm_recyclerview_room.adapter

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.example.mvvm_recyclerview_room.database.Product
import com.example.mvvm_recyclerview_room.databinding.ItemMainBinding

class ProductAdapter(private val itemClickListener: (Product) -> Unit) :
    RecyclerView.Adapter<ProductAdapter.ViewHolder>() {
    private var products: List<Product>? = null

    inner class ViewHolder(private val binding: ItemMainBinding) :
        RecyclerView.ViewHolder(binding.root) {
        fun bindViews(product: Product) {
            binding.itemTextView.text = product.title

            binding.btnRemove.setOnClickListener {
                itemClickListener(product)A
            }
        }

    }

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

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        products?.let {
            holder.bindViews(it[position])
        }
    }

    override fun getItemCount(): Int {
        return products?.size ?: 0
    }

    fun submitList(items: List<Product>) {
        products = items
        notifyDataSetChanged()
    }
}

๋Œ€๋ถ€๋ถ„์˜ ์ฝ”๋“œ๋Š” ๊ธฐ์กด RecyclerView ์˜ Adapter ์™€ ๋งค์šฐ ์œ ์‚ฌํ•˜๋‹ค. itemClickListener ๋ฅผ ํ†ตํ•ด Product ๋ฅผ ์ „๋‹ฌํ•˜๊ณ  ์ด๋ฅผ MainActivity ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ๋” ํ•œ๋‹ค. ์œ„์—์„œ ์„ค๋ช…ํ•œ submitList ๋Š” ์ „๋‹ฌ ๋ฐ›์€ ์•„์ดํ…œ(๋ฆฌ์ŠคํŠธ)๋กœ ๊ฐฑ์‹ ํ•œ๋‹ค. notifyDataSetChanged ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ RecyclerView ์— ์•Œ๋ฆฐ๋‹ค.

MainActivity

    private fun insertProduct(title: String) {
        CoroutineScope(Dispatchers.IO).launch {
            viewModel.insert(Product(id = null, title = title))
        }
    }

    private fun deleteProduct(product: Product) {
        CoroutineScope(Dispatchers.IO).launch {
            viewModel.delete(product)
        }
    }

๋‹ค์‹œ MainActivity ๋กœ ๋„˜์–ด์™”๋‹ค. ๋‘ ๊ฐœ์˜ ๋ฉ”์†Œ๋“œ๋Š” Room ์œผ๋กœ ๊ตฌํ˜„ํ•œ ๋‚ด์žฅ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ํ•ญ๋ชฉ์„ ์ถ”๊ฐ€ํ•˜๊ณ  ์‚ญ์ œํ•˜๋Š” ๊ธฐ๋Šฅ์ด๋‹ค. insert ์™€ delete ์—์„œ๋Š” ์ฝ”๋ฃจํ‹ด ์ด๋ผ๋Š” ์ƒˆ๋กœ์šด ๊ฐœ๋…์ด ๋“ฑ์žฅํ•˜๋Š”๋ฐ ๋„คํŠธ์›Œํ‚น ์ž‘์—… ์ด๋‚˜ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ž‘์—… ์˜ ๊ฒฝ์šฐ ๋ฉ”์ธ ์“ฐ๋ ˆ๋“œ ์—์„œ ์ž‘์—…ํ•˜๋ฉด ์•ˆ๋œ๋‹ค. ์ด๋Ÿฐ ๊ฐ„๋‹จํ•œ ํ”„๋กœ์ ํŠธ๋Š” ๊ดœ์ฐฎ์ง€๋งŒ ๋ฌด๊ฑฐ์šด ์ž‘์—…์„ ๋ฉ”์ธ ์“ฐ๋ ˆ๋“œ์—์„œ ์ž‘์—…ํ•˜๊ฒŒ ๋˜๋ฉด ANR(Application Not Responding) ์ด ๋ฐœ์ƒํ•˜๋ฉด์„œ ์•ฑ์ด ๊ฐ•์ œ์ข…๋ฃŒ ๋˜๊ณ  ์‚ฌ์šฉ์ž์—๊ฒŒ ๋ถˆํŽธํ•จ์„ ์ค„ ๊ฒƒ์ด๋‹ค. ์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด UI ์ž‘์—… ์„ ์ œ์™ธํ•˜๊ณ  ๋‹ค๋ฅธ ์“ฐ๋ ˆ๋“œ์—์„œ ์ง„ํ–‰ํ•˜๋Š”๋ฐ ์ด๋ฅผ ๋„์™€์ฃผ๋Š” ๊ฒƒ์ด ์ฝ”๋ฃจํ‹ด ์ด๋‹ค. CoroutineScope ์œผ๋กœ ์‹คํ–‰๋œ ๋ฒ”์œ„๋ฅผ ์ง€์ •ํ•˜๊ณ  Dispatcher ๋กœ ์–ด๋–ค ์“ฐ๋ ˆ๋“œ ์—์„œ ์ž‘์—…ํ• ์ง€๋ฅผ ์ง€์ •ํ•œ๋‹ค.

๊ตฌํ˜„ ๊ฒฐ๊ณผ

๊ตฌํ˜„ ๋ชจ์Šต

๋Š๋‚€ ์ 

์‚ฌ์‹ค ์ง€๊ธˆ ์ด๋ ‡๊ฒŒ ๋ณด๋‹ˆ ์ž˜๋ชป ๊ตฌํ˜„๋˜์—ˆ๋‹ค๊ณ  ์ƒ๊ฐํ•œ๋‹ค. MainActivity ์—์„œ๋Š” Data ์— ๋Œ€ํ•œ ์ ‘๊ทผ์„ ์ง€์–‘ํ•ด์•ผํ•˜๋Š”๋ฐ ์ง€๊ธˆ ๊ตฌํ˜„ํ•œ ์ฝ”๋“œ๋ฅผ ๋ณด๋ฉด Product ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•ด์„œ ์ „๋‹ฌํ•˜๊ณ  ์žˆ๋‹ค. ๋‹ค์‹œ ๊ณ ์ณ๋ด์•ผ๊ฒ ๋‹ค.