Android kotlin data ์ €์žฅ ๋ฐฉ๋ฒ•

๐Ÿ’Œ [Android/Kotlin] ROOM Database ์‚ฌ์šฉํ•˜๊ธฐ

๐Ÿ“Œ Room ์ด๋ž€?

  • ์•ˆ๋“œ๋กœ์ด๋“œ์—์„œ ์•ฑ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ํฌ๊ฒŒ ํŒŒ์ผ, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค, ํ”„๋ฆฌํผ๋Ÿฐ์Šค๋กœ ๋‚˜๋‰œ๋‹ค.
    ๊ทธ ์ค‘ ๋ฐ์ดํ„ฐ ๋ฒ ์ด์Šค ํ”„๋กœ๊ทธ๋ž˜๋ฐ์„ ์ด์šฉํ•˜์—ฌ ์•ˆ๋“œ๋กœ์ด๋“œ ํฐ์—์„œ DB๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ์˜คํ”ˆ์†Œ์Šค SQLite ๊ฐ€ ์žˆ๋‹ค.
  • Room์€ SQLite๋ฅผ ์™„๋ฒฝํžˆ ํ™œ์šฉํ•˜๋ฉด์„œ ์›ํ™œํ•œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์•ก์„ธ์Šค๊ฐ€ ๊ฐ€๋Šฅํ•˜๋„๋ก SQLite์— ์ถ”์ƒํ™” ๊ณ„์ธต์„ ์ œ๊ณตํ•œ๋‹ค.

๐Ÿ’œ Room์˜ ์ด์ 

  • SQL ์ฟผ๋ฆฌ์˜ ์ปดํŒŒ์ผ ์‹œ๊ฐ„ ํ™•์ธ
  • ๋ฐ˜๋ณต์ ์ด๊ณ  ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๊ธฐ ์‰ฌ์šด ์ƒ์šฉ๊ตฌ ์ฝ”๋“œ๋ฅผ ์ตœ์†Œํ™”ํ•˜๋Š” ํŽธ์˜ ์ฃผ์„
  • ๊ฐ„์†Œํ™”๋œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ด์ „ ๊ฒฝ๋กœ

๐Ÿ“Œ ์„ค์ •

build.gradle (:app)

plugins {
	id 'kotlin-kapt'
}

dependencies {
	// ROOM Database ์‚ฌ์šฉ
    implementation("androidx.room:room-runtime:2.4.3")
    annotationProcessor("androidx.room:room-compiler:2.4.3")
    kapt("androidx.room:room-compiler:2.4.3")
    implementation("androidx.room:room-ktx:2.4.3")
}

๐Ÿ“Œ ๊ธฐ๋ณธ ๊ตฌ์„ฑ ์š”์†Œ

  • Database : ๋ฐ์ดํ„ฐ ๋ฒ ์ด์Šค๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ๊ด€๋ฆฌํ•˜๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ฐ์ฒด ๋งŒ๋“ค๊ธฐ ์œ„ํ•œ ์ถ”์ƒ ํด๋ž˜์Šค.
  • Entity : ์•ฑ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ํ…Œ์ด๋ธ”.
  • DAO (Data Access Object) : ๋ฐ์ดํ„ฐ์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋Š” ๋ฉ”์†Œ๋“œ(์‚ฝ์ž…,์‚ญ์ œ,์กฐํšŒ ๋“ฑ)๋ฅผ ์ •์˜ํ•ด๋†“์€ ์ธํ„ฐํŽ˜์ด์Šค.
    Android kotlin data ์ €์žฅ ๋ฐฉ๋ฒ•

๐Ÿ“Œ Entity ์ •์˜

  • ๋ฐ์ดํ„ฐ ๋ฒ ์ด์Šค์˜ ํ…Œ์ด๋ธ” ํ–‰ ์—ญํ• .
  • ๋ฐ˜๋“œ์‹œ PrimaryKey๊ฐ€ ์žˆ์–ด์•ผ ํ•จ. (์ž๋™ ์ƒ์„ฑ์€ autoGenerate = true ์†์„ฑ ์ถ”๊ฐ€)
  • ๊ธฐ๋ณธ์ ์œผ๋กœ ํด๋ž˜์Šค ์ด๋ฆ„์ด ํ…Œ์ด๋ธ”๋ช…์ด์ง€๋งŒ ๋”ฐ๋กœ ์ง€์ •์€ @Entity(tableName = " ")์„ ์‚ฌ์šฉ.
@Entity(tableName = "table_bookmark")
data class ParkBookmarkEntity(
    @PrimaryKey
    val parkingCode: String = "",
    val parkingName: String = "",
    val addr: String = "",
    val lat: Double = 0.0,
    val lng: Double = 0.0,
    val tel: String = "",
    val operation_rule_nm: String = "",
    var isSelected:Boolean = false
)

๐Ÿ“Œ DAO ์ •์˜

  • ์ธํ„ฐํŽ˜์ด์Šค๋‚˜ ์ถ”์ƒ ํด๋ž˜์Šค๋กœ ์ •์˜ ๊ฐ€๋Šฅ.
  • ์ผ๋ฐ˜์ ์œผ๋กœ ์ธํ„ฐํŽ˜์ด์Šค๋กœ ๊ตฌํ˜„. @Dao ์–ด๋…ธํ…Œ์ด์…˜ ํ•„์ˆ˜.
  • ์•ฑ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ๋ฐ์ดํ„ฐ์™€ ์ƒํ˜ธ์ž‘์šฉํ•˜๋Š” ๋ฉ”์†Œ๋“œ๋ฅผ ํ•˜๋‚˜ ์ด์ƒ ์ •์˜.
@Dao
interface ParkDAO {

    @Query("SELECT * FROM table_bookmark")
    fun getAll(): List<ParkBookmarkEntity>

    // parkingCode ์— ํ•ด๋‹นํ•˜๋Š” selected ๊ฐ’ ๊ฐ€์ ธ์˜ค๊ธฐ
    @Query("SELECT isSelected FROM table_bookmark WHERE parkingCode = :parkingCode")
    fun getBookmark(parkingCode: String) : Boolean

    // bookmark ์ €์žฅ - ์ค‘๋ณต ๊ฐ’ ์ถฉ๋Œ ๋ฐœ์ƒ ์‹œ ์ƒˆ๋กœ ๋“ค์–ด์˜จ ๋ฐ์ดํ„ฐ๋กœ ๊ต์ฒด.
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun saveBookmark(bookmarkEntity: ParkBookmarkEntity)

    // bookmark ์‚ญ์ œ
    @Delete
    fun deleteBookmark(bookmarkEntity: ParkBookmarkEntity)
}

๐Ÿ“Œ Database ํด๋ž˜์Šค ์ •์˜

  • version ์€ Entity ์˜ ๊ตฌ์กฐ ๋ณ€๊ฒฝํ•ด์•ผ ํ•˜๋Š” ์ผ์ด ์ƒ๊ฒผ์„ ๋•Œ ์ด์ „ ๊ตฌ์กฐ์™€ ํ˜„์žฌ ๊ตฌ์กฐ๋ฅผ ๊ตฌ๋ถ„ํ•ด์ฃผ๋Š” ์—ญํ• .
@Database(entities = [ParkBookmarkEntity::class], version = 1)
abstract class ParkDatabase : RoomDatabase() {
    abstract fun parkDao(): ParkDAO

    // ๋ฐ์ดํ„ฐ ๋ฒ ์ด์Šค ๊ฐ์ฒด๋ฅผ ์‹ฑ๊ธ€ํ†ค์œผ๋กœ ์ธ์Šคํ„ด์Šค.
    companion object {
        private var instance: ParkDatabase? = null

        @Synchronized
        fun getInstance(context: Context): ParkDatabase? {
            if (instance == null)
                synchronized(ParkDatabase::class) {
                    instance = Room.databaseBuilder(
                        context.applicationContext,
                        ParkDatabase::class.java,
                        "park.db"
                    )
                        .build()
                }
            return instance
        }

        fun destroyInstance() {
            instance = null
        }
    }
}

๐Ÿ“Œ ์‚ฌ์šฉ

  • ์„œ์šธ์‹œ ๊ณต๊ณต๋ฐ์ดํ„ฐ์˜ ์ฃผ์ฐจ์žฅ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์™€ ๋งต์— ๋ฟŒ๋ ค์ค€ ํ›„, ์ฆ๊ฒจ์ฐพ๊ธฐ์— ์ €์žฅํ•˜๋Š” ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•ด๋ณด๊ฒ ๋‹ค.
  • ๋ฐ์ดํ„ฐ ๋ฒ ์ด์Šค ์ž‘์—…์€ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ์—์„œ ์ž‘์—…ํ•  ์ˆ˜ ์—†์œผ๋ฏ€๋กœ ์ฝ”๋ฃจํ‹ด ์„ ์ด์šฉํ•œ๋‹ค.
class ParkBottomSheetFragment(val mContext : Context) : BottomSheetDialogFragment() {

	private var db: ParkDatabase? = null

	override fun onCreateView(...): View? {
        super.onCreateView(inflater, container, savedInstanceState)
        
        // ์ƒ๋žต
        
        db = ParkDatabase.getInstance(mContext)

}

๐Ÿ’œ ์ฆ๊ฒจ์ฐพ๊ธฐ ํ•˜๊ธฐ

// ๋ฒ„ํŠผ xml
<androidx.appcompat.widget.AppCompatButton
            android:id="@+id/btn_bookmark"
            android:layout_width="wrap_content"
            android:layout_height="36dp"
            android:background="@drawable/selector_btn_bookmark"
            android:drawableLeft="@drawable/ic_heart"
            android:paddingHorizontal="@dimen/margin_20"
            android:layout_marginEnd="10dp"
            android:text="์ฆ๊ฒจ์ฐพ๊ธฐ"
            android:textColor="@color/white"
            android:textSize="16dp"
            android:textStyle="bold" />
            
// selector
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_selected="false">
        <shape android:shape="rectangle">
            <solid android:color="@color/main_color" />
        </shape>
    </item>
    <item android:state_selected="true">
        <shape android:shape="rectangle">
            <solid android:color="@color/main_purple1" />
        </shape>
    </item>
</selector>
// ์ฆ๊ฒจ์ฐพ๊ธฐ ์ €์žฅํ•˜๊ธฐ
btnBookmark.setOnClickListener {
	// UI ์™€ ์ƒํ˜ธ์ž‘์šฉํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ.
	CoroutineScope(Dispatchers.Main).launch {
		val temp: Deferred<Boolean> = async(Dispatchers.IO) { // async ๋กœ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜
			val isBookmarked = db!!.parkDao().getBookmark(data.PARKING_CODE)
			if (isBookmarked) { // ์ด๋ฏธ ์ฆ๊ฒจ์ฐพ๊ธฐ ๋˜์–ด์žˆ์œผ๋ฉด ์‚ญ์ œ
				db!!.parkDao().deleteBookmark(bookmarkData)
				false
                
                
			} else { // ์—†์œผ๋ฉด ์ฆ๊ฒจ์ฐพ๊ธฐ ์ €์žฅ
				db!!.parkDao().saveBookmark(bookmarkData)
				true
			}
		}
        			// UI ๋ณ€๊ฒฝ
                    val selected = temp.await() // async ์ž‘์—…์ด ์™„๋ฃŒ ๋˜๊ณ  ๋‚œ ํ›„ ํ˜ธ์ถœ.
                    it.isSelected = selected
                }
            }

๐Ÿ’œ ์ฆ๊ฒจ์ฐพ๊ธฐ ๋ฆฌ์ŠคํŠธ ๊ฐ€์ ธ์˜ค๊ธฐ

Android kotlin data ์ €์žฅ ๋ฐฉ๋ฒ•

// ์ฆ๊ฒจ์ฐพ๊ธฐ ๋œ ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ
btnGetData.setOnClickListener {
		CoroutineScope(Dispatchers.IO).launch { // ์ฝ”๋ฃจํ‹ด ์‚ฌ์šฉ ๋น„๋™๊ธฐ๋กœ ์‹คํ–‰.
			val parkData = db!!.parkDao().getAll()
			Utils.Log("db ์กฐํšŒ -> $parkData")
		}
}

๐Ÿ’œ ์ฆ๊ฒจ์ฐพ๊ธฐ ์‚ญ์ œํ•˜๊ธฐ

// ์ฆ๊ฒจ์ฐพ๊ธฐ ์‚ญ์ œํ•˜๊ธฐ
btnDeleteData.setOnClickListener {
	CoroutineScope(Dispatchers.IO).launch { // ์ฝ”๋ฃจํ‹ด ์‚ฌ์šฉ ๋น„๋™๊ธฐ๋กœ ์‹คํ–‰.
		db!!.parkDao().deleteBookmark(bookmarkData)
			Utils.Log("์ฆ๊ฒจ์ฐพ๊ธฐ ์‚ญ์ œ ์™„๋ฃŒ")
		}
}

์ˆœ์„œ๋Œ€๋กœ ํด๋ฆญํ•ด์„œ ์ฆ๊ฒจ์ฐพ๊ธฐ๋ฅผ ํ•˜๊ณ , ๋ฆฌ์ŠคํŠธ๋ฅผ ๊ฐ€์ ธ์˜ค๊ณ , ์‚ญ์ œํ•œ ํ›„, ์ž˜ ์ฐํžˆ๋Š” ๋กœ๊ทธ๋“ค์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

Android kotlin data ์ €์žฅ ๋ฐฉ๋ฒ•

๐Ÿ’Œ๐Ÿ“Œ๐Ÿ’œ๐Ÿค
๊ณต์‹ ๋ฌธ์„œ : https://developer.android.com/training/data-storage/room?hl=ko