7주차 실습(1)-roomDB
1. 프로젝트 만들고
2. 그래들 dependendcies에 room
// RoomDB
implementation("androidx.room:room-runtime:2.6.0")
annotationProcessor("androidx.room:room-compiler-processing:2.6.0")
// kapt("androidx.room:room-compiler:$room_version")
맨마지막줄은 왜 잘 안되는줄 모르겠음
3. 똑같이 그래들 android에 뷰바인딩 설정해주고
4. activity_main.xml에 아무 리사이클러 뷰 선언하고 아이디 달아줌 id는 rv_home
5. MainActivity에 뷰바인딩 하기
package com.iyr.a7thweekpractice
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.recyclerview.widget.LinearLayoutManager
import com.iyr.a7thweekpractice.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
lateinit var binding : ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.rvHome.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
}
}
6. 리사이클러뷰 어댑터 만들어주기
package com.iyr.a7thweekpractice
import androidx.recyclerview.widget.RecyclerView
class JustAdapter(var items: List<String>): RecyclerView.Adapter<JustAdapter.ViewHolder>() {
inner class ViewHolder(val binding : )
}
inner class ViewHolder 만들어주고 아이템레이아웃만들러 가기
7. item_home.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"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tv_item_home"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:text="item"
android:textSize="50sp"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
8. 다시 어댑터에서 바인딩부터 해주기
8-1. 아이템 레이아웃 바인딩
inner class ViewHolder(val binding : ItemHomeBinding) : RecyclerView.ViewHolder(binding.root) {
}
8-2. 3가지 메서드 override
package com.iyr.a7thweekpractice
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.iyr.a7thweekpractice.databinding.ItemHomeBinding
class JustAdapter(var items: List<String>): RecyclerView.Adapter<JustAdapter.ViewHolder>() {
inner class ViewHolder(val binding : ItemHomeBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(item : String) {
binding.tvItemHome.text = item
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding = ItemHomeBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ViewHolder(binding)
}
override fun getItemCount(): Int {
return items.size
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(items[position])
}
}
여기까지는 항상 쓰는 코드.
getItemCount에서 item 크기 반환해주고,
onCreateViewHolder에서 아이템레이아웃 바인딩한 다음
뷰홀더에 바인딩한거 넣어서 반환해주고,
(ViewHolder에 bind 함수 선언해서(바인딩한 텍스트뷰를 전달받은 item 값으로 세팅하도록))
onBindViewHolder에서 bind 함수 호출하면서 items의 position 값 전달.
9. MainActivity에서 데이터 리스트 생성하고 어댑터 연결
package com.iyr.a7thweekpractice
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.recyclerview.widget.LinearLayoutManager
import com.iyr.a7thweekpractice.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
lateinit var binding : ActivityMainBinding
val dataList : ArrayList<String> = arrayListOf<String>("item0", "item1", "item2", "item3", "item4", "item5", "item6", "item7")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.rvHome.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
binding.rvHome.adapter = JustAdapter(dataList)
}
}
리사이클러뷰 자체의 어댑터에 내가 만든 어댑터에 데이터리스트 전달한 것을 할당해주기
리사이클러뷰 출력 화면.
여기까진 복습.
이제 리사이클러뷰의 아이템을 데이터베이스에 저장해서 가져오는 것을 해볼 것이다.
10. 소스파일로 entity(테이블) 추가. 이름은 MyStringEntity
10-1. @Entity 라는 거 알려주고(class 밖에 추가) 이름은 MyStringTable
10-2. data class로 바꿔주고
10-3. 변수는 string으로 하나만(소괄호 안에)
10-4. (중괄호 안에)데이터베이스가 가져야 하는 키 선언
초기값은 0
11. entity(DB)에 접근할 수 있는 DAO(Data Access Object) 만들어주기
코틀린파일 새로 만듦. 이름은 MyStringDAO
11-1. entity랑 똑같이 class 밖에 @Dao 라고 명시
11-2. class 가 아닌 interface로 바꾸기
11-3. @Insert
11-4. (함수선언)이 DAO를 통해 entity로 접근해서 (myString이라는) 객체 추가해줄거임
fun addMyString(myString : MyStringEntity)
MyStringDao 객체를 가져와서(인터페이스로부터?)
addMyString 함수를 호출해서
그 인자로 myString 객체를 넣어주면
테이블에 써진다!
11-5. @Update, @Delete도 똑같음
fun updateMyString(myString : MyStringEntity)
@Delete
fun deleteMyString(myString : MyStringEntity)
11-6. 근데 Select는 옵션이 많아서 따로 작성해줘야함
간단하게 전체를 선택하는 query문을 작성해보겠다.
query 문법은 따로 공부해야함
fun getAllMyStrings() : List<MyStringEntity>
package com.iyr.a7thweekpractice
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update
@Dao
interface MyStringDAO {
@Insert
fun addMyString(myString : MyStringEntity)
@Update
fun updateMyString(myString : MyStringEntity)
@Delete
fun deleteMyString(myString : MyStringEntity)
@Query("SELECT * FROM MyStringTable")
fun getAllMyStrings() : List<MyStringEntity>
}
12. 이 DAO를 담고 있는 데이터베이스 객체를 만들어주자
New 코틀린 파일-이름은 MyStringDatabase, 그리고 RoomDatabase를 상속받아야 함
12-1. @Database 명시(class 밖에)
12-2. 명시할 게 많음.
먼저, 가지고 있는 테이블이 뭔지, 데이터베이스 버전 몇인지 등등
이 두가지는 필수적으로 명시
12-3. class -> abstract class로 바꿈
12-4. 데이터베이스는 entity에 접근할 수 있는 dao들을 가짐. 그 dao들을 가져오는 abstract 함수 선언.
(가져옴으로써 편집 등을 할 수 있도록)
싱글톤 패턴이란?
싱글톤(Singleton) 패턴의 정의는 단순하다. 객체의 인스턴스가 오직 1개만 생성되는 패턴
장점 1. 메모리측면
장점 2. 클래스 간의 데이터 공유가 쉬움
싱글톤 패턴 이용
일단 객체 생성하고
var instance
static 함수로 MyString DB를 가져올수있도록 fun getInstance 선언함(? 이해안됨~)
instance 가 null이면
Room이라는 object의 databaseBuilder()라는 static 함수이용해서 데이터 할당(?)
package com.iyr.a7thweekpractice
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.Room.databaseBuilder
import androidx.room.RoomDatabase
@Database(entities = [MyStringEntity::class], version = 1)
abstract class MyStringDatabase : RoomDatabase() {
abstract fun getMyStringDAO() : MyStringDAO
companion object {
var instance : MyStringDatabase? = null
fun getInstance(context: Context) : MyStringDatabase {
if(instance == null) {
instance = Room.databaseBuilder(
context,
MyStringDatabase::class.java,
"mystring-database"
).fallbackToDestructiveMigration()
// 데이터 베이스 버전 올릴 때마다 데이터베이스 초기화됨
.allowMainThreadQueries().build() // databaseBuilder() 뒤에 꼭 build() 붙여야함
// 원래는 안되지만 일단 스레드 생성 없이 메인스레드에서 데이터에 접근하도록 요청함.
}
return instance!!
}
}
}
싱글톤 패턴으로 구성된 MyStringDatabase라는 함수(추상클래스?)가 만들어졌음
이 추상 클래스는
-RoomDatabase()를 상속하고 있고
-MyStringDAO를 가져올 수 있는 추상 함수도 있다.
그럼 객체는 언제 생성하냐?
객체를 코드에서 직접 생성하는 게 아닌,
MyStringDatabase.getInstance 로 가져올거임
fun getInstance 안에서 Room이라는 object 안에 있는 databaseBuidler()라는 함수가 생성함
알아서 dao도, entity, db도 생성해줌
이제 dao, entity, db 구성이 끝났음
몇가지 편의를 위한 함수를 추가해주자면
데이터베이스 버전 올릴때마다 데이터 베이스가 초기화됨
원래 안되는데 메인스레드에서 데이터 접근할수있도록 함.
13. 이제 데이터베이스를 사용해보자.
13-1. 우선 MainActivity에서, 데이터를 살짝 바꿔주자
<변경전>
<변경후>
데이터 따로 안넣어주고 빈 Arraylist로 선언
❗️트러블슈팅
변수선언할 땐 꼭 초기화를 해주거나(대입연산자가 있어야한단 뜻)
아니면 lateinit과 같은 초기화 지연을 해줘야한다.
var dataList : ArrayList<MyStringEntity> = arrayListOf<MyStringEntity>()
또는
var dataList = ArrayList<MyStringEntity>() 와 같이 초기화
13-2. 데이터베이스 객체 만들기
아까는 item0과 같은 데이터를 넣은 리스트를 선언해서 바로 어댑터에 넣어줬다.
이번에는 그 대신 데이터베이스 객체를 가져와서
데이터베이스 안에 데이터 저장한 다음,
데이터베이스 안에 저장된 데이터를 가져와서~ 그걸 어댑터에 넘겨주도록 하겠다.
(아까는 리스트에 직접 데이터 넣었으면 이번에는 db의 데이터를 이용하겠단 뜻)
그러므로 데이터베이스 객체부터 만들어주자
var myStringDatabase : MyStringDatabase? = null
13-3. 데이터베이스 객체 가져오기
onCreate 함수에서
myStringDatabase 변수에 MyStringDatabase 객체를 가져올 것이다.
MyStringDatabase 객체는 싱글톤 패턴이다.
그리고 MyStringDatabase() 이런 식으로 직접 객체를 만드는 게 아니라
myStringDatabase = MyStringDatabase.getInstance(this)
이런 식으로 그 안에 있는 getInstance라는 함수를 활용해서 객체를 가져오는 것이다.
그리고
myStringDatabase!!.getMyStringDAO().addMyString(MyStringEntity("item0"))
myStringDatabase!!.getMyStringDAO().addMyString(MyStringEntity("item1"))
그 안에 있는 getMyStringDAO에 접근을 해서 데이터를 넣을 것이다.(addMyString)
getMyStringDAO()를 이용해서 entity에 MyString 객체를 넣어줄 것이다.
그리고 값 넣어서 초기화해줌(item0, item1)
13-4. 데이터 베이스 가져오기
데이터를 넣었으니 가져와보자
똑같이 onCreate안에서
myStringDatabase!!.getMyStringDAO().getAllMyStrings()
또 getMyStringDAO에 접근해서
getAllMyStrings 선언해놨던 걸로 모든 데이터를 가져오도록 한다.
그리고 그걸 dataList라는 아까 선언한 변수에 저장해준다.
dataList.addAll(myStringDatabase!!.getMyStringDAO().getAllMyStrings())
13-5. 데이터타입도 알맞게 바꿔주기
이때 아까 dataList를 그냥 ArrayList<String>으로 선언하는 게 아니라
String을 MyStringEntity로 바꿔줘야 한다.
어댑터도 바꿔준다
var items: List<String> -> <MyStringEntity>
fun bind(item : String) -> (item : MyStringEntity)
binding.tvItemHome.text = item
->
binding.tvItemHome.text = item.str
// MainActivity 전체코드
package com.iyr.a7thweekpractice
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.recyclerview.widget.LinearLayoutManager
import com.iyr.a7thweekpractice.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
lateinit var binding : ActivityMainBinding
var dataList1 = ArrayList<MyStringEntity>() // 초기화 해야함
var dataList2 : ArrayList<String> = arrayListOf() // null로 선언
var myStringDatabase : MyStringDatabase? = null // 데이터베이스 객체 만듦
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
myStringDatabase = MyStringDatabase.getInstance(this)
myStringDatabase!!.getMyStringDAO().addMyString(MyStringEntity("item0"))
myStringDatabase!!.getMyStringDAO().addMyString(MyStringEntity("item1"))
myStringDatabase!!.getMyStringDAO().addMyString(MyStringEntity("item2"))
myStringDatabase!!.getMyStringDAO().addMyString(MyStringEntity("item3"))
myStringDatabase!!.getMyStringDAO().addMyString(MyStringEntity("item4"))
myStringDatabase!!.getMyStringDAO().addMyString(MyStringEntity("item5"))
myStringDatabase!!.getMyStringDAO().addMyString(MyStringEntity("item6"))
myStringDatabase!!.getMyStringDAO().addMyString(MyStringEntity("item7"))
myStringDatabase!!.getMyStringDAO().addMyString(MyStringEntity("item8"))
myStringDatabase!!.getMyStringDAO().addMyString(MyStringEntity("item9"))
myStringDatabase!!.getMyStringDAO().addMyString(MyStringEntity("item10"))
dataList1.addAll(myStringDatabase!!.getMyStringDAO().getAllMyStrings())
binding.rvHome.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
binding.rvHome.adapter = JustAdapter(dataList1)
}
}
// JustAdapter 전체 코드
package com.iyr.a7thweekpractice
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.iyr.a7thweekpractice.databinding.ItemHomeBinding
class JustAdapter(var items: List<MyStringEntity>): RecyclerView.Adapter<JustAdapter.ViewHolder>() {
inner class ViewHolder(val binding : ItemHomeBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(item : MyStringEntity) {
binding.tvItemHome.text = item.str
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding = ItemHomeBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ViewHolder(binding)
}
override fun getItemCount(): Int {
return items.size
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(items[position])
}
}
[정리]
우리가 한 일은
1. 데이터 베이스 객체 가져와서
myStringDatabase = MyStringDatabase.getInstance(this)
2. 데이터 넣고
myStringDatabase!!.getMyStringDAO().addMyString(MyStringEntity("item0"))
3. 그 데이터베이스에서 데이터 모두 가져온걸, dataList라는 변수에 넣은 다음
dataList.addAll(myStringDatabase!!.getMyStringDAO().getAllMyStrings())
4. 그 dataList를 가지고 어댑터 만듦
binding.rvHome.adapter = JustAdapter(dataList)
그러면 데이터가 실행할 때마다 누적되면서 새로 추가된다.
데이터 지우고 싶으면 버전을 업데이트 하면 초기화되고 한번만 데이터가 추가된 상태로 실행된다.
(내 프로젝트는 오류났는데 왜났는지 몰겟음~