스터디/Android+Kotlin

안드로이드 코틀린 ROOM (룸, 데이터베이스) 사용해서 RecyclerView 적용하기.

Dalmangyi 2020. 7. 14.

안녕하세요

이번에는 안드로이드에서 코틀린으로 데이터베이스를 쉽게 쓰는 방법인 ROOM에 대해 소개해보고자 합니다

 

 

기존에는 안드로이드에서 SQLite를 사용하여

데이터베이스의 파일도 관리해야하고, 마이그레이션도 직접해야하고 

일일히 get, set도 만들고 여간 귀찮은게 아니였습니다.

 

그런 불편함을 해소해주기 위해 나온 ROOM (룸)

SQLite를 안쓰는게 아닌, SQLite를 맵핑 해주는 라이브러리로 나오게 되었습니다.

 

자세한건 코드와 함께 소개해보고자 합니다.

 

 


1. ROOM 의 3가지 개념

Database (데이터베이스)

    저장하는 데이터의 집합 단위를 말합니다

 

Entity (항목)

    데이터베이스 내의 테이블을 의미합니다

 

DAO (다오)

    데이터베이스에 접근하는 함수(insert,update,delete,...)를 제공합니다

 

 

 


이전 게시글에 있는 친구 이름과 전화번호를 가지고 있는 전화번호부 프로그램을 ROOM을 이용해서 만들어 볼까 합니다

 

 

 

 

2. Gradle에 ROOM 추가

먼저 dependencies를 추가해줍니다.

dependencies {
  def room_version = "2.2.5"

  implementation "androidx.room:room-runtime:$room_version"
  kapt "androidx.room:room-compiler:$room_version" // For Kotlin use kapt instead of annotationProcessor

  // optional - Kotlin Extensions and Coroutines support for Room
  implementation "androidx.room:room-ktx:$room_version"

  // optional - RxJava support for Room
  implementation "androidx.room:room-rxjava2:$room_version"

  // optional - Guava support for Room, including Optional and ListenableFuture
  implementation "androidx.room:room-guava:$room_version"

  // Test helpers
  testImplementation "androidx.room:room-testing:$room_version"
}

app/build.gradle

 

 

 

 

 

 

 


 

 

 

 

 

3. Entity 만들기

Contacts.kt를 Room에서 사용할 테이블인 Entity로 변경해 보겠습니다

@Entity 어노테이션을 class위에 추가합니다

Entity의 테이블 이름을 tb_contacts로 지정해 줍니다.

@PrimaryKey 어노테이션과 autoGenerate=true를 이용해서 Contacts가 새로 생길때마다 id를 자동으로 올라가게 만들어줍니다.

이외에도 이름과 전화번호를 생성자 파라매터로 넣어줍니다.

package com.example.mylistapplication

import androidx.room.*

@Entity(tableName = "tb_contacts")
data class Contacts(
    @PrimaryKey(autoGenerate = true) val id:Long,
    var name: String,
    var tel: String
)

Contacts.kt

 

 

 

 

 

 

 

 


 

 

 

 

 

 

 

4. DAO 만들기

Data Access Objects(DAO)

테이블인 Entity Contacts를 쿼리로 접근하는 인터페이스를 만들어주는 부분입니다

간단히 어노테이션으로만 인서트, 딜리트, 쿼리를 할 수 있습니다

 

@Dao를 interface위에 선언해줍니다

데이터를 전부 가져올 getAll 함수를 만들고, @Query 쿼리 어노테이션을 선언해줍니다

데이터를 넣을땐 @Insert 인서트 어노테이션을 선언해줍니다. 데이터가 여러개가 필요할 수 있기때문에 가변인자 vararg를 사용합니다

삭제할땐 @Delete 딜리트 어노테이션을 추가해줍니다

package com.example.mylistapplication

import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query

@Dao
interface ContactsDao {
    @Query("SELECT * FROM tb_contacts")
    fun getAll(): List<Contacts>

    @Insert
    fun insertAll(vararg contacts: Contacts)

    @Delete
    fun delete(contacts: Contacts)
}

ContactsDao.kt

 

 


 

 

 

5. AppDatabase

Room을 실질적으로 구현하는 부분입니다. 

우선 RoomDatabase를 상속받습니다.

@Database 어노테이션도 추가해줍니다. Contacts를 사용하며, 버전은 1, 스키마를 추출하지 않음으로 해줍니다.

버전은 추후에 Contacts를 변경할때 migration 할 수 있는 기준이 됩니다. 처음 변경된 적이 없는 처음이니 1 로 해줍니다.

미리 만들어놓은 ContactsDao를 접근할 수 있도록 abstract fun을 이용해서 contactsDao()를 만들어 줍니다.

어디서든 접근가능하고 중복 생성되지 않게 싱글톤으로 companion object를 이용해서 만들어 줍니다.

데이터베이스 이름은 database-contacts 입니다.

그리고 지금은 UI Thread (MainThread)에서 접근 할 수 있도록 allowMainThreadQueries로 만들어주겠습니다.

package com.example.mylistapplication

import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase

@Database(entities = [Contacts::class], version = 1, exportSchema = false)
abstract class AppDatabase : RoomDatabase() {
    abstract fun contactsDao(): ContactsDao

    companion object {
        private var instance: AppDatabase? = null

        @Synchronized
        fun getInstance(context: Context): AppDatabase? {
            if (instance == null) {
                instance = Room.databaseBuilder(
                    context.applicationContext,
                    AppDatabase::class.java,
                    "database-contacts"
                )
                .allowMainThreadQueries()
                .build()
            }
            return instance
        }
    }
}

AppDatabase.kt

 

 

 

 

 

 


 

 

 

 

6. Contact 추가 버튼

기존 list_activity.xml 화면의 가운데 아래에 Button을 추가해줍니다

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <androidx.recyclerview.widget.RecyclerView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/mRecyclerView"
        app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Plus"
        android:id="@+id/mPlusButton"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:layout_margin="10dp"/>

</RelativeLayout>

list_activity.xml

 

 

 

 

 


 

 

 

7.ListActivity

우선 상단에 db를 싱글턴으로 초기화 해주고,

리스트를 가변적일 수 있게 mutableListOf로 만들어줍니다

val TAG = "ListActivity"
var db : AppDatabase? = null
var contactsList = mutableListOf<Contacts>()

ListActivity.kt

 

 

DB를 초기화 하고

Dao를 이용해서 쉽게 이전 데이터를 불러오고

 //초기화
db = AppDatabase.getInstance(this)
        
//이전에 저장한 내용 모두 불러와서 추가하기
val savedContacts = db!!.contactsDao().getAll()
if(savedContacts.isNotEmpty()){
	contactsList.addAll(savedContacts)
}

ListActivity.kt

 

 

어댑터를 만들어서 리사이클러뷰에 세팅해줍니다

어댑터의 클릭에는 Dao에 선언해둔 delete함수를 이용하여 DB에서 제거해줍니다

그리고 리스트에서도 지우고 리사이클러뷰를 갱신해줍니다.

//어댑터, 아이템 클릭 : 아이템 삭제
val adapter = ContactsListAdapter(contactsList)
adapter.setItemClickListener(object : ContactsListAdapter.OnItemClickListener{
  override fun onClick(v: View, position: Int) {

    val contacts = contactsList[position]

    db?.contactsDao()?.delete(contacts = contacts) //DB에서 삭제
    contactsList.removeAt(position) //리스트에서 삭제
    adapter.notifyDataSetChanged() //리스트뷰 갱신

    Log.d(TAG, "remove item($position). name:${contacts.name}")
  }
})
mRecyclerView.adapter = adapter

ListActivity.kt

 

 

버튼에 추가하는 기능을 구현해줍니다.

랜덤 숫자를 만들어서 Contact를 만들고

contactsDao에 미리 만들어놓은 insertAll함수를 이용해서 DB에 추가합니다

//플러스 버튼 클릭 : 데이터 추가
mPlusButton.setOnClickListener {

  //랜덤 번호 만들기
  val random = Random()
  val numA = random.nextInt(1000)
  val numB = random.nextInt(10000)
  val numC = random.nextInt(10000)
  val rndNumber = String.format("%03d-%04d-%04d",numA,numB,numC)

  val contact = Contacts(0, "New $numA", rndNumber) //Contacts 생성
  db?.contactsDao()?.insertAll(contact) //DB에 추가
  contactsList.add(contact) //리스트 추가

  adapter.notifyDataSetChanged() //리스트뷰 갱신
}

ListActivity.kt

 

 

 

 

 

 


 

 

 

 

 

 

완성된 모습

메인쓰레드를 이용해서 DB에 insert하고 delete하기 때문에

적절히 느리게 해야 합니다..

그래야 화면출력을 하면서 DB반영이 되기 때문입니다 ㅎㅎ.. 

간단하게 보이기 위해 정말 간단히 코딩해버렸네요.

 

 

 

 

 

 

 

 

 

마치며

ROOM은 정말 쉽게 SQLite를 사용하게 해줘서 정말 감격이였습니다...

항상 다른 프로젝트마다 매번 같은걸 코딩하고 있으면 라이브러리로 만들어서 추가하곤 했지만

제가 부족했던 부분들을 다양하고 꼼꼼하게 체계화 시켜놓은 점이 정말 마음에 듭니다 ㅎㅎ

 

 

 

 

 

다음 게시글에선 좀 더 스마트한 방법이 없나 찾아보겠습니다.

감사합니다~

 

 

 

 

아참~ 소스! 올립니다

MyListApplication.zip
0.74MB

 

 

 

 

 

 

 

 

 

 

 

 

 

 

댓글