[Kotlin] RecyclerView에 LiveData/ViewModel 적용하기
목표
전 포스트에서 Recyclerview에 item을 추가 삭제할 수 있는 기능을 구현해봤는데요, 화면 UI가 변경될 때 뷰가 초기화되는 현상이 있었습니다. 이 문제를 해결하기 위해 이 포스트에서는 LiveData와 ViewModel을 이용해 UI 관련 데이터를 한 곳에서 제어할 수 있는 ViewModel을 만들어 UI가 다시 만들어져도 데이터가 초기화되지 않도록 해보겠습니다. (뷰바인딩에 익숙하지 않으시다면 아래 링크되어있는 전 포스트를 보고 오시는 것을 추천합니다.)
[Kotlin] RecyclerView에 setOnClickListener 추가 삭제하기 - RecyclerView 2
RecyclerView에 setonClickListener 추가 삭제하기 - RecyclerView 2 전 포스트에서 Recyclerview에 Viewbinding을 적용했는데요, 이번 포스트에서는 뷰 바인딩이 적용된 리사이클러뷰에 버튼을 눌러서 항목을 지..
underdog11.tistory.com
ViewModel 이해
- 아래 이미지는 android developer에 있는 기존 라이프 사이클과 Viewmodel라이프 사이클의 비교 이미지입니다.
- 우리가 전에 만들었던 todoList에서는 데이터들이 onCreate에있어서, activity가 rotate 됐을 때 onDestroy에서 다시 oncreate에서 그려지는 과정에서 데이터가 리셋됨을 확인했습니다.
- ViewModel은 앱이 종료될때까지 살아있어서 lifecycle을 신경 써야 하는 고충을 해결해줍니다.
- 이번예제에서는 ViewModel을 사용해 UI가 변경되어도 data가 유지될수 있도록 하고
- LiveData를 활용해 ViewModel에 있는 데이터가 추가되거나 제거됬을때 업데이트 되도록 해보겠습니다.
Dependencies 추가
- buildGradle.app
android{
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
// ViewModel
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1")
// LiveData
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.3.1")
//KTX
implementation "androidx.fragment:fragment-ktx:1.3.3"
}
ViewModel 만들어주기
- 이해를 돕기위해 전 포스트에서 만든 ViewModel 적용전 코드입니다.
- Mainactivity를 보면 데이터를 수정하는 addTaskfunction과 deleteTask가 onCreate 안에서 실행되고있는것을 볼수있습니다.
- addTask, deleteTask function을 ViewModel로 이동을 하여 데이터 관련 함수들은 모두 Viewmodel에서 사용할수있도록 하겠습니다. (MainActivity아래에 만들었습니다.)
class MyViewModel : ViewModel() {
val data = arrayListOf<Todo>() //class MainActivity에있던 val data를 ViewModel로 가져옵니다.
// mainactivity에있던 데이터관련 function들을 가져옵니다.
//수정전 시작
// fun addTask() {
// val todo = Todo(binding.editText.text.toString()) //Todo를 밖에서 가져오도록 바꿉니다.
// data.add(todo)
// binding.recyclerView.adapter?.notifyDataSetChanged() 더이상 바인드를 access할수없게되므로 지웁니다.
// }
// fun deleteTask(todo: Todo) {
// data.remove(todo)
// binding.recyclerView.adapter?.notifyDataSetChanged() addtask와 똑같이 지워줍니다.
//}
//수정전 끝
//수정후 시작
fun addTask(todo:Todo) {//
data.add(todo)
//norifyDataSetChanged가 없어져서 UI로 업데이트를 할수없습니다.
}
fun deleteTask(todo: Todo) {
data.remove(todo)
}
//수정후 끝
ViewModel에 LiveData 추가하기
- addTask와 deleteTask 함수들이 Viewmodel에들어가면서 binding.recyclerView.Adapter?.notifyDataSetChanged()를 사용할수없게되면서, UI에 바뀐데이터를 업데이트해줄 코드가 없어진걸 확인할수있습니다.
- 그래서 UI업데이트를 해주기위해 LiveData를 추가해줘야합니다.
- LiveData는 관찰/변경 가능하기 때문에 val data에있었던 데이터들을 변경이있을때마다 todoLiveData로 업데이트를 해주겠습니다.
class MyViewModel : ViewModel() {
//추가 시작
val todoLiveData = MutableLiveData<List<Todo>>() //변경/관찰가능한 List<Todo>타입에 LiveData
//추가 끝
private val data = arrayListOf<Todo>()
fun toggleTodo(todo:Todo){
todo.isDone = !todo.isDone
todoLiveData.value = data
}
fun addTask(todo:Todo) {
data.add(todo)
//추가시작
todoLiveData.value = data //todoLiveData를 add된 데이터로 변경
//추가 끝
}
fun deleteTask(todo: Todo) {
data.remove(todo)
//추가시작
todoLiveData.value = data //todoLiveData를 remove된 데이터로 변경, 이제 TodoLiveData로 UI값을 변경해줘야한다.
//추가 끝
}
- 이렇게 ViewModel에 Livedata적용은 끝났습니다.
LiveData를 MainActivity - onCreate로 불러오기
- 앞에서 말했듯이 LiveData는 관찰과 변경이 가능합니다. 그래서 데이터가 변할때 관찰을 onCreate에서 하게 해서 UI가 다시만들어지거나 변할때 기존데이터와 업데이트되는 데이터를 불러오게 하겠습니다.
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private val viewModel: MyViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
binding.recyclerView.layoutManager = LinearLayoutManager(this)
binding.recyclerView.adapter = TodoAdapter(
emptyList(),
onClickDeleteIcon = {
viewModel.deleteTask(it)
//제거 binding.recyclerView.adapter?.notifyDataSetChanged() LiveData로 UI업데이트를 해줄거기때문에 이제 필요없습니다.
}
)
binding.addButton.setOnClickListener {
val todo = Todo(binding.editText.text.toString())
viewModel.addTask(todo)
//제거 binding.recyclerView.adapter?.notifyDataSetChanged()
}
//추가시작
//관찰UI업데이트
viewModel.todoLiveData.observe(this, Observer { //viewmodel에서 만든 변경관찰 가능한todoLiveData를 가져온다.
(binding.recyclerView.adapter as TodoAdapter).setData(it) //setData함수는 TodoAdapter에서 추가하겠습니다.
})
}
//추가끝
}
RecyclerAdapter에 setData함수만들기
- setData라는 함수를 Adapter안에 구현해서, 이함수에 LiveData를 recyclerAdapter로 가져온후 기존데이터를 새로운데이터로 바꿔주도록 기능을만듭니다. 그리고 위에 관찰 UI에서 실행하도록 합니다.
class TodoAdapter(
private var mydataSet: List<Todo>,
val onClickDeleteIcon: (todo: Todo) -> Unit
) :
RecyclerView.Adapter<TodoAdapter.TodoViewHolder>() {
class TodoViewHolder(val binding: ItemTodoBinding) :
RecyclerView.ViewHolder(binding.root) { //아이템을 만들때 여러 뷰가있기때문에 itemTodobinding으로 가져온다.
}
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): TodoViewHolder {
val view = LayoutInflater.from(viewGroup.context)
.inflate(R.layout.item_todo, viewGroup, false) //내가 각아이템에 사용하는 view
return TodoViewHolder(ItemTodoBinding.bind(view))
}
override fun onBindViewHolder(todoViewHolder: TodoViewHolder, position: Int) {//item을 화면에 표시해주는
val listposition = mydataSet[position]
todoViewHolder.binding.todoText.text = listposition.text
todoViewHolder.binding.deleteImage.setOnClickListener {
onClickDeleteIcon.invoke(listposition) //눌렀을때 listposition를 전달하면서 함수를 실행한다.
}
}
override fun getItemCount() = mydataSet.size
//추가시작
fun setData(newData:List<Todo>){
mydataSet = newData
notifyDataSetChanged()
}
//추가 끝
}
실행해보시면 화면전환후에도 TodoList들이 유지되는걸 볼수있습니다. 아래는 이전 포스트들입니다.
1편:[Kotlin] RecyclerView에 ViewBinding 적용하기 - RecyclerView 1
[Kotlin] RecyclerView에 ViewBinding 적용하기 - RecyclerView 1
[Kotlin] RecyclerView에 ViewBinding 적용하기 목표 ViewBinding 추가하기 buildGradle.app android{ viewBinding { enabled = true } } MainActivity onCreate을 Viewbinding으로 수정해주기 **AcivityMainBind..
underdog11.tistory.com
2편:[Kotlin] RecyclerView에 setOnClickListener 추가 삭제하기 - RecyclerView 2