본문 바로가기
안드로이드 코틀린

[Kotlin][Android] Room 으로 DB 저장하기

by teamnova 2021. 8. 1.
728x90

 

서버-클라이언트 구조에서 대규모 데이터베이스 관리를 위한 시스템인 MySQL, MariaDB, Oracle 등 이 있다면

 

로컬에서 사용하는 경량 데이터베이스가 있는데요 대표적으로 SQLite가 있습니다.

 

안드로이드에서 사용하는 로컬 데이터베이스가 바로 SQLite 입니다. 

 

공식문서를 보면 다음과 같은 주의 사항이 있습니다. 

 

https://developer.android.com/training/data-storage/sqlite?hl=ko

 

SQLite를 사용하여 데이터 저장  |  Android 개발자  |  Android Developers

데이터베이스에 데이터를 저장하는 작업은 연락처 정보와 같이 반복적이거나 구조화된 데이터에 이상적입니다. 이 페이지에서는 개발자가 일반적으로 SQL 데이터베이스를 잘 알고 있다고 가정

developer.android.com

안드로이드 측에서는 Room의 사용을 적극적으로 권장하고 있습니다. 

 

그러면 Room은 뭐지?

 

정식 명칭은 Room Persistence Library

 

Room은 SQLite에 대한 추상화 레이어를 제공하여 원활한 데이터베이스 액세스를 지원하는 동시에 SQLite를 완벽히 활용합니다.

 

Room의 구성요소를 알아보고 바로 만들어 보겠습니다.

 

 

 

- Database (데이터베이스)

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

 

- Entity (항목)

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

 

- DAO (다오)

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

 

 

 

먼저 gradle에 Room을 추가해 줍니다.

 

app/build.gradle

dependencies {
  def room_version = "2.2.5"

  implementation "androidx.room:room-runtime:$room_version"
  annotationProcessor "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"
}

 

 

 

Entity 만들기

 

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

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

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

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

 

Contacts.kt

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
)

 

 

DAO 만들기

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

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

 

 

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)
}

 

 

AppDatabase

 

Room을 실질적으로 구현하는 부분입니다. 우선 RoomDatabase를 상속받습니다.

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

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

 

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
        }
    }
}

 

activity_layout.xml

<?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>

 

item_contacts.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:id="@+id/mRootView"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="20dp">

    <TextView
        android:id="@+id/mName"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/mTel"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</LinearLayout>

 

 

 

리싸이클러뷰를 정의해 줍니다. 

 

ContactsListAdapter.kt

package com.example.mylistapplication

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView

class ContactsListAdapter(private val itemList : List<Contacts>) : RecyclerView.Adapter<ContactsViewHolder>()  {

    override fun getItemCount(): Int {
        return itemList.size
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ContactsViewHolder {
        val inflatedView = LayoutInflater.from(parent.context).inflate(R.layout.item_contacts, parent, false)
        return ContactsViewHolder(inflatedView)
    }

    override fun onBindViewHolder(holder: ContactsViewHolder, position: Int) {
        val item = itemList[position]

        holder.itemView.setOnClickListener {
            itemClickListener.onClick(it, position)
        }
        holder.apply {
            bind(item)
        }
    }

    //ClickListener
    interface OnItemClickListener {
        fun onClick(v: View, position: Int)
    }
    private lateinit var itemClickListener : OnItemClickListener

    fun setItemClickListener(itemClickListener: OnItemClickListener) {
        this.itemClickListener = itemClickListener
    }

}

 

ContactsViewHolder.kt

package com.example.mylistapplication

import android.view.View
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.item_contacts.view.*

class ContactsViewHolder(v: View) : RecyclerView.ViewHolder(v){
    var view : View = v

    fun bind(item: Contacts) {
        view.mName.text = item.name
        view.mTel.text = item.tel
    }
}

 

 

MainActivity.kt

package com.example.mylistapplication

import android.app.Activity
import android.os.Bundle
import android.util.Log
import android.view.View
import kotlinx.android.synthetic.main.activity_layout.*
import java.util.*

class MainActivity : Activity() {

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

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_layout)

        //초기화
        db = AppDatabase.getInstance(this)

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

        //어댑터, 아이템 클릭 : 아이템 삭제
        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


        //플러스 버튼 클릭 : 데이터 추가
        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() //리스트뷰 갱신
        }

    }
}

 

 

 

'Stickode'에서 업로드한 동영상

 

stickode.tistory.com