๊พธ์ค€ํ•จ์ด ์ง„๋ฆฌ๋‹ค!!

์–ด์ œ๋ณด๋‹ค ๋ฐœ์ „ํ•œ ์˜ค๋Š˜์ด ๋˜๊ณ ํ”ˆ ๐Ÿง‘๐Ÿปโ€๐Ÿ’ป ์˜ ๋ธ”๋กœ๊ทธ

Android/Kotlin

[Kotlin][Android] ์•ˆ๋“œ๋กœ์ด๋“œ Room์˜ ์‚ฌ์šฉ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด์„œ

๋ށ์š” 2022. 12. 16. 18:21

์•ˆ๋…•ํ•˜์„ธ์š” ๐Ÿ‘‹

์˜ค๋Š˜์€ ์•ˆ๋“œ๋กœ์ด๋“œ Room์˜ ์‚ฌ์šฉ๋ฒ•์— ๋Œ€ํ•ด์„œ ํฌ์ŠคํŒ…ํ•ด๋ณด๋ ค ํ•ฉ๋‹ˆ๋‹ค.


Room์ด๋ž€?

์Šค๋งˆํŠธํฐ ๋‚ด์žฅ DB์— ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•˜๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ

ORM(Object Relational Mapping)๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ž…๋‹ˆ๋‹ค.

Room์€ DB ๋ฐ์ดํ„ฐ๋ฅผ Java ๋˜๋Š” ์ฝ”ํ‹€๋ฆฐ ๊ฐ์ฒด๋กœ ๋งคํ•‘ํ•ด์ฃผ๋ฉฐ,

SQLite๋ฅผ ๋‚ด๋ถ€์ ์œผ๋กœ ์‚ฌ์šฉํ•จ์œผ๋กœ์จ DB๋ฅผ ๊ตฌ์กฐ์ ์œผ๋กœ ๋ถ„๋ฆฌํ•˜๊ณ 

๋ฐ์ดํ„ฐ ์ ‘๊ทผ์˜ ํŽธ์˜์„ฑ์„ ๋†’์—ฌ์ฃผ์–ด ์œ ์ง€๋ณด์ˆ˜๋ฅผ ํŽธ๋ฆฌํ•˜๊ฒŒ ํ•ด ์ค๋‹ˆ๋‹ค.


Room์˜ ๊ตฌ์กฐ์— ๋Œ€ํ•ด์„œ

room์˜ ๊ตฌ์กฐ๋Š” ํฌ๊ฒŒ 

Database , DAO , Entity๋กœ ๋‚˜๋ˆŒ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์•„๋ž˜๋Š” Room์˜ ๊ตฌ์กฐ์ด๋ฉฐ ๊ฐ๊ฐ์— ๋Œ€ํ•ด ์„ค๋ช…ํ•˜๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

Room์˜ ๊ตฌ์กฐ

 

Entity(์—”ํ‹ฐํ‹ฐ)

DB ๋‚ด์˜ Table๋กœ class์˜ ๋ณ€์ˆ˜๋“ค์ด ์นผ๋Ÿผ(column)์ด ๋˜์–ด Table์ด ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค.

Annotation ์„ค๋ช… ์‚ฌ์šฉ๋ฒ•
@Entity Table ์ด๋ฆ„์„ ์„ ์–ธํ•ฉ๋‹ˆ๋‹ค. @Entity(tableName = StudentEntry.TABLE_NAME)
@PrimaryKey ํ•ด๋‹น property๋ฅผ primaryKey๋กœ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.  
@ColumnInfo Table ๋‚ด column ์„ ๋ณ€์ˆ˜์™€ ๋งค์นญ ์‹œ์ผœ์ค๋‹ˆ๋‹ค.  

 

DAO(Data Access Object)

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ ‘๊ทผํ•˜์—ฌ ์ˆ˜ํ–‰ํ•  ์ž‘์—…์„ ๋ฉ”์†Œ๋“œ ํ˜•ํƒœ๋กœ ์ •์˜ํ•˜๋Š” class์ž…๋‹ˆ๋‹ค.(SQL ์ฟผ๋ฆฌ๋„ ์ง€์ • ๊ฐ€๋Šฅ)

 

Annotation ์„ค๋ช…
@Insert Entity๋ฅผ setํ•˜๋Š” ํ•จ์ˆ˜ ๋˜๋Š” ๋ฉ”์†Œ๋“œ๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.
@Entity๋กœ ์ •์˜๋œ class๋งŒ ์ธ์ž๋กœ ๋ฐ›๊ฑฐ๋‚˜, ๊ทธ class์˜ collection ๋˜๋Š” array ๋งŒ ์ธ์ž๋กœ ๋ฐ›์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
@Update Entity๋ฅผ updateํ•˜๋Š” ํ•จ์ˆ˜ ๋˜๋Š” ๋ฉ”์†Œ๋“œ๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.
@Delete Entity๋ฅผ deleteํ•˜๋Š” ํ•จ์ˆ˜ ๋˜๋Š” ๋ฉ”์†Œ๋“œ๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.
@Query ์„ ์–ธ๋œ query๋ฅผ ํ†ตํ•ด DB๋ฅผ ์กฐํšŒํ•œ ๊ฒฐ๊ณผ๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” ํ•จ์ˆ˜ ๋˜๋Š” ๋ฉ”์†Œ๋“œ๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.

 

DataBase

 ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ์ „์ฒด์ ์ธ ์†Œ์œ ์ž ์—ญํ• ์„ ํ•˜๋Š” ์ถ”์ƒ ํด๋ž˜์Šค๋กœ

RoomDB์—์„œ DAO๋ฅผ ๊ฐ€์ ธ์™€์„œ ๊ฐ์ฒด๋ฅผ ํ†ตํ•ด ๋ฐ์ดํ„ฐ๋ฅผ CRUD ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

 

์†์„ฑ ์„ค๋ช…
entities Database์— ์–ด๋–ค Entitiy๊ฐ€ ์žˆ๋Š”์ง€ ๋ช…์‹œ
version Database์˜ version์„ ๋‚˜ํƒ€๋‚ธ๋‹ค (scheme์ด ๋ฐ”๋€”๋•Œ ํ•จ๊ป˜ ๋ฐ”๊พธ์–ด ์ฃผ์–ด์•ผํ•œ๋‹ค.)
scheme Room์˜ Schema ๊ตฌ์กฐ๋ฅผ ํด๋”๋กœ Export ํ•  ์ˆ˜ ์žˆ๋Š”์ง€์— ๋Œ€ํ•œ ์—ฌ๋ถ€๋ฅผ ์„ค์ •

์ด์ œ๋Š” ์˜ˆ์ œ๋ฅผ ํ†ตํ•ด ์‚ฌ์šฉ๋ฒ•์„ ์•Œ์•„๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ๐Ÿค”

๋จผ์ €, ์ „๋ฐ˜์ ์ธ RoomDB ์„ค์ •๋ถ€ํ„ฐ ๊ด€๋ จ class๋“ค์„ ๋งŒ๋“ค๊ฒ ์Šต๋‹ˆ๋‹ค.

1. gradle์„ ์„ค์ •ํ•ด์ค๋‹ˆ๋‹ค.

plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
    id 'kotlin-kapt' // kapt๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด ํ•„์ˆ˜ ์ถ”๊ฐ€
}

android{
   ... ์ƒ๋žต
    buildFeatures {
            viewBinding true
            dataBinding true
    }
}
dependencies {

    implementation 'androidx.core:core-ktx:1.9.0'
    implementation 'androidx.appcompat:appcompat:1.5.1'
    implementation 'com.google.android.material:material:1.7.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.4'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0'

    //Room
    implementation 'androidx.room:room-runtime:2.4.3'
    implementation "androidx.room:room-ktx:2.4.3"
    kapt 'androidx.room:room-compiler:2.4.3'

    // Coroutine
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.1"

}

ํ•„์ž๋Š” ํ•ด๋‹น ์˜ˆ์ œ์—์„œ Coroutine๊ณผ DataBinding์„ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํ•ด๋‹น ๋ถ€๋ถ„๋„ ํ•จ๊ป˜ ์ถ”๊ฐ€ํ•ด์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค. ๐Ÿ˜Ž

 

2. Entity๋ฅผ ์ƒ์„ฑํ•ด์ค๋‹ˆ๋‹ค.

@Entity(tableName = "user_tbl")
data class UserEntity(
    @PrimaryKey(autoGenerate = true)
    val userId: Int?,
    var userName: String,
    var userAge: Int
    )

 

3. DB๋ฅผ ์ปจํŠธ๋กคํ•  DAO interface๋ฅผ ๋งŒ๋“ค์–ด ์ค๋‹ˆ๋‹ค.

@Dao
interface UserDao {
    @Insert // ์ƒˆ๋กœ์šด User ์ƒ์„ฑ
    suspend fun insertUser(user : UserEntity)

    @Query("SELECT * FROM user_tbl") // ๋ชจ๋“  User ๊ฐ€์ ธ์˜ค๊ธฐ
    suspend fun getAllUser() : MutableList<User>

    @Delete // ์„ ํƒํ•œ User ์‚ญ์ œํ•˜๊ธฐ
    suspend fun deleteUser(user : UserEntity)

    @Update // ์„ ํƒํ•œ User ์—…๋ฐ์ดํŠธ ํ•˜๊ธฐ
    suspend fun updateUser(user : UserEntity)

}

 

/**
* User์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ฌ๋•Œ ์‚ฌ์šฉํ•  data class
**/

data class User (
    val userId : Int ,
    var userName : String ,
    var userAge : Int
) : Serializable

 

4. Application class๋ฅผ ์ƒ์„ฑํ•˜์—ฌ Application Context๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

class MainApplication : Application() {
    companion object{
        private lateinit var appInstance : MainApplication
        fun getInstance() = appInstance
    }

    override fun onCreate() {
        super.onCreate()
        appInstance = this
    }
}

 

5. RoomDB๋ฅผ ์ƒ์„ฑํ•ด์ค๋‹ˆ๋‹ค.

@Database(entities = [UserEntity::class] , version = 1 , exportSchema = false)
abstract class UserDatabase : RoomDatabase() {
    abstract fun userDAO() : UserDao
    companion object{
        private lateinit var instance : UserDatabase
        internal fun getInstance() : UserDatabase {
            if(!::instance.isInitialized){
                synchronized(UserDatabase::class.java){
                    instance = Room.databaseBuilder(
                        MainApplication.getInstance() , 
                        // Application Context๋ฅผ ์ฐธ์กฐํ•˜์—ฌ ์•ฑ๊ณผ ์ƒ๋ช…์ฃผ๊ธฐ๋ฅผ ๊ฐ™๊ฒŒ ํ•ด์ค๋‹ˆ๋‹ค.
                        // ์ฆ‰, ํ•ด๋‹น ์•ฑ์ด ์ฃฝ์œผ๋ฉด instance๋„ ์‚ฌ๋ผ์ง
                        UserDatabase::class.java ,
                        "user.db" ,
                    ).build()
                }
            }
            return instance
        }
    }
}

 

โœ”๏ธ Database๋ฅผ ์ƒ์„ฑํ•˜๋Š” ์ผ์€ ๊ต‰์žฅํžˆ ๋งŽ์€ IO๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ์ผ์ด๋ฏ€๋กœ ์‹ฑ๊ธ€ํ„ด ํŒจํ„ด์„ ํ†ตํ•ด ๋‹จ ํ•œ ๋ฒˆ๋งŒ ์ƒ์„ฑํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.


์ด๋ฒˆ์—๋Š” MVVMํŒจํ„ด์„ ํ†ตํ•ด Room์„ ์‚ฌ์šฉํ•˜๋Š” ์ „๋ฐ˜์ ์ธ ์ฝ”๋“œ์— ๋Œ€ํ•ด 

์„ค๋ช…๋“œ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค.


1. MainActivity( ์ž…๋ ฅ์ฐฝ )

<?xml version="1.0" encoding="utf-8"?>
<layout>
    <data>
        <variable
            name="activityMainViewModel"
            type="com.lee.viewmodel.ActivityMainViewModel" />
    </data>
    <androidx.constraintlayout.widget.ConstraintLayout 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"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

        <LinearLayout
            android:id="@+id/userNameLayout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:layout_marginTop="10dp"
            app:layout_constraintTop_toTopOf="parent"
            >
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="์ด๋ฆ„"
                android:textSize="20sp"
                android:layout_marginStart="10dp"
                />
            <EditText
                android:id="@+id/userNameEditText"
                android:layout_width="match_parent"
                android:layout_height="50dp"
                android:layout_margin="20dp"
                android:background="@drawable/border_textview"
                android:text="@={activityMainViewModel.userName}"
                android:paddingStart="10dp"
                android:paddingEnd="0dp"
                />
        </LinearLayout>

        <LinearLayout
            android:id="@+id/userAgeLayout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:layout_marginTop="10dp"
            app:layout_constraintTop_toBottomOf="@id/userNameLayout"
            >
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="๋‚˜์ด"
                android:textSize="20sp"
                android:layout_marginStart="10dp"
                />
            <EditText
                android:id="@+id/userAgeEditText"
                android:layout_width="match_parent"
                android:layout_height="50dp"
                android:layout_margin="20dp"
                android:background="@drawable/border_textview"
                android:text="@={activityMainViewModel.userAge}"
                android:inputType="number"
                android:paddingStart="10dp"
                android:paddingEnd="0dp"
                />
        </LinearLayout>

        <Button
            android:id="@+id/confirmButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="์ €์žฅํ•˜๊ธฐ"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            android:layout_margin="10dp"
            android:onClick="@{() -> activityMainViewModel.insertUser()}"
            />

        <Button
            android:id="@+id/listButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintBottom_toTopOf="@id/confirmButton"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            android:text="๋ชฉ๋ก์œผ๋กœ"
            />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>
class MainActivity : AppCompatActivity() {
    private lateinit var binding : ActivityMainBinding
    private lateinit var mViewModel : ActivityMainViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this , R.layout.activity_main)
        mViewModel = ViewModelProvider(this@MainActivity , MainViewModelFactory(MainRepository.getInstance()))[ActivityMainViewModel::class.java]
        binding.activityMainViewModel = mViewModel
        addListeners()
        observeData()
    }

    private fun addListeners() { // ๋ฆฌ์Šค๋„ˆ ์ถ”๊ฐ€
        with(binding){
            listButton.setOnClickListener {
                with(Intent(this@MainActivity , ListActivity::class.java)){
                    startActivity(this)
                }
            }
        }
    }

    private fun observeData() { // LiveData ๊ด€์ฐฐ ํ•จ์ˆ˜
        with(mViewModel){
            message.observe(this@MainActivity){
                Toast.makeText(MainApplication.getInstance() , it , Toast.LENGTH_SHORT).show()
            }
        }
    }
}

 

1-1. MainActivity( ์ž…๋ ฅ์ฐฝ )์˜ ViewModel

class ActivityMainViewModel(private val repository: MainRepository) : ViewModel() {
    val userName = MutableLiveData<String>("")
    val userAge = MutableLiveData<String>("")
    val message = MutableLiveData<String>()

    fun insertUser(){ // Repository๋ฅผ ํ†ตํ•ด userDAO์˜ insertUser๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค.
        CoroutineScope(Dispatchers.IO).launch {
            val user = UserEntity(null , userName.value!! , userAge.value!!.toInt())
            repository.insertUser(user)
            CoroutineScope(Dispatchers.Main).launch {
                message.value = "์ •์ƒ์ ์œผ๋กœ ๋“ฑ๋ก๋˜์—ˆ์Šต๋‹ˆ๋‹ค."
            }
        }
    }

    fun updateUser(user : UserEntity){ // Repository๋ฅผ ํ†ตํ•ด userDAO์˜ updateUser๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค.
        CoroutineScope(Dispatchers.IO).launch {
            with(user){
                val updateUser = UserEntity(userId , userName , userAge)
                repository.updateUser(updateUser)
                CoroutineScope(Dispatchers.Main).launch{
                    message.value = "์ •์ƒ์ ์œผ๋กœ ์—…๋ฐ์ดํŠธ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค."
                }
            }
        }
    }
}

์œ„ ViewModel์€ MainActivity์™€ UpdateActivity์—์„œ ํ•จ๊ป˜ ์‚ฌ์šฉํ•  ViewModel์ž…๋‹ˆ๋‹ค.

 

1-2.  ViewModel Factory 

class MainViewModelFactory(private val repository: MainRepository) : ViewModelProvider.Factory {
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        return if(modelClass.isAssignableFrom(ActivityMainViewModel::class.java)){
            ActivityMainViewModel(repository) as T
        }else if(modelClass.isAssignableFrom(ActivityListViewModel::class.java)){
           ActivityListViewModel(repository) as T
        } else {
            throw java.lang.IllegalArgumentException("ํ•ด๋‹น ViewModel์„ ์ฐพ์„์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค!")
        }
    }
}

1-3.  Repository class

class MainRepository {
    private val userDao = UserDatabase.getInstance().userDAO()
    companion object{ // Repository๋Š” ์‹ฑ๊ธ€ํ„ด ํŒจํ„ด์œผ๋กœ instance๋ฅผ ๊ฐ€์ ธ์˜ฌ๊ฒƒ์ด๋‹ค.
        private lateinit var instance : MainRepository
        fun getInstance() : MainRepository {
            if(!::instance.isInitialized){
                instance = MainRepository()
            }
            return instance
        }
    }

    suspend fun insertUser(user : UserEntity) = userDao.insertUser(user)

    suspend fun deleteUser(user : UserEntity) = userDao.deleteUser(user)

    suspend fun updateUser(user : UserEntity) = userDao.updateUser(user)

    suspend fun getAllUser() = userDao.getAllUser()
}

2.  ListActivity(User ๋ชฉ๋ก ์กฐํšŒ ํŽ˜์ด์ง€)

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <TextView
        android:id="@+id/headerTitle"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toTopOf="parent"
        android:gravity="center"
        android:text="์œ ์ €๋ชฉ๋ก"
        android:textSize="30sp"
        android:padding="5dp"
        />

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/userListRecyclerView"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:layout_constraintTop_toBottomOf="@id/headerTitle"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        tools:listitem="@layout/user_list_item"
        />

</androidx.constraintlayout.widget.ConstraintLayout>
private const val TAG = "ListActivity"
const val EXTRA_SELECTED_USER = "selected_user" // ์„ ํƒ๋œ User ๊ฐ์ฒด๋ฅผ Intent์— ๋ณด๋‚ผ๋•Œ ์‚ฌ์šฉํ•˜๋Š” key

class ListActivity : AppCompatActivity() {
    private lateinit var binding : ActivityListBinding
    private lateinit var mRecyclerViewAdapter: UserListRecyclerViewAdapter
    private lateinit var mViewModel : ActivityListViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityListBinding.inflate(layoutInflater).also {
            setContentView(it.root)
        }
        mViewModel = ViewModelProvider(this@ListActivity , MainViewModelFactory(MainRepository.getInstance()))[ActivityListViewModel::class.java]
        observeData()
        initRecyclerView()
    }

    override fun onResume() {
        super.onResume()
        mViewModel.getAllUser()
    }

    /**
    * RecyclerView ์ดˆ๊ธฐํ™”
    **/
    private fun initRecyclerView() {
        mRecyclerViewAdapter = UserListRecyclerViewAdapter()
        mRecyclerViewAdapter.setOnMenuItemClickListener(PopupMenuClickListener())
        mRecyclerViewAdapter.setOnItemClickListener(UserItemClickListener())
        with(binding.userListRecyclerView){
            layoutManager = LinearLayoutManager(this@ListActivity)
            adapter = mRecyclerViewAdapter
        }
    }

    @SuppressLint("NotifyDataSetChanged")
    private fun observeData() {
        with(mViewModel){
            userList.observe(this@ListActivity){
                mRecyclerViewAdapter.setList(it)
                mRecyclerViewAdapter.notifyDataSetChanged()
            }
            message.observe(this@ListActivity){
                Toast.makeText(MainApplication.getInstance() , it , Toast.LENGTH_SHORT).show()
            }
        }
    }
	/**
    * PopupMenu ์„ ํƒ์‹œ ๋ฐœ์ƒํ•˜๋Š” Listener
    **/
    private inner class PopupMenuClickListener() : OnMenuItemClickListener{
        override fun onMenuItemClick(menuItem: MenuItem?): Boolean {
            when(menuItem?.itemId){
                R.id.delete -> {
                    Log.d(TAG, "onMenuItemClick: click delete")
                    val deleteUser : UserEntity
                    with(mRecyclerViewAdapter.getSelectedUser()){
                        deleteUser = UserEntity(userId , userName , userAge)
                    }
                    mViewModel.deleteSelectedUser(deleteUser)
                }
            }
            return true
        }
    }

    /**
    * RecyclerView Item ์„ ํƒ์‹œ ๋ฐœ์ƒํ•˜๋Š” Listener
    **/
    private inner class UserItemClickListener : UserListRecyclerViewAdapter.OnItemClickListener{
        override fun onItemClick(view: View, data: User, position: Int) {
            with(Intent(this@ListActivity , UpdateActivity::class.java)){
                putExtra(EXTRA_SELECTED_USER , data)
                startActivity(this)
            }
        }
    }
}

2-1.  ListActivity์˜ ViewModel

class ActivityListViewModel(private val repository: MainRepository) : ViewModel() {
    val userList = MutableLiveData<MutableList<User>>()
    val message = MutableLiveData<String>()

    fun getAllUser(){ // ๋ชจ๋“  User ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ
        CoroutineScope(Dispatchers.IO).launch {
            val list = repository.getAllUser()
            CoroutineScope(Dispatchers.Main).launch {
                userList.value = list
            }
        }
    }

    fun deleteSelectedUser(user : UserEntity){ // ์„ ํƒ๋œ User ์‚ญ์ œํ•˜๊ธฐ
        CoroutineScope(Dispatchers.IO).launch {
            repository.deleteUser(user)
            getAllUser()
            CoroutineScope(Dispatchers.Main).launch{
                message.value = "์ •์ƒ์ ์œผ๋กœ ์‚ญ์ œ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค."
            }
        }
    }
}

 

2-2.  RecyclerView๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•œ Adapter class

class UserListRecyclerViewAdapter : RecyclerView.Adapter<UserListRecyclerViewAdapter.UserListRecyclerViewHolder>() {
    private var userList = mutableListOf<User>()
    private var mOnMenuItemClickListener : OnMenuItemClickListener? = null
    private var mOnItemClickListener : OnItemClickListener? = null
    private lateinit var mSelectedData : User
    
    /**
    * ItemClickListener๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด Interface๋ฅผ ์„ ์–ธ
    **/
    interface OnItemClickListener { 
        fun onItemClick(view : View, data : User , position: Int)
    }


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

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

    override fun getItemCount() = userList.size

    fun setList(list : MutableList<User>){
        userList = list
    }
    
     /**
    * Menu Item ์„ ํƒ์‹œ ํ˜ธ์ถœ ๋  Listener๋ฅผ setting
    **/
    fun setOnMenuItemClickListener(listener: OnMenuItemClickListener){
        mOnMenuItemClickListener = listener
    }

	/**
    * RecyclierViewItem์ด ์„ ํƒ์‹œ์— ํ˜ธ์ถœ ๋  Listener๋ฅผ setting
    **/
    fun setOnItemClickListener(listener: OnItemClickListener){
        mOnItemClickListener = listener
    }

    fun getSelectedUser() = mSelectedData // ์„ ํƒ๋œ Data๋ฅผ ListActivityd์—์„œ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•œ ํ•จ์ˆ˜

    inner class UserListRecyclerViewHolder(private val binding : UserListItemBinding) : RecyclerView.ViewHolder(binding.root){
        fun bind(data : User) {
            with(binding){
                userNameTextView.text = data.userName
                userAgeTextViuew.text = data.userAge.toString()
            }
            val position = adapterPosition
            if(position != RecyclerView.NO_POSITION){
                mSelectedData = data
                itemView.setOnLongClickListener { // Long Click์‹œ์— PopupMenu๋ฅผ ์ƒ์„ฑ
                    val popupMenu = PopupMenu(binding.root.context , it)
                    popupMenu.menuInflater.inflate(R.menu.list_menu , popupMenu.menu)
                    popupMenu.setOnMenuItemClickListener(mOnMenuItemClickListener)
                    popupMenu.show()
                    true
                }

                itemView.setOnClickListener { // ์„ค์ •ํ•œ ItemClickListener๋ฅผ ํ˜ธ์ถœ
                    mOnItemClickListener?.onItemClick(it , data , position)
                }
            }
        }
    }
}

2-3.  PopupMenu์— ์‚ฌ์šฉํ•  XML ์ •์˜

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id="@+id/delete" android:title="์‚ญ์ œํ•˜๊ธฐ"/>
</menu>

3.  RecyclerView ์•„์ดํ…œ ์„ ํƒ ์‹œ ๋‚˜ํƒ€๋‚˜๋Š” UpdateActivity

class UpdateActivity : AppCompatActivity() {
    private lateinit var binding : ActivityUpdateBinding
    private lateinit var mViewModel : ActivityMainViewModel
    private var mUserInfo : User? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this , R.layout.activity_update)
        mViewModel = ViewModelProvider(this , MainViewModelFactory(MainRepository.getInstance()))[ActivityMainViewModel::class.java]
        init()
        addListener()
        observeData()
        binding.updateActivityViewModel = mViewModel
    }
    
    /**
    * ListActivity๋กœ ๋ถ€ํ„ฐ ์ „๋‹ฌ๋ฐ›์€ User ๊ฐ์ฒด๋ฅผ ํ†ตํ•ด EditText์˜ ๊ฐ’์„ settingํ•œ๋‹ค.
    **/
    private fun init() { 
        mUserInfo = intent?.extras?.getSerializable(EXTRA_SELECTED_USER) as User
        with(mViewModel){
            userName.value = mUserInfo!!.userName
            userAge.value = mUserInfo!!.userAge.toString()
        }
    }

 	
    private fun addListener() {
        /**
        * ์ž…๋ ฅ๋œ ์ •๋ณด์— ๋”ฐ๋ผ User๋ฅผ updateํ•˜๋„๋ก ํ•˜๋Š” ๋™์ž‘
        **/
        binding.updateButton.setOnClickListener {
            val user = UserEntity(mUserInfo?.userId , mViewModel.userName.value!! , mViewModel.userAge.value!!.toInt())
            mViewModel.updateUser(user)
        }
    }

    private fun observeData() {
        with(mViewModel){
            message.observe(this@UpdateActivity){
                Toast.makeText(MainApplication.getInstance() , it , Toast.LENGTH_SHORT).show()
            }
        }
    }
}

โœ”๏ธ ์œ„์—์„œ ์–ธ๊ธ‰ํ–ˆ๋“ฏ ํ•ด๋‹น Activity๋Š” ActivityMain์˜ ViewModel์„ ํ•จ๊ป˜ ์“ฐ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

 

๋งˆ์ง€๋ง‰์œผ๋กœ ์ „๋ฐ˜์ ์ธ ์‹คํ–‰ํ™”๋ฉด์„ ๋์œผ๋กœ ํฌ์ŠคํŒ…์„ ๋งˆ์น˜๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

์˜ค๋Š˜๋„ ์ฆ์ฝ”ํ•˜์„ธ์š” :)

 

์‹œ์—ฐํ™”๋ฉด