TIL 32일차 - SharedPreferences, Room (24.04.30.)

남형주·2024년 4월 30일

TIL

목록 보기
32/35

preference

1. 기본 정보

  • 어플을 종료하더라도 정보를 남기고 싶을 때 사용한다.
  • 사용 시 어플 내 폴더에 XML 파일로 키-값 세트 정보가 저장된다.
  • 응용 프로그램의 고유한 정보로 외부에서는 읽을 수 없다.

2. 사용 방식

  • getSharedPreferences(name, mode)
val pref = activity?.getSharedPreferences(
	getString(R.string.string1), Context.MODE_PRIVATE
)
  • 여러개의 파일을 사용하는 경우에 사용한다.
  • name은 데이터를 저장할 XML 파일의 이름이다.
  • mode에 쓰이는 MODE_PRIVATE는 애플리케이션 내에서만 읽기 쓰기가 가능하다는 뜻이며 다른 모드는 현재 deprecated 됨.
val edit = pref.edit() // 위의 pref를 수정할 때 사용한다.
edit.putString("", "") // 1번째는 key값, 2번째는 실제 저장할 값
edit.apply() // 수정 완료

binding.tvText.text = pref.getString("", "") // 1번째는 key값, 2번째는 null 시에 불러올 값

Room

1. 기본 정보

  • SQL을 쉽게 사용할 수 있는 데이터베이스 객체 매핑 라이브러리
  • Query를 쉽게 사용할 수 있는 API를 제공한다.
  • LiveData로 Query 결과를 쉽게 UI에 반영할 수 있다.

2. 주요 3요소

  • @Database : 클래스를 데이터베이스로 지정하는 annotation이며 RoomDatabase를 상속 받은 클래스여야 한다. Room.databaseBuilder를 이용하여 인스턴스를 생성한다.
  • @Entity : 클래스를 데이터 스키마로 지정하는 annotation이다.
  • @Dao : 클래스를 Data Access Object로 지정하는 annotation이다. 기본적인 insert, delete, update SQL은 자동으로 생성해준다. 직접 만들 수도 있다.

3. gradle 설정

plugins {
		....
    id 'kotlin-kapt'
}
.....

dependencies {

    ......

    def room_version = "2.5.1"
    implementation "androidx.room:room-runtime:$room_version"
    annotationProcessor "androidx.room:room-compiler:$room_version"
    kapt "androidx.room:room-compiler:$room_version"
    // optional - Kotlin Extensions and Coroutines support for Room
    implementation "androidx.room:room-ktx:$room_version"
    // optional - Test helpers
    testImplementation "androidx.room:room-testing:$room_version"
}

Entity

@Entity(tableName = "student_table")    // 테이블 이름을 student_table로 지정함
data class Student (
    @PrimaryKey 
		@ColumnInfo(name = "student_id") 
    val id: Int,
    val name: String
)

CREATE TABLE table_name (table_id INTEGER PRIMARY KEY, name TEXT NOT NULL);

DAO

  • interface, abstrack class로 정의되어야 한다.
  • SQL 정의 시 메소드 인자를 사용할 수 있다.
@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<> 사용 (코루틴 필요x)

    @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

    // ...
}

Database

  • RoomDatabase를 상속하여 Room 클래스를 만들어야 한다.
  • @Database에 entity와 데이터베이스 버전을 지정한다.
  • DAO를 가져올 수 있는 메소드를 만든다.
@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
        }
    }
}

Migration

  • @Database에 지정한 버전이 기존에 저장되어 있는 버전보다 높으면 수행하게 된다.
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")
    }
}

UI 연결하기

  • 코루틴 이용
myDao = MyDatabase.getDatabase(this).getMyDao()
runBlocking { // (주의) UI를 블록할 수 있는 DAO 메소드를 UI 스레드에서 바로 호출하면 안됨
    myDao.insertStudent(Student(1, "james"))  // suspend 지정되어 있음
}
val allStudents = myDao.getAllStudents() // LiveData는 Observer를 통해 비동기적으로 데이터를 가져옴
  • LiveData
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
}



오늘은 팀 편성이 새로 됐다. 발제를 새로 했다는 것이다. 핀볼을 돌렸는데 내가 팀장이 되었다.

언젠가는 내가 팀장이 걸릴 수도 있겠다고 생각은 했는데 이게 이번일 줄은 몰랐다.

어쨌거나 이왕 팀장이 된 거 조금 적극적으로 임해보기로 했다.

팀원분들도 다들 협조적으로 응해주셔서 팀 규칙도 원활하게 짤 수 있었다.

그래서 오랜만에 TIL도 작성하게 됐다. 내일부터는 알고리즘 문제도 오랜만에 풀어보기로 했다.

한동안 해이해졌던 마음을 다잡고 열심히 해봐야겠다.

이번 주차도 화이팅이다.

0개의 댓글