본문 바로가기
개발/자세히 쳐다 보면...

토이프로젝트 1-5 room 써보기

by lonewhitedot 2023. 3. 12.
반응형

먼저 Room에 대한 간단한 설명은...

 

Room이 뭐야? 간단히 설명

 

Room이 뭐야? 간단히 설명

Room은 안드로이드에서 SQLite 데이터베이스를 쉽게 사용할 수 있도록 도와주는 라이브러리야. Room은 Android Architecture Components 라이브러리 중 하나로, 안드로이드 앱의 데이터베이스를 관리하고 제

www.lonewhite.com

먼저 Room을 가져오려면 maven을 통해 가져와햐해

 

Maven이 뭐냐고?

 

maven이란? 개념

 

maven이란? 개념

Maven은 자바 기반 프로젝트의 빌드, 관리, 배포를 자동화하기 위한 도구야. Maven은 Apache Software Foundation라는 곳에서 개발하였으며, 프로젝트의 라이프사이클 관리와 의존성 관리를 중심으로 제공

www.lonewhite.com

 

현재 최신 버전은 2.5.0이고, 그래들에 아래처럼 room을 가져오게 추가해보자

dependencies {
    def room_version = "2.5.0"

    implementation "androidx.room:room-runtime:$room_version"
    annotationProcessor "androidx.room:room-compiler:$room_version"
    // To use Kotlin annotation processing tool (kapt)
    kapt "androidx.room:room-compiler:$room_version"
}

 

그리고 룸 쓸때 코틀린으로 어노테이션도 써야해서 kapt를 쓰니깐.. 그 플러그인도 설정해야함

plugins {
    id 'com.android.library'
    id 'org.jetbrains.kotlin.android'
    id 'kotlin-kapt'
}

 

 

그리고 그래들 싱크하면 끝. 정말임. 이렇게만 하면 룸 쓸수 있는 환경이 끝남. maven의 장점이지 뭐

 

그럼 간단하게 함 써볼까?

 

난 이걸 지난번 data 모듈에 추가했어. view 쪽은 룸이 뭐가 알 필요도 없고, data에서만 관리하면 되는거지. 모듈의 사용이유지 뭐.

 

잘 되는지 한번 테스트 해보기 위해서 data 모듈에 DB 컬럼에 대응되는 data class를 하나 만들어

 

모델이라는 패키지를 하나 만들고 그 안에 아래처럼 data class를 추가했어

@Entity(tableName = INV_ITEM_TABLE_NAME)
data class InvItem(
    @ColumnInfo(name = "item_name") val itemName: String,
    @ColumnInfo(name = "item_original_id") val itemOriginalId: String?,
    @ColumnInfo(name = "price") val price: Int,
    @ColumnInfo(name = "buy_date") @TypeConverters(DateConverter::class) val buyDate: Date?,
    @ColumnInfo(name = "sell_date") @TypeConverters(DateConverter::class) val sellDate: Date?
){
    @PrimaryKey(autoGenerate = true)
    var id: Long? = null
}

그리고 저기서 DataConverter는... 내가 쓸꺼는 간단하게 그냥 Date로 하고 DB는 스트링으로 한다고 했을때, 그 타입 차이를 어떻게 해결 해 줄지 쓰는 부분이야. 그래서 아래처럼 작성했지.

즉 코드는 그냥 Date로 쓸꺼고, DB에는 스트링으로 저장되겠지

const val DATE_FORMAT = "yyyy-MM-dd"

class DateConverter {
    var df: DateFormat = SimpleDateFormat(DATE_FORMAT)

    @TypeConverter
    fun fromTimestamp(value: String?): Date? {
        return if (value != null) {
            try {
                return df.parse(value)
            } catch (e: ParseException) {
                e.printStackTrace()
            }
            null
        } else {
            null
        }
    }

    @TypeConverter
    fun dateToTimestamp(value: Date?): String? {
        return if (value == null) null else df.format(value)
    }
}

 

그리고 Dao를 통해서 미리 어떻게 가져올지 쿼리문등을 정의해야해.

근데 room을 쓰니깐 모든 컬럼 다 쓰고 귀찮은 그런 일은 빌드할때 알아서 해주니깐, 아래처럼 간단히 입렵하면 됨.

 

@Dao
interface InvItemDao {
    @Query("SELECT * FROM $INV_ITEM_TABLE_NAME")
    fun getAll(): List<InvItem>

    @Insert
    fun insertAll(vararg invItems: InvItem)

    @Delete
    fun delete(item: InvItem)
}

 

그럼 이제 저 InvItem 모양 그대로 DB를 만들어야겠지?

const val DATABASE_NAME = "FinHelperDb"

@Database(entities = [InvItem::class], version = 1)
@TypeConverters(DateConverter::class)
abstract class AppDatabase : RoomDatabase() {

    abstract fun invItemDao(): InvItemDao

    fun getDb(context: Context): RoomDatabase {
        return Room.databaseBuilder(
            context,
            AppDatabase::class.java, DATABASE_NAME
        ).build()
    }
}

저 내용을 해석하면, 컬럼이름은 InvItem따라가소,  version은 1이고, 가져오고 쓰고 하는건 invItemDao에 있다라는 뜻.

 

테스트 하기 위해서 Repository 하나 만들고 DB 생성 코드를 아래처럼 작성함.

class Repository(context: Context) {
    private val db = Room.databaseBuilder(context.applicationContext,
        AppDatabase::class.java, DATABASE_NAME).build()

    fun getAllData(): List<InvItem> {
        return db.invItemDao().getAll()
    }

    fun insertData(invItem: InvItem) {
        db.invItemDao().insertAll(invItem)
    }
}

 

근데 실행하면 아래 처럼 메인 스레드에서 DB 읽는다고 모라함.

java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time.

 

일단 테스트니깐, 룸한테 메인 스레드에서도 문제 없이 해달라고 추가하고.. allowMainThreadQueries

class Repository(context: Context) {
    private val db = Room.databaseBuilder(context.applicationContext,
        AppDatabase::class.java, DATABASE_NAME).allowMainThreadQueries().build()

    fun getAllData(): List<InvItem> {
        return db.invItemDao().getAll()
    }

    fun insertData(invItem: InvItem) {
        db.invItemDao().insertAll(invItem)
    }
}

 

테스트용 액티비티에 저 Repository를 가져오게 하고, get은 몇개인지 textview에 쓰고, set은 테스트용 아이템을 DB에 저장하게 설정!

class TestActivity : AppCompatActivity() {
    lateinit var repository: Repository
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_test)
        repository = Repository(this)

        val textView = findViewById<TextView>(R.id.textView)
        val getButton = findViewById<Button>(R.id.button1)
        getButton.setOnClickListener{
            val items = repository.getAllData()
            textView.text = items.size.toString()
        }
        val setButton = findViewById<Button>(R.id.button2)
        setButton.setOnClickListener{
            val invItem = InvItem("test1", null, 55, Date(235235L), null)
            repository.insertData(invItem)
        }
    }
}

 

그리고 실행하면, set 누른 개수만큼 아이템이 들어서, 겟 하면 몇개 인지 쓰는 코드가 나오게 됨요 헤헤

 

참고로 파일 추가한 package 구조는 아래처럼 함.

설계할때 의존성 생각하고 해야하니깐.. 요런 부분이 중요하지용

 

반응형

댓글