[Android] 8(3). Room: ORM 라이브러리
Room: ORM 라이브러리
ORMObject-Relational Mapping 은 객체(Class)와 관계형 데이터베이스의 데이터(Table)을 매핑하고 변환하는 기술로 복잡한 쿼리를 잘 몰라도 코드만으로 데이터베이스의 모든 것을 컨트롤할 수 있도록 도와줍니다.
간단한 예로 다음 그림과 같이 코드의 클래스 파일에 ORM을 적용하면 자동으로 쿼리로 변환해서 테이블을 생성해줍니다.
안드로이드는 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] 키를 누릅니다.
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’으로 하여 평소에는 보이지 않다가 수정 작업을 할 때만 가시화되도록 합니다.
아래는 평소 화면과 수정 작업 시 화면입니다.
이제 본격적으로 코드를 수정합니다.
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
수정을 하기 위해서는 어댑터가 액티비티에 있는 메서드를 호출해야 합니다. 수정 과정은 다음과 같습니다.
- 리사이클러 뷰 목록의 아이템 클릭
- 클릭 신호를 어댑터의 리스너에서 받아서 mainActivity의 setUpdate 메서드 호출
- 호출된 mainActivity의 setUpdate 메서드에서 데이터베이스와 리사이클러 뷰 목록 수정 메서드 호출
- 실제 수정 수행
즉 클릭 신호는 어댑터에서 받고, 수정 메서드 호출은 메인 액티비티에서 이루어지기 때문에 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번 메모를 수정한 모습입니다.
정리
- Room 라이브러리가 사용하는 ORM 기술은 클래스와 관계형 데이터베이스의 데이터를 매핑하고 변환하는 기술입니다.
- Room 라이브러리는 다음 과정을 따라 이용할 수 있습니다.
- 플러그인 및 의존성 추가
- RoomMemo 클래스 생성: 데이터베이스에서 하나의 테이블을 나타냄
- RoomMemoDAO 인터페이스 생성: DML 쿼리를 수행하는 메서드를 선언
- RoomHelper 추상 클래스 생성: RoomHelperDAO 인터페이스와 실제 데이터베이스를 연결
Leave a comment