2024. 5. 1. 20:24, ๐ฑAndroid Study
๋ฐ์ํ
์๋๋ก์ด๋์์์ ์๊ตฌ(๋นํ๋ฐ์ฑ) ๋ฐ์ดํฐ ์ ์ฅ๋ฒ
- SharedPreference
- ๋ฐ์ดํฐ๋ฒ ์ด์ค
- ํ์ผ
1) Room ๊ฐ์
- SQLite๋ฅผ ์ฝ๊ฒ ์ฌ์ฉํ ์ ์๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค ๊ฐ์ฒด ๋งคํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
- ์ฝ๊ฒ Query๋ฅผ ์ฌ์ฉํ ์ ์๋ API๋ฅผ ์ ๊ณต
- Query๋ฅผ ์ปดํ์ผ ์๊ฐ์ ๊ฒ์ฆํจ
- Query๊ฒฐ๊ณผ๋ฅผ LiveData๋กํ์ฌ ๋ฐ์ดํฐ๋ฒ ์ด์ค๊ฐ ๋ณ๊ฒฝ๋ ๋ ๋ง๋ค ์ฝ๊ฒ UI๋ฅผ ๋ณ๊ฒฝํ ์ ์์
- SQLite ๋ณด๋ค Room์ ์ฌ์ฉํ ๊ฒ์ ๊ถ์ฅํจ
2) Room ์ฃผ์ 3์์
- @Database
- ํด๋์ค๋ฅผ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ก ์ง์ ํ๋ annotation, RoomDatabase๋ฅผ ์์ ๋ฐ์ ํด๋์ค์ฌ์ผ ํจ
- Room.databaseBuilder๋ฅผ ์ด์ฉํ์ฌ ์ธ์คํด์ค๋ฅผ ์์ฑํจ - @Entity
- ํด๋์ค๋ฅผ ํ ์ด๋ธ ์คํค๋ง๋ก ์ง์ ํ๋ annotation - @Dao
- ํด๋์ค๋ฅผ DAO(Data Access Object)๋ก ์ง์ ํ๋ annotation
- ๊ธฐ๋ณธ์ ์ธ insert, delete, update SQL์ ์๋์ผ๋ก ๋ง๋ค์ด์ฃผ๋ฉฐ, ๋ณต์กํ SQL์ ์ง์ ๋ง๋ค ์ ์์
3) gradle ํ์ผ ์ค์
plugins {
..
kotlin("kapt")
}
..
dependencies {
..
val room_version = "2.6.1" // ์ต์ ๋ฒ์ ์๋๋ก์ด๋ ๊ฒ์ํด์ ๋ค์ด๊ฐ๊ธฐ
implementation("androidx.room:room-runtime:$room_version")
annotationProcessor("androidx.room:room-compiler:$room_version")
kapt("androidx.room:room-compiler:$room_version") // To use Kotlin annotation processing tool (kapt)
// To use Kotlin Symbol Processing (KSP)
ksp("androidx.room:room-compiler:$room_version")
// optional - Kotlin Extensions and Coroutines support for Room
implementation("androidx.room:room-ktx:$room_version")
// optional - RxJava2 support for Room
implementation("androidx.room:room-rxjava2:$room_version")
// optional - RxJava3 support for Room
implementation("androidx.room:room-rxjava3:$room_version")
// optional - Guava support for Room, including Optional and ListenableFuture
implementation("androidx.room:room-guava:$room_version")
// optional - Test helpers
testImplementation("androidx.room:room-testing:$room_version")
// optional - Paging 3 Integration
implementation("androidx.room:room-paging:$room_version")
}
- Room์ ์๋๋ก์ด๋ ์ํคํ ์ฒ์ ํฌํจ๋์ด ์์
- ์ฌ์ฉํ๊ธฐ ์ํด build.gradle ํ์ผ์ dependencies์ ์๋ ๋ด์ฉ์ ์ถ๊ฐํด์ผ ํจ
- Androidx ์ฌ์ฉํ๋ ๊ฒฝ์ฐ๋ฅผ ๊ฐ์ ํจ, Android Studio์ SDK๋ ์ต์ ๋ฒ์ ์ผ๋ก ์ฌ์ฉ
- 'kotlin-kapt' ํ๋ฌ๊ทธ์ธ ์ถ๊ฐ
- dependencies ์ถ๊ฐ
4) Entity ์์ฑ
CREATE TABLE student_table (student_id INTEGER PRIMARY KEY, name TEXT NOT NULL); ์ ๊ฒฝ์ฐ
@Entity(tableName = "student_table") // ํ
์ด๋ธ ์ด๋ฆ์ student_table๋ก ์ง์ ํจ
data class Student (
@PrimaryKey @ColumnInfo(name = "student_id") val id: Int,
val name: String
)
- Entity๋ ํ
์ด๋ธ ์คํค๋ง ์ ์
(๋ชจ๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ ํ ์ด๋ธ์ ๊ฐ์ง๊ณ ์๊ณ , ๊ฐ ํ ์ด๋ธ๋ค์ ์ด๋ค ์ปฌ๋ผ๋ค์ ๊ฐ์ง๊ณ ์์์ง ์ ์ํ๋ ๊ฒ์ด ์คํค๋ง) - @Entity data class Student
5) DAO ์์ฑ
@Query("SELECT * from table") fun getAllData() : List<Data>
- DAO๋ interface๋ abstract class๋ก ์ ์๋์ด์ผ ํจ
- Annotation์ SQL ์ฟผ๋ฆฌ๋ฅผ ์ ์ํ๊ณ ๊ทธ ์ฟผ๋ฆฌ๋ฅผ ์ํ ๋ฉ์๋๋ฅผ ์ ์ธ
- ๊ฐ๋ฅํ annotation์ผ๋ก @Insert, @Update, @Delete, @Query๊ฐ ์์
- @Insert, @Update, @Delete๋ SQL ์ฟผ๋ฆฌ๋ฅผ ์์ฑํ์ง ์์๋ ์ปดํ์ผ๋ฌ๊ฐ ์๋์ผ๋ก ์์ฑํจ
- @Insert๋ @Update๋ key๊ฐ ์ค๋ณต๋๋ ๊ฒฝ์ฐ ์ฒ๋ฆฌ๋ฅผ ์ํด onConflict๋ฅผ ์ง์ ํ ์ ์์
- OnConflictStrategy.ABORT: key ์ถฉ๋์ ์ข ๋ฃ
- OnConflictStrategy.IGNORE: key ์ถฉ๋ ๋ฌด์
- OnConflictStrategy.REPLACE: key ์ถฉ๋์ ์๋ก์ด ๋ฐ์ดํฐ๋ก ๋ณ๊ฒฝ
- @Update๋ @Delete๋ primary key์ ํด๋น๋๋ ํํ์ ์ฐพ์์ ๋ณ๊ฒฝ/์ญ์ ํจ
@Query("SELECT * from table") fun getAllData() : LiveData<List<Data>>
- @Query๋ก ๋ฆฌํด๋๋ ๋ฐ์ดํฐ์ ํ์
์ LiveData<>๋ก ํ๋ฉด,
๋์ค์ ์ด ๋ฐ์ดํฐ๊ฐ ์ ๋ฐ์ดํธ๋ ๋ Observer๋ฅผ ํตํด ํ ์ ์์
@Query("SELECT * FROM student_table WHERE name = :sname")
suspend fun getStudentByName(sname: String): List<Student>
- @Query์ SQL์ ์ ์ํ ๋ ๋ฉ์๋์ ์ธ์๋ฅผ ์ฌ์ฉํ ์ ์์ (์: ์ธ์ sname์ SQL์์ :sname์ผ๋ก ์ฌ์ฉ)
@Dao
interface MyDAO {
@Insert(onConflict = OnConflictStrategy.REPLACE) // INSERT, key ์ถฉ๋์ด ๋๋ฉด ์ ๋ฐ์ดํฐ๋ก ๊ต์ฒด
suspend fun insertStudent(student: Student)
@Query("SELECT * FROM student_table")
fun getAllStudents(): LiveData<List<Student>> // LiveData<> ์ฌ์ฉ
@Query("SELECT * FROM student_table WHERE name = :sname")
suspend fun getStudentByName(sname: String): List<Student>
@Delete
suspend fun deleteStudent(student: Student); // primary key is used to find the student
// ...
}
- fun ์์ suspend๋ Kotlin coroutine์ ์ฌ์ฉํ๋ ๊ฒ
๋์ค์ ์ด ๋ฉ์๋๋ฅผ ๋ถ๋ฅผ ๋๋ runBlocking {} ๋ด์์ ํธ์ถํด์ผ ํจ - LiveData๋ ๋น๋๊ธฐ์ ์ผ๋ก ๋์ํ๊ธฐ ๋๋ฌธ์ coroutine์ผ๋ก ํ ํ์๊ฐ ์์
- @Query("SELECT * from table") fun getAllData() : LiveData<List<Data>>
@Query("SELECT * FROM student_table WHERE name = :sname")
suspend fun getStudentByName(sname: String): List<Student> - ์ธ์ sname์ SQL์์ :sname์ผ๋ก ์ฌ์ฉ
6) Database ์์ฑ
@Database(entities = [Student::class, ClassInfo::class, Enrollment::class, Teacher::class], version = 1)
abstract class MyDatabase : RoomDatabase() {
abstract fun getMyDao() : MyDAO
companion object {
private var INSTANCE: MyDatabase? = null
private val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) { ์๋ต }
}
private val MIGRATION_2_3 = object : Migration(2, 3) {
override fun migrate(database: SupportSQLiteDatabase) { ์๋ต }
}
fun getDatabase(context: Context) : MyDatabase {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(
context, MyDatabase::class.java, "school_database")
.addMigrations(MIGRATION_1_2, MIGRATION_2_3)
.build()
}
return INSTANCE as MyDatabase
}
}
}
- RoomDatabase๋ฅผ ์์ํ์ฌ ์์ ์ Room ํด๋์ค๋ฅผ ๋ง๋ค์ด์ผ ํจ
- ํฌํจ๋๋ Entity๋ค๊ณผ ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ฒ์ (version)์ @Database annotation์ ์ง์ ํจ
- version์ด ๊ธฐ์กด์ ์ ์ฅ๋์ด ์๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ณด๋ค ๋์ผ๋ฉด, ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ openํ ๋ migration์ ์ํํ๊ฒ ๋จ
- Migration ์ํ ๋ฐฉ๋ฒ์ RoomDatabase ๊ฐ์ฒด์ addMigration() ๋ฉ์๋๋ฅผ ํตํด ์๋ ค์ค - DAO๋ฅผ ๊ฐ์ ธ์ฌ ์ ์๋ getter ๋ฉ์๋๋ฅผ ๋ง๋ฌ
- ์ค์ ๋ฉ์๋ ์ ์๋ ์๋์ผ๋ก ์์ฑ๋จ - Room ํด๋์ค์ ์ธ์คํด์ค๋ ํ๋๋ง ์์ผ๋ฉด ๋๋ฏ๋ก Singleton ํจํด์ ์ฌ์ฉ
- Room ํด๋์ค์ ๊ฐ์ฒด ์์ฑ์ Room.databaseBuilder()๋ฅผ ์ด์ฉํจ
7) Migration
Room.databaseBuilder(...).addMigrations(MIGRATION_1_2, MIGRATION_2_3)
private val MIGRATION_1_2 = object : Migration(1, 2) { // version 1 -> 2
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE student_table ADD COLUMN last_update INTEGER")
}
}
private val MIGRATION_2_3 = object : Migration(2, 3) { // version 2 -> 3
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE class_table ADD COLUMN last_update INTEGER")
}
}
- ์์์ MyRoomDatabase๊ฐ์ฒด ์์ฑ ํ addMigrations() ๋ฉ์๋๋ฅผ ํธ์ถํ์ฌ Migration ๋ฐฉ๋ฒ์ ์ง์ ํ์
- ์ฌ๋ฌ๊ฐ์ Migration ์ง์ ๊ฐ๋ฅ
8) UI์ ์ฐ๊ฒฐ
myDao = MyDatabase.getDatabase(this).getMyDao()
runBlocking { // (์ฃผ์) UI๋ฅผ ๋ธ๋กํ ์ ์๋ DAO ๋ฉ์๋๋ฅผ UI ์ค๋ ๋์์ ๋ฐ๋ก ํธ์ถํ๋ฉด ์๋จ
myDao.insertStudent(Student(1, "james")) // suspend ์ง์ ๋์ด ์์
}
val allStudents = myDao.getAllStudents() // LiveData๋ Observer๋ฅผ ํตํด ๋น๋๊ธฐ์ ์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ด
- ์๋๋ก์ด๋ ์ํคํ
์ฒ์ ๋ฐ๋ผ Repository์ ViewModel์ ์ฌ์ฉํ๊ธธ ๊ถ์ฅํ์ง๋ง
- ์ฐธ๊ณ : https://codelabs.developers.google.com/codelabs/android-room-with-a-view-kotlin/
- ์ฌ๊ธฐ์์๋ Room๊ณผ LiveData ์ฌ์ฉ๋ง ๋ค๋ฃธ - RoomDatabase๊ฐ์ฒด์์ DAO ๊ฐ์ฒด๋ฅผ ๋ฐ์์ค๊ณ , ์ด DAO๊ฐ์ฒด์ ๋ฉ์๋๋ฅผ ํธ์ถํ์ฌ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ์ ๊ทผํจ
9) UI์ ์ฐ๊ฒฐ - LiveData
LiveData?
- ์๋๋ก์ด๋ ์ํคํ ์ฒ ์ปดํฌ๋ํธ์ ์ผ๋ถ๋ก, ๊ด์ฐฐ ๊ฐ๋ฅํ ๋ฐ์ดํฐ ํ๋ ํด๋์ค
- ์ด๋ฅผ ํตํด UI ์ปดํฌ๋ํธ(์: ์กํฐ๋นํฐ, ํ๋๊ทธ๋จผํธ)๋ ๋ฐ์ดํฐ์ ๋ณ๊ฒฝ ์ฌํญ์ ๊ด์ฐฐํ๊ณ ์ด์ ๋ฐ์
- ๋ฐ์ดํฐ๊ฐ ๋ณ๊ฒฝ๋ ๋๋ง๋ค LiveData๋ ๊ด์ฐฐ์์๊ฒ ์๋ฆผ์ ๋ณด๋
LiveData์ ํต์ฌ ํน์ง
- 1. ์๋ช
์ฃผ๊ธฐ ์ธ์
- LiveData๋ ์๋๋ก์ด๋์ ์๋ช ์ฃผ๊ธฐ๋ฅผ ์ธ์
- ์ฆ, ์กํฐ๋นํฐ๋ ํ๋๊ทธ๋จผํธ์ ์๋ช ์ฃผ๊ธฐ ์ํ์ ๋ฐ๋ผ ์๋ฆผ์ ์๋์ผ๋ก ๊ด๋ฆฌ
- ์ด๋ก ์ธํด ๋ฉ๋ชจ๋ฆฌ ๋์ ๋ฐ ์กํฐ๋นํฐ๊ฐ ์ข ๋ฃ๋ ์ํ์์ ๋ฐ์ํ ์ ์๋ ํฌ๋์๋ฅผ ๋ฐฉ์ง - 2. UI์ ๋ฐ์ดํฐ ์ํ์ ์ผ๊ด์ฑ ์ ์ง
- LiveData๋ฅผ ์ฌ์ฉํ๋ฉด UI๊ฐ ๋ฐ์ดํฐ์ ์ผ๊ด๋๊ฒ ์ ์ง
- ๋ฐ์ดํฐ๊ฐ ๋ณ๊ฒฝ๋ ๋ UI๊ฐ ์๋์ผ๋ก ๊ฐฑ์ ๋๊ธฐ ๋๋ฌธ์, ์ฌ์ฉ์์๊ฒ ์ต์ ์ ์ ๋ณด๋ฅผ ์ ๊ณต - 3. ์ค์ ์ง์ค์ ์ธ ๋ฐ์ดํฐ ๊ด๋ฆฌ
- LiveData๋ ๋ทฐ๋ชจ๋ธ(ViewModel)๊ณผ ํจ๊ป ์ฌ์ฉ๋์ด ์ฑ์ ๋ฐ์ดํฐ๋ฅผ ์ค์์์ ๊ด๋ฆฌํ ์ ์๊ฒ ํจ
- ์ด๋ ๋ฐ์ดํฐ ๊ด๋ฆฌ๋ฅผ ๋์ฑ ํจ์จ์ ์ผ๋ก ๋ง๋ค์ด ์ค - 4. ๋ฐ์ดํฐ ๋ณ๊ฒฝ์ ๋ฐ๋ฅธ ์๋ ์
๋ฐ์ดํธ
- LiveData์ ๊ด์ฐฐ์๋ ์ค์ง ํ์ฑ ์๋ช ์ฃผ๊ธฐ ์ํ(active lifecycle state)์ ์ปดํฌ๋ํธ์๋ง ์๋ฆผ์ ๋ณด๋
- ์ด๋ ๋ฐ์ดํฐ๊ฐ ๋ณ๊ฒฝ๋ ๋ ํ์ฑ ์ํ์ UI๋ง ์ ๋ฐ์ดํธ๋์ด, ๋ถํ์ํ ๋ฆฌ์์ค ์ฌ์ฉ์ ์ค์ฌ์ค
val allStudents = myDao.getAllStudents()
allStudents.observe(this) { // Observer::onChanged() ๋ SAM ์ด๊ธฐ ๋๋ฌธ์ lambda๋ก ๋์ฒด
val str = StringBuilder().apply {
for ((id, name) in it) {
append(id)
append("-")
append(name)
append("\n")
}
}.toString()
binding.textStudentList.text = str
}
- LiveData<> ํ์
์ผ๋ก ๋ฆฌํด๋๋ DAO ๋ฉ์๋ ๊ฒฝ์ฐ
- observe() ๋ฉ์๋๋ฅผ ์ด์ฉํ์ฌ Observer๋ฅผ ์ง์
- ๋ฐ์ดํฐ๊ฐ ๋ณ๊ฒฝ๋ ๋๋ง๋ค ์๋์ผ๋ก Observer์ onChanged()๊ฐ ํธ์ถ๋จ - LiveData<>๋ฅผ ๋ฆฌํดํ๋ DAO ๋ฉ์๋๋ Observer๋ฅผ ํตํด ๋น๋๊ธฐ์ ์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ๋ฐ๊ธฐ ๋๋ฌธ์, UI ์ค๋ ๋์์ ์ง์ ํธ์ถํด๋ ๋ฌธ์ ์์
10) Room Database์ ์ฃผ์ ์ด๋ ธํ ์ด์ (Annotations)
- @Database
- ๋ฐ์ดํฐ๋ฒ ์ด์ค ํด๋์ค๋ฅผ ์ ์ํ ๋ ์ฌ์ฉํจ
- ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ํฌํจ๋ ์ํฐํฐ์ ๋ฒ์ ์ ๋ช ์ํจ - @Entity
- ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ด์ ํ ์ด๋ธ์ ์ ์ํ ๋ ์ฌ์ฉํจ
- ํด๋์ค ์ด๋ฆ์ด ํ ์ด๋ธ ์ด๋ฆ์ผ๋ก ์ฌ์ฉ๋๋ฉฐ, ํ๋๋ ์ปฌ๋ผ์ผ๋ก ๋งคํ๋จ - @PrimaryKey
- ์ํฐํฐ์ ๊ธฐ๋ณธ ํค(primary key)๋ฅผ ์ ์ํจ
- ์ ๋ํฌํ ๊ฐ์ด์ด์ผ ํ๋ฉฐ, ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ด์์ ๊ฐ ์ํฐํฐ๋ฅผ ๊ตฌ๋ถํ๋ ๋ฐ ์ฌ์ฉ๋จ - @ColumnInfo
- ํ ์ด๋ธ์ ์ปฌ๋ผ ์ ๋ณด๋ฅผ ์ธ๋ถ์ ์ผ๋ก ์ ์ํ ๋ ์ฌ์ฉํจ
- ์ปฌ๋ผ์ ์ด๋ฆ, ํ์ , ์ธ๋ฑ์ค ๋ฑ์ ์ค์ ํ ์ ์์ - @Dao
- ๋ฐ์ดํฐ ์ ๊ทผ ๊ฐ์ฒด(Data Access Object)๋ฅผ ์ ์ํ ๋ ์ฌ์ฉํจ
- ๋ฐ์ดํฐ๋ฒ ์ด์ค์ CRUD(Create, Read, Update, Delete) ์ฐ์ฐ์ ์ํ ๋ฉ์๋๋ฅผ ํฌํจํจ - @Insert
- ๋ฐ์ดํฐ๋ฅผ ์ฝ์ ํ๋ ๋ฉ์๋์ ์ฌ์ฉํจ
- ํด๋น ๋ฉ์๋๋ ์ํฐํฐ๋ฅผ ์ธ์๋ก ๋ฐ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ถ๊ฐํจ - @Query
- ๋ณต์กํ SQL ์ฟผ๋ฆฌ๋ฅผ ์คํํ๋ ๋ฉ์๋์ ์ฌ์ฉํจ
- ๋ฉ์๋์ ์ฃผ์ด์ง SQL ์ฟผ๋ฆฌ๋ฅผ ์คํํ์ฌ ๊ฒฐ๊ณผ๋ฅผ ๋ฐํํจ - @Update
- ๋ฐ์ดํฐ๋ฅผ ์ ๋ฐ์ดํธํ๋ ๋ฉ์๋์ ์ฌ์ฉํจ
- ์ธ์๋ก ๋ฐ์ ์ํฐํฐ์ ๋ฐ์ดํฐ๋ก ๊ธฐ์กด ๋ ์ฝ๋๋ฅผ ๊ฐฑ์ ํจ - @Delete
- ๋ฐ์ดํฐ๋ฅผ ์ญ์ ํ๋ ๋ฉ์๋์ ์ฌ์ฉํจ
- ์ธ์๋ก ๋ฐ์ ์ํฐํฐ๋ฅผ ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ์ ๊ฑฐํจ - @Transaction
- ๋ฉ์๋๊ฐ ํ๋์ ํธ๋์ญ์ ์ผ๋ก ์คํ๋์ด์ผ ํจ์ ๋ํ๋
- ์ฌ๋ฌ ์ฐ์ฐ์ ํ๋์ ์์ ์ผ๋ก ๋ฌถ์ด ์คํํ ๋ ์ฌ์ฉํจ - @ForeignKey
- ์ํฐํฐ ๊ฐ์ ์ธ๋ ํค ๊ด๊ณ๋ฅผ ์ ์ํ ๋ ์ฌ์ฉํจ
- ์ฐธ์กฐ ๋ฌด๊ฒฐ์ฑ์ ์ ์งํ๋ ๋ฐ ๋์์ ์ค - @Index
- ํน์ ์ปฌ๋ผ์ ์ธ๋ฑ์ค๋ฅผ ์์ฑํ ๋ ์ฌ์ฉํจ
- ์ฟผ๋ฆฌ ์ฑ๋ฅ์ ํฅ์์ํค๋ ๋ฐ ์ ์ฉํจ
11) Room ์์
๋๋ณด๊ธฐ
build.gradle(:app)
plugins {
alias(libs.plugins.androidApplication)
alias(libs.plugins.jetbrainsKotlinAndroid)
kotlin("kapt")
}
android {
namespace = "com.limheejin.test"
compileSdk = 34
defaultConfig {
applicationId = "com.limheejin.test"
minSdk = 26
targetSdk = 34
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
buildFeatures {
viewBinding = true
}
}
dependencies {
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.appcompat)
implementation(libs.material)
implementation(libs.androidx.activity)
implementation(libs.androidx.constraintlayout)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
val room_version = "2.6.1"
implementation("androidx.room:room-runtime:$room_version")
annotationProcessor("androidx.room:room-compiler:$room_version")
kapt("androidx.room:room-compiler:$room_version")
implementation("androidx.room:room-ktx:$room_version")
testImplementation("androidx.room:room-testing:$room_version")
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<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"
android:padding="5dp"
tools:context=".MainActivity">
<EditText
android:id="@+id/edit_student_id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ems="10"
android:hint="ID"
android:inputType="number"
app:layout_constraintEnd_toStartOf="@+id/query_student"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintHorizontal_chainStyle="spread_inside"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/edit_student_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ems="10"
android:hint="student name"
android:inputType="textPersonName"
app:layout_constraintEnd_toStartOf="@+id/add_student"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintHorizontal_chainStyle="spread_inside"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/edit_student_id" />
<Button
android:id="@+id/add_student"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Add Student"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/edit_student_name"
app:layout_constraintTop_toBottomOf="@+id/query_student" />
<Button
android:id="@+id/query_student"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Query Student"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/edit_student_id"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Result of Query Student"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/edit_student_name" />
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="Student List"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_query_student" />
<TextView
android:id="@+id/text_query_student"
android:layout_width="0dp"
android:layout_height="100sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView" />
<TextView
android:id="@+id/text_student_list"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView2" />
</androidx.constraintlayout.widget.ConstraintLayout>
MyEntity
package com.limheejin.test
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "student_table")
data class Student(
@PrimaryKey @ColumnInfo(name = "student_id") val id: Int,
val name: String
)
MyDatabase.kt
package com.limheejin.test
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
@Database(entities = [Student::class], exportSchema = false, version = 1)
abstract class MyDatabase : RoomDatabase() {
abstract fun getMyDao(): MyDAO
companion object {
private var INSTANCE: MyDatabase? = null
private val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(db: SupportSQLiteDatabase) {
}
}
private val MIGRATION_2_3 = object : Migration(2, 3) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("ALTER TABLE student_table ADD COLUMN last_update INTEGER")
}
}
fun getDatabase(context: Context): MyDatabase {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(
context, MyDatabase::class.java, "school_database"
)
.addMigrations(MIGRATION_1_2, MIGRATION_2_3)
.build()
// for in-memory database
/*INSTANCE = Room.inMemoryDatabaseBuilder(
context, MyDatabase::class.java
).build()*/
}
return INSTANCE as MyDatabase
}
}
}
MyDao
package com.limheejin.test
import androidx.lifecycle.LiveData
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
@Dao
interface MyDAO {
@Insert(onConflict = OnConflictStrategy.REPLACE) // INSERT, key ์ถฉ๋์ด ๋๋ฉด ์ ๋ฐ์ดํฐ๋ก ๊ต์ฒด
suspend fun insertStudent(student: Student) // suspend๋ ์ฝ๋ฃจํด์ ์ฌ์ฉํ๋ ๊ฒ (๋ผ์ด๋ธ๋ฐ์ดํฐ์ ๊ฒฝ์ฐ ํ์ ์์)
@Query("SELECT * FROM student_table")
fun getAllStudents(): LiveData<List<Student>> // LiveData<> ์ฌ์ฉ
@Query("SELECT * FROM student_table WHERE name = :sname") // ๋ฉ์๋ ์ธ์๋ฅผ SQL๋ฌธ์์ :๋ฅผ ๋ถ์ฌ ์ฌ์ฉ
suspend fun getStudentByName(sname: String): List<Student>
@Delete
suspend fun deleteStudent(student: Student);
}
MainActivity.kt
package com.limheejin.test
import android.os.Bundle
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import com.limheejin.test.databinding.ActivityMainBinding
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class MainActivity : AppCompatActivity() {
private val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }
lateinit var myDao: MyDAO
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
myDao = MyDatabase.getDatabase(this).getMyDao()
val allStudents = myDao.getAllStudents()
allStudents.observe(this) {
val str = StringBuilder().apply {
for ((id, name) in it) {
append(id)
append("-")
append(name)
append("\n")
}
}.toString()
binding.textStudentList.text = str
}
binding.addStudent.setOnClickListener {
val id = binding.editStudentId.text.toString().toInt()
val name = binding.editStudentName.text.toString()
if (id > 0 && name.isNotEmpty()) {
CoroutineScope(Dispatchers.IO).launch {
myDao.insertStudent(Student(id, name))
}
}
binding.editStudentId.text = null
binding.editStudentName.text = null
}
binding.queryStudent.setOnClickListener {
val name = binding.editStudentName.text.toString()
CoroutineScope(Dispatchers.IO).launch {
val results = myDao.getStudentByName(name)
if (results.isNotEmpty()) {
val str = StringBuilder().apply {
results.forEach { student ->
append(student.id)
append("-")
append(student.name)
}
}
withContext(Dispatchers.Main) {
binding.textQueryStudent.text = str
}
} else {
withContext(Dispatchers.Main) {
binding.textQueryStudent.text = ""
}
}
}
}
}
}
๋ฐ์ํ
๐ฌ C O M M E N T