지난 포스트
목표
이 포스트는 총 5편으로 이루어져 있습니다. 밑줄 친 항목이 이번 포스트에서 다룰 항목 입니다.
RoomDatabase 개념
Roomdatabase의 기본 요소인 Entity, Database, Dao 개념/구현 (1편)LiveData, ViewModel, Repository를 이용하여 MVVM 아키텍처 구성(1편)
Insert 구성
Navigation Graph , NavHostFragment추가(2편)각 list, update, add fragment의 layout 및 프래그먼트 구현(2편)Add Fragment를 여는 플로팅 버튼 추가 (2편)DB browser를 통해 database 확인(2편)RecyclerView에 들어갈 ItemLayout과 ListAdapter 추가(3편)RecyclerView와 LiveData 적용하기(3편)위와 같이 Room database에 저장된 값을 Recyclerview와 연결하기 (3편)
Update 구성
- Update layout 생성(4편)
- Update Layout Navigation Graph에 연결(4편)
- Navigation Argument 추가하기(4편)
- 데이터베이스에 수정된 값 업데이트(4편)
Delete 구성
- 제거 버튼 Action bar에 추가(5편)
- 선택한 데이터 삭제(5편)
- 모든 데이터 삭제(5편)
Update 구성
Recyclerview에 있는 리스트를 클릭했을때 클릭한 데이터를 수정할수있도록, Update를 구성하려합니다. 그러기위해서 새로운 fragment가 필요합니다.
Update 레이아웃 생성
layout에는 Add에 썻던 layout에 타이틀만 바꿔서 사용하였습니다. id만 바꿔주겠습니다.
- fragment_update
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="24dp"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".fragments.update.UpdateFragment">
<EditText
android:id="@+id/updateFirstName_et"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="88dp"
android:ems="10"
android:hint="First Name"
android:inputType="textPersonName"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/updateLastName_et"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ems="10"
android:hint="Last Name"
android:inputType="textPersonName"
app:layout_constraintEnd_toEndOf="@+id/updateFirstName_et"
app:layout_constraintStart_toStartOf="@+id/updateFirstName_et"
app:layout_constraintTop_toBottomOf="@+id/updateFirstName_et" />
<EditText
android:id="@+id/updateAge_et"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ems="10"
android:hint="age"
android:inputType="number"
app:layout_constraintEnd_toEndOf="@+id/updateLastName_et"
app:layout_constraintStart_toStartOf="@+id/updateLastName_et"
app:layout_constraintTop_toBottomOf="@+id/updateLastName_et" />
<Button
android:id="@+id/update_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button"
app:layout_constraintEnd_toEndOf="@+id/updateAge_et"
app:layout_constraintStart_toStartOf="@+id/updateAge_et"
app:layout_constraintTop_toBottomOf="@+id/updateAge_et" />
</androidx.constraintlayout.widget.ConstraintLayout>
Update Layout Navigation Graph에 연결
이제 recyclerview list가 클릭되면 update fragment로 화면전환이 이뤄지도록 2편에서 다뤘던 navigation graph에 update fragment를 list fragment 와 연결해주겠습니다.
Navigation Argument로 데이터 전달
Update fragment에 Argument가 필요합니다.
Argument는 navigation 대상간에 데이터 전달을 할때 사용하게 됩니다.
Argument를 사용하면 유저가 위에서 list Fragment에서 아이템을 클릭하면 update fragment로 이동하면서 어떤 USER 오브젝트 아이템이 클릭되었는지 전달하게 됩니다.
Argument를 추가해 보도록 하겠습니다.
Argument를 추가하기전 USER 모델을 Parcel가능하게 만들어야합니다. 왜냐하면 fragment사이에서 User오브젝트 자체를 주고받을수없고 오브젝트를 먼저 parcelable 하게 해야 전달 가능해집니다. User 모델로 가줍니다.
- model/user
@Parcelize //추가
@Entity(tableName = "user_table")
data class User(
@PrimaryKey(autoGenerate = true)
val id:Int,
val firstName:String,
val lastName:String,
val age:Int
):Parcelable{ //추가
}
- res/navigation/my_nav
my_nav로 들어가준후 designtab으로 이동합니다. 그리고 Argument Tab에서 +를 눌러줍니다.
Add Argument창이 뜨면 이름을 CurrentUser로 하고 타입을 custom Parcelable로 하면 Parcel가능한 오브젝트 리스트가 보이게됩니다.
방금 parcelable로 설정해준 User모델을 볼수있습니다. 선택후 OK눌러줍니다.
그러면 argument가 추가되었습니다.
이제 listFragment에서 리스트에 setOnClickListener를 추가해줘야합니다.
- fragments/list/ListAdapter
class ListAdapter: RecyclerView.Adapter<ListAdapter.MyViewHolder>() {
private var userList = emptyList<User>() //
class MyViewHolder(itemView: View):RecyclerView.ViewHolder(itemView){
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
return MyViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.custom_row,parent,false))
}
override fun getItemCount(): Int {
return userList.size
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val currentItem = userList[position]
holder.itemView.id_txt.text = currentItem.id.toString()
holder.itemView.firstName_txt.text = currentItem.firstName
holder.itemView.lastName_txt.text = currentItem.lastName
holder.itemView.age_txt.text = currentItem.age.toString()
//추가 시작
holder.itemView.rowLayout.setOnClickListener{
//actionListFragment 함수가 자동으로 생성 되지않는다면 프로젝트를 rebuild 해주세요
val action = ListFragmentDirections.actionListFragmentToUpdateFragment(currentItem)
holder.itemView.findNavController().navigate(action)
}
//추가 끝
}
fun setData(user:List<User>){
this.userList = user
notifyDataSetChanged()
}
}
이제 실행을 해보겠습니다. 그럼 아래와같이 editText에 빈값이 들어가있는걸 확인할수있습니다. 데이터베이스에 User오브젝트 데이터 값을 넣어주어서 유저가 데이터를 관리하기 더 편리하게 해보겠습니다.
- fragments/update/UpdateFragment
class UpdateFragment : Fragment() {
private val args by navArgs<UpdateFragmentArgs>() //만약 에러가 보인다면 dependency를 추가해야합니다.
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_update, container, false)
//editText부분을 argument에서 받아온 값을 넣어줍니다.
view.updateFirstName_et.setText(args.currentUser.firstName)
view.updateLastName_et.setText(args.currentUser.firstName)
view.updateAge_et.setText(args.currentUser.age.toString())
return view
}
**fragmentArgs에 에러가 보인다면 아래 compile options과 kotlin options를 추가해주세요**
android {
compileSdkVersion 30
buildToolsVersion "30.0.3"
defaultConfig {
applicationId "com.tassiecomp.roomdatabasepractice"
minSdkVersion 26
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
//추가시작
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
}
//추가 끝
}
**그래도 해결이 안된다면 Gradle 아래에 플러그인이 있는지 확인해주세요**
build.gradle(module)
apply plugin: "androidx.navigation.safeargs.kotlin"
buildGradle(project)
dependencies {
...
def nav_version = "2.3.5"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
}
데이터베이스에 수정된 값 업데이트
우리가 updateFragment에서 수정된값을 EditText에 입력하면 데이터에 이 값을 전달하기 위해서 새로운 함수를 UpdateFragment에 만들어줘야합니다.
class UpdateFragment : Fragment() {
private val args by navArgs<UpdateFragmentArgs>()
private lateinit var mUserViewModel:UserViewModel
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val view = inflater.inflate(R.layout.fragment_update, container, false)
mUserViewModel = ViewModelProvider(this).get(UserViewModel::class.java)
view.updateFirstName_et.setText(args.currentUser.firstName)
view.updateLastName_et.setText(args.currentUser.firstName)
view.updateAge_et.setText(args.currentUser.age.toString())
view.update_button.setOnClickListener{
updateItem()
}
//Add Menu
setHasOptionsMenu(true)
return view
}
private fun updateItem(){
// 변경된 값들을 editText에서 가져옵니다.
val firstName = updateFirstName_et.text.toString()
val lastName = updateLastName_et.text.toString()
val age = Integer.parseInt(updateAge_et.text.toString())
if (inputCheck(firstName,lastName,updateAge_et.text)){
//updatedUser는 업데이트된 데이터입니다.
val updatedUser = User(args.currentUser.id, firstName, lastName,age)
//updateUser쿼리를 만들어서 Update Query를 이용하여 database에 추가해줘야합니다.
//Update 쿼리는 DAO에서 추가해야합니다.
//지금은 viewModel에 update 쿼리가 생기면 updatedUser가 전달되도록 구현만 해놓겠습니다.
mUserViewModel.updateUser(updatedUser)
Toast.makeText(requireContext(),"UpdatedSuccessfully",Toast.LENGTH_SHORT).show()
//navigate back
findNavController().navigate(R.id.action_updateFragment_to_listFragment)
} else{
//EditText가 빈칸이면 토스트 메세지
Toast.makeText(requireContext(),"Please fill out all field",Toast.LENGTH_SHORT).show()
}
}
//editText가 비어있는지 확인하는 함수
private fun inputCheck(firstName:String, lastName:String, age: Editable):Boolean{
return !(TextUtils.isEmpty(firstName)&& TextUtils.isEmpty(lastName)&& age.isEmpty())
}
}
- data/UserDao
interface UserDao {
@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun addUser(user: User)
//추가시작
@Update
suspend fun updateUser(user:User) //suspend 코루틴사용됨
//추가끝
@Query("SELECT * FROM user_table ORDER BY id ASC")
fun readAllData(): LiveData<List<User>>
}
- repository/UserRepository
class UserRepository(private val userDao: UserDao) {
val readAllData: LiveData<List<User>> = userDao.readAllData()
suspend fun addUser(user: User){ //suspend를 붙여준 이유는 coroutine을 사용하기 위함입니다.
userDao.addUser(user) //DAO에서 만들었던 adduser을 실행합니다.
}
suspend fun updateUser(user:User){
userDao.updateUser(user)
}
}
- viewmodel/UserViewModel
class UserViewModel(application: Application): AndroidViewModel(application) {
val readAllData:LiveData<List<User>>
private val repository: UserRepository
init {
val userDao = UserDatabase.getDatabase(application).userDao()
repository = UserRepository(userDao) //initialize repository
readAllData = repository.readAllData
}
fun addUser(user: User){
viewModelScope.launch(Dispatchers.IO) {
repository.addUser(user)
}
}
fun updateUser(user:User){
viewModelScope.launch(Dispatchers.IO) {
repository.updateUser(user)
}
}
}
다음포스트에서는 데이터베이스에서 항목을 delete 쿼리를 사용하여 삭제해보겠습니다.