목표
이 포스트는 총 5편으로 이루어져 있습니다. 밑줄 친 항목이 이번 포스트에서 다룰 항목 입니다.
RoomDatabase 개념
- Roomdatabase의 기본 요소인 Entity, Database, Dao 개념/구현 (1편)
- LiveData, ViewModel, Repository를 이용하여 MVVM 아키텍처 구성(1편)
Insert 구성
- Navigation Graph , NavHostFragment추가(2편)
- 각 list, update, add fragment의 layout 및 프래그먼트 구현(2편)
- Add Fragment를 여는 플로팅 버튼 추가 (2편)
- DB browser를 통해 database 확인(2편)
- RecyclerView에 들어갈 ItemLayout과 ListAdapter 추가(3편)
- RecyclerView와 LiveData 적용하기(3편)
- 위와 같이 Room database에 저장된 값을 Recyclerview와 연결하기 (3편)
Update 구성
- Update layout 생성(4편)
- Update Layout Navigation Graph에 연결(4편)
- Navigation Argument 추가하기(4편)
- 데이터베이스에 수정된 값 업데이트(4편)
Delete 구성
- 제거 버튼 Action bar에 추가(5편)
- 선택한 데이터 삭제(5편)
- 모든 데이터 삭제(5편)
Room Database 개념
룸 데이터베이스는 sqlite를 안드로이드에서 쉽게 매핑할 수 있게 만들어준 library입니다. 룸 사용 시 3가지 장점이 있습니다.
1. compile-time에 실행되기 때문에 데이터를 불러오다가 크래시 되는 것을 막아줍니다.
2. 코드가 간결해집니다.
3. 다른 architect component와 잘 사용됩니다.
Room Database의 요소
1. Entity
엔티티는 데이터 베이스의 테이블입니다. 우리는 roomdatabase에서 데이터를 저장할 때, 엑셀처럼 테이블 형식으로 저장하게 됩니다. 우리가 테이블에 데이터들을 기록할 때 아래 예제처럼, 각 열에는 속성을 쓰고, 가로에는 데이터가 가지고 있는 각 속성 값을 기록하게 됩니다. 엔티티가 테이블을 나타낸다면, 테이블의 열(column)들은 어떻게 만들까요? 엔티티의 class는 열(column)을 나타냅니다. 아래 예제 테이블에서는 이름과 나이가 entity의 클래스가 됩니다. 그리고 행(row)에 있는 데이터들은 고유의 Primary key를 가지게 됩니다. 이 primary key는 우리가 정합니다.
primary Key | 이름 | 나이 |
1 | a | 12 |
2 | b | 20 |
3 | c | 12 |
2. DAO
DAO에서는 위에 작성한 데이터 베이스 테이블을 접근하기 위해 함수를 만드는 인터페이스입니다. 예를 들면 나이 12를 가진 데이터 모음을 하면 1과 3을 불러올 수 있게 됩니다. 사용하는 방법은 아래 예제에서 다루겠습니다.
3. Database
database는 데이터베이스에 접근할 수 있게 main access point를 제공합니다.
Room Database 예제
Dependency 추가하기
Build.app
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
//추가 시작
apply plugin: "org.jetbrains.kotlin.kapt" // kapt를 사용하기때문에 kapt plugin을 추가해줘야합니다.
apply plugin: "androidx.navigation.safeargs.kotlin"
android {
...
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
...
// Navigation Component
implementation 'androidx.navigation:navigation-fragment-ktx:2.2.2'
implementation 'androidx.navigation:navigation-ui-ktx:2.2.2'
// Room components
implementation "androidx.room:room-runtime:2.2.5"
kapt "androidx.room:room-compiler:2.2.5"
implementation "androidx.room:room-ktx:2.2.5"
androidTestImplementation "androidx.room:room-testing:2.2.5"
// Lifecycle components
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
implementation "androidx.lifecycle:lifecycle-common-java8:2.2.0"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
// Kotlin components
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.72"
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.5"
api "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.5"
}
//추가끝
build.project
buildscript {
ext.kotlin_version = "1.5.20"
repositories {
google()
mavenCentral()
}
dependencies {
classpath "com.android.tools.build:gradle:4.2.1"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
//추가시작
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.3.0-rc01"
//추가끝
}
}
위에 코드를 모두 추가해주셨다면 sync를 시작해주세요
Entity 추가
Entity는 data class에서 만들어집니다. 우리는 user라는 entity를 만들 겁니다. 그리고 이 entity는 데이터베이스 테이블이 됩니다.
먼저 파일들을 만들어주겠습니다.
새로운 데이터 패키지 폴더를 생성후 그 아래에
User 클래스
UserDao 인터페이스
UserDatabase 클래스
UserRepository 클래스
UserViewModel 클래스를 만들어줍니다.
Entity annotationd를 User 데이터 클래스에 추가 해줘야합니다.
- data/User.kt
@Entity(tableName = "user_table") //데이터를 불러올때 테이블의 이름을 정합니다.
data class User(
@PrimaryKey(autoGenerate = true) //primary key는 자동으로 만들게 합니다.
val id:Int,
val firstName:String,
val lastName:String,
val age:Int
) {
}
- @Entity annotation을 붙여주었기때문에, Room database가 이 dataclass가 Entity라는 걸 인식 하게됩니다.
- 그리고 위에서 언급한것처럼 Entity는 테이블입니다.
- 테이블이름을 user_table로 지정해주었습니다.
- User 클래스에 id, firstName, lastName, age를 열(column)로 만들어지게됩니다.
- 그리고 PrimaryKey annotation을 추가해줬기때문에 자동으로 데이터마다 PrimaryKey가 생성됩니다.
DAO 추가
- data/UserDao
@Dao
interface UserDao {
@Insert(onConflict = OnConflictStrategy.IGNORE)//같은 데이터가 있으면 무시
suspend fun addUser(user:User) //suspend:코루틴사용 해서 user추가
@Query("SELECT * FROM user_table ORDER BY id ASC")
fun readAllData(): LiveData<List<User>>
}
위에서 말했듯이 데이터베이스를 접근할 때 DAO를 쓰게 됩니다.
@Insert annotation을 보면 ( ) 안에의미는 같은 데이터를 추가하려할때 그 데이터를 추가하지않고 무시한다는 함수입니다. OnConflictStrategy.REPLACE를 쓰면 덮어씌우기가 됩니다.
@Query 에서 SELECT * FROM user_table(엔티티에서 만들어준 테이블 이름) ORDER BY id ASC (ASC는 Accending order 오름차순을 의미합니다)
위에 Query로 불러올때 LiveData로 불러오는걸 볼수있습니다.
Database 추가
database는 데이터 테이블을 접근하고 있을 때 메인 엑서스 포인트입니다. 이곳을 통해 디바이스에 있는 데이터를 접근과 수정, 삭제를 하게 됩니다.
- data/UserDatabase
@Database(entities = [User::class], version = 1, exportSchema = false)
abstract class UserDatabase: RoomDatabase() {
abstract fun userDao(): UserDao
companion object{
@Volatile //다른 thread에서 접근 가능하게 만드는 것입니다.
private var INSTANCE: UserDatabase? = null
fun getDatabase(context: Context):UserDatabase{
val tempInstance = INSTANCE
if(tempInstance != null){
return tempInstance
}
synchronized(this){ //synchronized는 새로운 데이터베이스를 instance시킵니다.
val instance = Room.databaseBuilder(
context.applicationContext,
UserDatabase::class.java,
"user_database"
).build()
INSTANCE = instance
return instance
}
}
}
}
Repository 연결해주기
repository는 깔끔한 코드 구성과 MVVM아키텍처를 위해서 추가하는 걸 권장합니다. repository의 역할은 데이터들을 접근하는 코드들을 모아 둘 때 유용합니다.
repository에 user를 db에 추가하는 코드를 만들어보겠습니다.
- data/UserRepository
class UserRepository(private val userDao: UserDao) {
val readAllData: LiveData<List<User>> = userDao.readAllData()
suspend fun addUser(user:User){ //suspend를 붙여준 이유는 coroutine을 사용하기 위함입니다.
userDao.addUser(user) //DAO에서 만들었던 adduser을 실행합니다.
}
}
DAO에서 데이터를 불러올때 LiveData로 불러오기때문에 val readAllData의 형식이 LiveData<List<User>>로 일치시킨걸 볼수있습니다.
suspend는 코 루틴을 사용하실 때 쓰는 용도입니다. coroutine을 모르신다면 아래 포스트를 확인해주세요.
ViewModel 만들기
사용자가 ui사용 중 데이터들이 바뀔 때 바로 업데이트가 가능하게 모든 라이프사이클에 살아있게 하는 ui데이터 저장소입니다. 뷰 모델을 모르신다면 아래 포스트를 확인해주세요.
repository에서 라이브 데이터를 관리하고 있기 때문에 repository에 있는 livedata에 새로운 항목이 추가될 때마다 ui에 새로운 데이터를 전달하도록 만들겠습니다.
- data/UserViewModel
class UserViewModel(application: Application): AndroidViewModel(application) {
private val readAllData:LiveData<List<User>>
private val repository:UserRepository
init {
val userDao = UserDatabase.getDatabase(application).userDao()
repository = UserRepository(userDao) //이니셜라이즈 해줍니다.
readAllData = repository.readAllData // readAlldata는 repository에서 만들어줬던 livedata입니다.
}
fun addUser(user:User){// 파라미터에 만든 데이터클래스가 들어갑니다.
viewModelScope.launch(Dispatchers.IO) { //코루틴 활성화 dispatcherIO는 백그라운드에서 실행
repository.addUser(user) //repository에 adduser함수 불러오기
}
}
}
다음 편에 이어서 만들어보겠습니다.