[Android] 8(3). Room: ORM 라이브러리

10 minute read


Room: ORM 라이브러리

ORMObject-Relational Mapping 은 객체(Class)와 관계형 데이터베이스의 데이터(Table)을 매핑하고 변환하는 기술로 복잡한 쿼리를 잘 몰라도 코드만으로 데이터베이스의 모든 것을 컨트롤할 수 있도록 도와줍니다.

간단한 예로 다음 그림과 같이 코드의 클래스 파일에 ORM을 적용하면 자동으로 쿼리로 변환해서 테이블을 생성해줍니다.

image-20210820180649115

안드로이드는 SQLite를 코드 관점에서 접근할 수 있도록 ORM 라이브러리인 Room을 제공합니다.

Room 라이브러리를 사용해 프로젝트를 진행해보겠습니다. 앞선 포스팅에서 작성했던 SQLite 프로젝트에서 몇 개의 화면과 액티비티는 복사해서 재사용하겠습니다.


Room 추가하기


1. build.gradle 파일 플러그인 추가

plugins {
    id 'com.android.application'
    id 'kotlin-android'
    // 1-1. Room 추가하기: kotlin-kapt 사용 명시
    id 'kotlin-kapt'
}

kapt란?

자바 6부터 도입된 Pluggable Annotation Processing API (JSR 269)를 Kotlin에서도 사용 가능하게 하는 것입니다. [안드로이드 공식 문서]

여기서 어노테이션 프로세싱이란 우리가 간단하게 ‘@명령어’처럼 사용하는 주석 형태의 문자열을 식제 코드로 생성해주는 것입니다. @로 시작하는 명령어를 어노테이션이라고 하는데, 어노테이션이 컴파일 시에 코드로 생성되기 때문에 실행 시에 발생할 수 있는 성능 문제가 많이 개선됩니다.

Room을 사용하면 클래스명이나 변수명 위에 @어노테이션을 사용해서 코드로 변환할 수 있습니다.


2. build.gradle 파일 의존성 추가

dependencies {
    // 1-2. Room 추가하기: Room 의존성 추가
    def room_version = "2.3.0"
    implementation "androidx.room:room-runtime:$room_version"
    kapt "androidx.room:room-compiler:$room_version"
    implementation "androidx.room:room-ktx:$room_version"
    ...
}

Room 버전

아래 주소에서 Room의 최신 버전을 확인하고 최신 버전으로 설정하세요.

https://developer.android.com/jetpack/androidx/releases/room



RoomMemo 클래스 정의히기


1. SQLite 프로젝트에서 파일 가져오기

SQLite 프로젝트에서 작성했던 MainActivity.kt, RecyclerAdapter.kt, activity_main.xml, item_recycler.xml 을 Room 프로젝트에 추가합니다. 추가 후에는 패키지 명 등을 room으로 바꿔줍니다.

xml 파일은 그대로 사용하고, kt 소스파일은 뒤에서 약간의 수정을 할 것입니다.


2. RoomMemo 클래스 생성

RoomMemo 클래스를 생성합니다. 이 RoomMemo 클래스가 하나의 테이블(table)이 될 것입니다.

Room 라이브러리는 @Entity 어노테이션이 적용된 클래스를 찾아 테이블로 변환합니다. 데이터베이스에서 테이블명을 클래스명과 다르게 하고 싶을 때는 @Entity(tableName = "테이블명")과 같이 작성하면 됩니다.

// 2-2. RoomMemo 클래스 생성하기: 클래스 생성하고 @Entity 선언
@Entity(tableName = "room_memo")
class RoomMemo {
}


3. 테이블의 컬럼 작성

앞에서 @Entity가 붙은 RoomMemo 클래스는 하나의 테이블을 나타낸다고 했습니다. 테이블을 생성했으므로 컬럼을 지정합니다.

컬럼을 지정할 때는 변수명 위에 @ColumnInfo 어노테이션을 작성해서 테이블의 컬럼으로 사용된다는 것을 명시합니다. 컬럼명도 테이블명처럼 변수명과 다르게 하고 싶을 때는 @ColumnInfo(name = "컬럼명")과 같이 작성하면 됩니다.

@Entity(tableName = "room_memo")
class RoomMemo {
    // 2-3. RoomMemo 클래스 생성하기: @ColumnInfo 으로 테이블 컬럼 명시
    // 변수명과 다르게 하고 싶으면 @ColumnInfo(name="컬럼명")으로 작성
    @ColumnInfo
    @PrimaryKey(autoGenerate = true) // 키 명시, 자동 증가 옵션
    var no: Long? = null
    @ColumnInfo
    var content: String = ""
    @ColumnInfo
    var datetime: Long = 0
}

no 변수에는 @PrimaryKey 어노테이션을 사용해서 키(Key)라는 것을 명시하고 자동 증가 옵션을 추가합니다.

변수를 테이블의 컬럼으로 사용하고 싶지 않을 때

@Ignore 어노테이션을 적용하면 해당 변수가 테이블과 관계없는 변수라는 정보를 알릴 수 있습니다.

@Ignore
var temp: String = "임시로 사용되는 데이터입니다."


4. 생성자 작성하기

content와 datetime을 받는 생성자를 작성합니다.

@Entity(tableName = "room_memo")
class RoomMemo {
  
    @ColumnInfo
    @PrimaryKey(autoGenerate = true) 
    var no: Long? = null
    @ColumnInfo
    var content: String = ""
    @ColumnInfo
    var datetime: Long = 0

    // 2-4. RoomMemo 클래스 생성하기: 생성자 작성
    constructor(content: String, datetime: Long){
        this.content = content
        this.datetime = datetime
    }
}



RoomMemoDAO 인터페이스 정의하기


Room은 데이터베이스에 읽고 쓰는 메서드를 인터페이스 형태로 설계하고 사용합니다. 코드 없이 이름만 명시하는 형태로 인터페이스를 만들면 Room이 나머지 코드를 자동 생성합니다.

DAO란?

Data Access Object 의 약어로 데이터베이스에 접근해서 DML 쿼리(SELECT, INSERT, UPDATE, DELETE)를 실행하는 메서드의 모음입니다.


1. RoomMemoDAO 인터페이스 생성

RoomMemoDAO 인터페이스를 생성합니다. 이 RoomMemoDAO 인터페이스는 DML 쿼리에 관련한 메서드를 선언(설계)하는 곳입니다.

인터페이스 위에 @Dao 어노테이션을 작성하여 DAO라는 것을 명시할 수 있습니다.

// 3-1. RoomMemoDAO 인터페이스 정의하기: 인터페이스 생성하고 @DAO 선언
@Dao
interface RoomMemoDAO {
}


2. DML 쿼리 메서드 생성

삽입, 조회, 수정, 삭제에 해당하는 3개의 메서드를 만들고 각각의 어노테이션을 붙여줍니다.

@Dao
interface RoomMemoDAO {
    // 다른 ORM 툴과는 다르게 Room 라이브러리는 조회를 하는 select 쿼리는 직접 작성하도록 설계되어 있습니다. 
    @Query("select * from room_memo")
    fun getAll(): List<RoomMemo>
    // REPLACE를 import할 때는 androidx.room 패키지로 시작하는 것을 고릅니다. 
    @Insert(onConflict = REPLACE)
    fun insert(memo: RoomMemo)

    @Delete
    fun delete(memo: RoomMemo)
}

두 번째 @Insert 어노테이션의 경우 옵션으로 onConflict = REPLACE를 적용하면 동일한 키를 가진 값이 입력되었을 때 UPDATE 쿼리로 실행됩니다.

어노테이션의 종류

어노테이션 위치 옵션 설명
@Database 클래스 entities, version 데이터베이스
@Entity 클래스 (tableName = “테이블명”) 테이블
@ColumnInfo 멤버 변수 (name = “컬럼명”) 컬럼
@PrimaryKey 멤버 변수 (autoGenerate = true) 컬럼 옵션
@Dao 인터페이스   실행 메서드 인터페이스
@Query 멤버 메서드 (“쿼리”) 쿼리를 직접 작성하고 실행
@Insert 멤버 메서드 (onConflict = REPLACE) 중복 시 수정
@Delete 멤버 메서드   삭제



RoomHelper 클래스 정의하기


SQLiteOpenHelper를 상속받아서 구현했던 것처럼 Room도 RoomDatabase를 상속받아 클래스를 생성합니다.

주의할 점은 추상 클래스로 생성해야 한다는 점입니다.


1. RoomHelper 클래스 생성

RoomHelper 추상 클래스를 생성하고 클래스명 위에 @Database 어노테이션을 작성합니다. RoomHelper 클래스는 RoomMemoDAO 인터페이스와 데이터베이스를 연결하는 역할을 합니다.

// 4-1. RoomHelper 클래스 정의하기: RoomDatabase를 상속하는 추상 클래스 생성, @Database 선언
@Database(entities = arrayOf(RoomMemo::class), version = 1, exportSchema = false)
abstract class RoomHelper: RoomDatabase() {
}

@Database 어노테이션 속성

옵션 설명
entities Room 라이브러리가 사용할 엔터티(테이블) 클래스 목록
version 데이터베이스의 버전
exportSchema true면 스키마 정보를 파일로 출력


2. RoomMemoDAO 인터페이스의 구현체를 사용할 수 있는 메서드명 정의

RoomHelper 클래스 안에 앞에서 정의한 RoomMemoDAO 인터페이스의 구현체를 사용할 수 있는 메서드명을 정의합니다.

@Database(entities = arrayOf(RoomMemo::class), version = 1, exportSchema = false)
abstract class RoomHelper: RoomDatabase() {
    // 4-2. RoomHelper 클래스 정의하기: RoomMemoDAO 인터페이스의 구현체를 사용할 수 있는 메서드명 정의
    abstract fun roomMemoDAO(): RoomMemoDAO
}

이로써 RoomHelper 인스턴스는 roomMemoDAO( ) 메서드를 호출함으로써 RoomMemoDAO 인터페이스 내에 선언된 DML 쿼리 메서드들을 사용할 수 있습니다.



어댑터에서 사용하는 Memo 클래스를 RoomMemo 클래스로 변경하기


1. Memo 문자열을 RoomMemo 문자열로 수정

이제 사용할 데이터의 이름이 RoomMemo이므로 추가한 RecyclerAdapter.kt 파일에서 클래스명들을 수정합니다.

[Ctrl + F] 키를 누른 후 Memo 문자열을 모두 RoomMemo로 수정합니다. 대소문자를 구분하기 위해서 Aa라고 써있는 아이콘을 클릭해서 활성화한 후 [Replace all]을 눌러서 문자열을 모두 수정합니다.

[Replace all] 버튼이 보이지 않는 경우 [Ctrl + R] 키를 누릅니다.

image-20210820224430161


2. 코드 수정하기

Room 라이브러리를 이용함에 따라 두 부분의 코드를 수정합니다.

아래의 전체 코드에서 주석이 달린 부분이 수정된 부분입니다.

class RecyclerAdapter: RecyclerView.Adapter<RecyclerAdapter.Holder>() {
    // 5. RoomHelper를 사용하도록 변경
    // var helper: SqliteHelper? = null
    var helper: RoomHelper? = null

    var listData = mutableListOf<RoomMemo>()

    inner class Holder(val binding: ItemRecyclerBinding): RecyclerView.ViewHolder(binding.root){

        var mRoomMemo: RoomMemo? = null

        init{
            binding.btnDelete.setOnClickListener {
                // 5. Memo 클래스 참조를 RoomMemo 클래스 참조로 변경
                // helper?.deleteRoomMemo(mRoomMemo!!)
                helper?.roomMemoDAO()?.delete(mRoomMemo!!)
              
                listData.remove(mRoomMemo)
                notifyDataSetChanged()
            }
        }


        fun setRoomMemo(memo: RoomMemo){
            this.mRoomMemo = memo

            binding.textNo.text = "${memo.no}"
            binding.textContent.text = memo.content
            val sdf = SimpleDateFormat("yyyy/MM/dd hh:mm")
            binding.textDatetime.text = "${sdf.format(memo.datetime)}"
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
        val binding = ItemRecyclerBinding.inflate(
            LayoutInflater.from(parent.context), parent, false)

        return Holder(binding)
    }

    override fun onBindViewHolder(holder: Holder, position: Int) {
        val memo = listData.get(position)
        holder.setRoomMemo(memo)
    }

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

RoomHelper를 사용할 때는 여러 개의 DAO가 있을 수 있기 때문에 헬퍼.DAO( ).메서드( ) 형태로 가운데에 어떤 DAO를 쓸 것인지를 명시해야 합니다.



MainActivity에서 RoomHelper 사용하기


마찬가지로 MainActivity.kt 파일에서도 기존의 SpliteHelper를 RoomHelper로 수정합니다.

아래의 전체 코드에서 주석이 달린 부분이 수정된 부분입니다.

class MainActivity : AppCompatActivity() {

    val binding by lazy { ActivityMainBinding.inflate(layoutInflater)}

    // 6-1. SqliteHelper를 RoomHelper로 변경
    // var helper = SqliteHelper(this, "memo", 1)
    var helper: RoomHelper? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)

        // 6-2. RoomHelper 생성
        helper = Room.databaseBuilder(this, RoomHelper::class.java, "room_memo")
            .allowMainThreadQueries().build()

        val adapter = RecyclerAdapter()
        adapter.helper = helper

        // 6-3. RoomHelper를 사용하도록 수정
        // adapter.listData.addAll(helper.selectMemo())
        adapter.listData.addAll(helper?.roomMemoDAO()?.getAll()?:listOf())

        binding.recyclerMemo.adapter = adapter
        binding.recyclerMemo.layoutManager = LinearLayoutManager(this)

        // 6-3. RoomHelper를 사용하도록 수정
        binding.btnSave.setOnClickListener {
            if(binding.editMemo.text.toString().isNotEmpty()) {
                val memo = RoomMemo(binding.editMemo.text.toString(), System.currentTimeMillis())
                helper?.roomMemoDAO()?.insert(memo)
                adapter.listData.clear()
                adapter.listData.addAll(helper?.roomMemoDAO()?.getAll()?:listOf())
                // val memo = RoomMemo(null, binding.editMemo.text.toString(), System.currentTimeMillis())
                // helper.insertMemo(memo)
                // adapter.listData.clear()
                // adapter.listData.addAll(helper.selectMemo())
                adapter.notifyDataSetChanged()
                binding.editMemo.setText("")
            }
        }


    }
}

6-2 번의 helper를 생성하는 부분을 보겠습니다. databaseBuilder( ) 메서드의 세번째 파라미터가 실제 생성되는 DB 파일의 이름입니다. Room은 기본적으로 서브 스레드에서 동작하도록 설계되어 있기 때문에 allowMainThreadQueries( ) 옵션이 적용되지 않으면 앱이 동작을 멈춥니다.

✋ 실제 프로젝트에서는 allowMainThreadQueries 옵션을 사용하지 않기를 권장합니다. 여기서는 옵션을 빼고 작성하면 코드가 너무 복잡해지므로 이해를 돕고자 사용했습니다.


[추가] 메모 수정하기


수정 기능을 추가하기 위해 Room 프로젝트에서 4개의 파일에 수정을 가합니다.

  • activity_main.xml
  • RoomMemoDAO.kt
  • RecyclerAdapter.kt
  • MainActivity.kt


activity_main.xml

0. activity_main.xml에 수정취소 버튼 추가

먼저 수정을 하려고 할 때 사용할 버튼이 필요합니다.

수정 버튼은 기존에 있는 저장 버튼을 재사용하고, 새롭게 수정 취소 버튼을 만듭니다.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerMemo"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginBottom="8dp"
        app:layout_constraintBottom_toTopOf="@+id/editMemo"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <EditText
        android:id="@+id/editMemo"
        android:layout_width="0dp"
        android:layout_height="100dp"
        android:layout_marginStart="8dp"
        android:layout_marginLeft="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginRight="8dp"
        android:layout_marginBottom="8dp"
        android:ems="10"
        android:hint="메모를 입력하세요"
        android:inputType="textMultiLine"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/btnSave"
        app:layout_constraintStart_toStartOf="parent" />

    <Button
        android:id="@+id/btnSave"
        android:layout_width="wrap_content"
        android:layout_height="0dp"
        android:layout_marginEnd="8dp"
        android:layout_marginRight="8dp"
        android:layout_marginBottom="8dp"
        android:text="저장"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/btnCancel" />
		<!-- 수정 취소 버튼 추가 -->
    <Button
        android:id="@+id/btnCancel"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="8dp"
        android:layout_marginRight="8dp"
        android:text="수정취소"
        android:visibility="gone"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="@+id/editMemo" />
</androidx.constraintlayout.widget.ConstraintLayout>

visibility 속성을 ‘gone’으로 하여 평소에는 보이지 않다가 수정 작업을 할 때만 가시화되도록 합니다.

아래는 평소 화면과 수정 작업 시 화면입니다.

image-20210821143721550

이제 본격적으로 코드를 수정합니다.


RoomMemoDAO.kt

1. DAO 인터페이스에 수정 메서드 추가

RoomMemoDAO 인터페이스에 수정 메서드를 추가합니다.

@Dao
interface RoomMemoDAO {
    @Query("select * from room_memo")
    fun getAll(): List<RoomMemo>

    @Insert(onConflict = REPLACE)
    fun insert(memo: RoomMemo)

    @Delete
    fun delete(memo: RoomMemo)

    // 7-1. DAO 인터페이스에 수정 메서드 추가
    @Update
    fun update(memo: RoomMemo)
}


RecyclerAdapter.kt

수정을 하기 위해서는 어댑터가 액티비티에 있는 메서드를 호출해야 합니다. 수정 과정은 다음과 같습니다.

  1. 리사이클러 뷰 목록의 아이템 클릭
  2. 클릭 신호를 어댑터의 리스너에서 받아서 mainActivity의 setUpdate 메서드 호출
  3. 호출된 mainActivity의 setUpdate 메서드에서 데이터베이스와 리사이클러 뷰 목록 수정 메서드 호출
  4. 실제 수정 수행

즉 클릭 신호는 어댑터에서 받고, 수정 메서드 호출은 메인 액티비티에서 이루어지기 때문에 RecyclerAdapter 클래스에 MainActivity를 저장하는 프로퍼티가 있어야 합니다.


2. 수정을 위해 MainActivity 연결

class RecyclerAdapter: RecyclerView.Adapter<RecyclerAdapter.Holder>() {

    // 7-2. 메모 수정하기: 수정을 위해서 MainActivity 연결
    var mainActivity: MainActivity? = null
    ...
}


3. Holder 클래스의 클릭 리스너에서 setUpdate 메서드 호출

앞에서 사용자가 아이템을 클릭하면 그 클릭 시그널을 어댑터에서 받고, 어댑터에서 메인 액티비티의 setUpdate 메서드를 호출한다고 했습니다. 정확하게는 이 과정을 어댑터 내의 Holder 클래스 내에서 수행합니다.

Holder 클래스 내에 클릭 리스너를 만들고, 클릭 리스너 안에서 setUpdate 메서드를 호출합니다.

    inner class Holder(val binding: ItemRecyclerBinding): RecyclerView.ViewHolder(binding.root){

        var mRoomMemo: RoomMemo? = null

        init{
            binding.btnDelete.setOnClickListener {
                helper?.roomMemoDAO()?.delete(mRoomMemo!!)
                listData.remove(mRoomMemo)
                notifyDataSetChanged()
            }
            // 7-3. 메모 수정하기: 수정 기능 추가(메모 내용 쿨락 사 MainActivity의 setUpdate 메서드 호출)
            binding.textContent.setOnClickListener {
                mainActivity?.setUpdate(mRoomMemo!!)
            }
        }
        ...
    }


MainActivity.kt

메인 액티비티에서는 어댑터에서 자신의 메서드를 호출하면 실질적인 수정을 수행하도록 코드를 작성합니다.


4. 수정할 데이터를 임시 저장할 프로퍼티 선언

메인 액티비티의 프로퍼티로 수정 작업 시 임시로 저장할 RoomMemo 프로퍼티를 하나 생성합니다.

    // 7-4. 메모 수정하기: 수정할 데이터를 임시 저장할 프로퍼티 생성
    var updateMemo: RoomMemo? = null


5. 수정을 위해 어댑터에 메인 액티비티 연결

앞에서 어댑터에 액티비티를 저장할 프로퍼티를 선언했습니다. 그 프로퍼티에 메인 액티비티를 저장합니다.

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)

        helper = Room.databaseBuilder(this, RoomHelper::class.java, "room_memo")
            .allowMainThreadQueries().build()

        val adapter = RecyclerAdapter()
        adapter.helper = helper
        adapter.listData.addAll(helper?.roomMemoDAO()?.getAll()?:listOf())
        // 7-5. 메모 수정하기: 수정을 위해 어댑터에 메인액티비티 연결
        adapter.mainActivity = this


6. 버튼 리스너에 수정 체크 로직 추가

activity_main.xml 파일에 대해 설명할 때 수정 버튼은 기존에 있는 저장 버튼을 재사용한다고 했습니다.

따라서 기존 btnSave 버튼의 클릭 리스너에 수정 체크를 하는 로직을 추가합니다.

        binding.btnSave.setOnClickListener {
            // 7-6. 메모 수정하기: 수정 체크 추가
            if (updateMemo != null){
                updateMemo?.content = binding.editMemo.text.toString()
                helper?.roomMemoDAO()?.update(updateMemo!!)
                refreshAdapter(adapter)
                cancelUpdate() // 수정 완료 후 원상태로 복귀
            }
            // 수정이 아니고 플레인 텍스트에 입력된 내용이 있으면,
            else if(binding.editMemo.text.toString().isNotEmpty()) {
                val memo = RoomMemo(binding.editMemo.text.toString(), System.currentTimeMillis())
                helper?.roomMemoDAO()?.insert(memo)
                adapter.listData.clear()
                adapter.listData.addAll(helper?.roomMemoDAO()?.getAll()?:listOf())
                adapter.notifyDataSetChanged()
                binding.editMemo.setText("")
            }
        }

추가한 코드 맨 아래에서 cancelUpdate( ) 메서드를 호출하는데, 이 때에는 수정을 취소하기 위한 목적이 아니라 수정을 완료한 후에 권상태로 복귀하기 위하여 호출하는 것입니다.


7. 수정취소 버튼 클릭 리스너 달기

activity_main.xml 파일에 대해 설명할 때 수정취소 버튼은 평소에는 보이지 않다가, 수정 작업을 할 때에만 가시화된다고 했습니다.

이 수정취소 버튼에도 클릭 리스너를 달아주고, 안에서는 수정취소 함수를 호출하도록 합니다.

        // 7-7. 메모 수정하기: 수정 취소 버튼 클릭 리스너 달기
        binding.btnCancel.setOnClickListener {
            cancelUpdate()
        }


이제 실질적인 수정 메서드들을 정의합니다.

메인 액티비티에 수정과 관련하여 3개의 메서드를 추가하는데, 각각 메모 수정 작업을 세팅, 수정을 취소(또는 완료 후 복귀), 수정 내용을 리사이클러 뷰 목록에 반영하기 위한 메서드입니다.


8. 메모 수정 작업 세팅 메서드 정의: setUpdate( )

리사이클러 뷰 아이템이 클릭되면 그 신호를 어댑터의 Holder 클래스에서 받고 액티비티의 setUpdate( ) 메서드를 호출합니다.

setUpdate( ) 메서드에서는 수정 작업을 위한 세팅을 합니다.

    // 7-8. 메모 수정하기: 수정 작업을 위한 세팅
    fun setUpdate(memo: RoomMemo){
        updateMemo = memo // 수정할 메모 임시 저장

        binding.editMemo.setText(updateMemo!!.content) // 플레인 텍스트 내용을 수정할 텍스트로 설정
        binding.btnCancel.visibility = View.VISIBLE    // 숨겨둔 수정취소 버튼 가시화
        binding.btnSave.text = "수정"                   // 원래 '저장' 이었던 문자열을 '수정' 으로 변경
    }


9. 메모 수정 작업 취소 메서드 정의: cancelUpdate( )

cancelUpdate( ) 메서드는 수정 작업을 취소하거나 완료했을 때 호출됩니다. ‘수정 취소’ 버튼 클릭 시에도 호출되지만, 수정 작업을 완료한 후에 메인 액티비티를 수정 전의 상태(평소 상태)로 되돌리는 역할도 합니다.

    // 7-9. 메모 수정하기: 수정을 취소(원상태로 복귀)
    fun cancelUpdate(){
        updateMemo = null

        binding.editMemo.setText("")              // 플레인 텍스트 초기화
        binding.btnCancel.visibility = View.GONE  // 수정취소 버튼 비가시화
        binding.btnSave.text = "저장"              // '수정' 이었던 문자열을 다시 '저장' 으로 변경
    }


10. 수정 내용을 리사이클러 뷰 목록에 반영하는 메서드 정의: refreshAdapter( )

마지막으로 수정 작업 완료 후에 수정된 데이터베이스 내용을 리사이클러 뷰 어댑터의 목록에 반영하는 메서드를 정의합니다.

    // 7-10. 메모 수정하기: 수정 내용을 리사이클러 뷰 목록에 반영
    fun refreshAdapter(adapter: RecyclerAdapter){
        adapter.listData.clear()
        adapter.listData.addAll(helper?.roomMemoDAO()?.getAll() ?: mutableListOf())
        adapter.notifyDataSetChanged()
    }


이상으로 수정 기능 추가를 위한 코드 작성을 마쳤습니다.



결과 화면


6, 7, 8번 메모를 추가한 후에 7번 메모를 수정한 모습입니다.

image-20210821153311170



정리


  • Room 라이브러리가 사용하는 ORM 기술은 클래스와 관계형 데이터베이스의 데이터를 매핑하고 변환하는 기술입니다.
  • Room 라이브러리는 다음 과정을 따라 이용할 수 있습니다.
    • 플러그인 및 의존성 추가
    • RoomMemo 클래스 생성: 데이터베이스에서 하나의 테이블을 나타냄
    • RoomMemoDAO 인터페이스 생성: DML 쿼리를 수행하는 메서드를 선언
    • RoomHelper 추상 클래스 생성: RoomHelperDAO 인터페이스와 실제 데이터베이스를 연결

Categories:

Updated:

Leave a comment