https://github.com/android/architecture-samples
이 프로젝트를 보고 해석하여 작성한 글이다. 일부 코드는 위 프로젝트와 다르게 작성되었다. 리사이클러뷰 데이터 바인딩은 조금 복잡하다. (정말?) 다음과 같은 과정으로 진행된다. Activity 와 ViewModel 을 바인딩하는 방법은 이미 알고 있을 거라 가정하며 자세하게 설명하지 않고 코드 위주로 설명한다.
- 프로젝트 세팅
- ViewModel 및 더미 데이터 생성
- MainActivity 와 ViewModel 연결
- Adapter 및 ViewHolder stub 구현
- Binding 함수 구현
- RecyclerView 배치 및 더미데이터 연결
- RecyclerView 의 item layout 에 데이터 바인딩
- ViewHolder 의 factory method 구현 및 bind 함수 구현
- RecyclerView.Adapter 구현
- MainActivity 에서 Adapter 연결
1. 프로젝트 세팅
ViewModel 과 데이터 바인딩을 위해 모듈 수준의 build.gradle 에 다음을 추가한다.
apply plugin: 'kotlin-kapt'
android {
dataBinding {
enabled = true
}
}
dependencies {
implementation "androidx.fragment:fragment-ktx:1.4.0"
}
2. ViewModel 및 더미데이터 생성
String 만 갖고 있는 간단한 데이터 클래스를 사용한다.
data class ExampleItem(val content: String)
class MainViewModel : ViewModel() {
val dummyList = listOf(
ExampleItem("아무말1"),
ExampleItem("아무말2"),
ExampleItem("아무말3"),
ExampleItem("아무말4"),
ExampleItem("아무말5"),
)
}
3. MainActivity 와 ViewModel 연결
<?xml version="1.0" encoding="utf-8"?>
<layout>
<data>
<variable
name="viewModel"
type="org.algosketch.datebindingtest.MainViewModel" />
</data>
<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.constraintlayout.widget.ConstraintLayout>
</layout>
4. Adapter 및 ViewHolder stub + @
class ExampleAdapter : RecyclerView.Adapter<ExampleAdapter.ExampleViewHolder>() {
lateinit var exampleList: List<ExampleItem>
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ExampleViewHolder {
TODO("Not yet implemented")
}
override fun onBindViewHolder(holder: ExampleViewHolder, position: Int) {
TODO("Not yet implemented")
}
override fun getItemCount(): Int {
TODO("Not yet implemented")
}
class ExampleViewHolder(view: View) : RecyclerView.ViewHolder(view) {
}
}
RecyclerView 에서 사용할 list 를 만들어 준다.
5. Binding 함수 구현
@BindingAdapter("app:items")
fun setList(recyclerView: RecyclerView, items: List<ExampleItem>?) {
items?.let {
(recyclerView.adapter as ExampleAdapter).exampleList = items
}
}
여기서부터 RecyclerView 를 바인딩하기 위한 코드가 시작된다. 이 함수는 RecyclerView 에 연결할 리스트를 연결하기 위한 용도로 사용된다. RecyclerView 를 사용하는 xml 파일에서 app:items 속성으로 대입할 수 있다. "여기에 들어갈 값은 아무렇게나 넣어도 된다."
6. RecyclerView 배치 및 더미데이터 연결
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
android:orientation="vertical"
app:items="@{viewModel.dummyList}" />
2번에서 ViewModel 에 만들어둔 더미 데이터를 5번에서 만든 바인딩 함수를 이용하여 데이터를 바인딩 시켜준다.
7. RecyclerView 의 item layout 에 데이터 바인딩
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="item"
type="org.algosketch.datebindingtest.ExampleItem" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
tools:text="여기 적힌 내용이 토스트 메시지로 출력될 거야!"
android:text="@{item.content}"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
tools:text 는 미리보기 화면에서만 보이는 내용이니 신경쓰지 않아도 된다. 중요한 건 android:text="@{item.content}" 와 같은 방식으로 데이터를 바인딩할 수 있다는 것이다. xml 에서 사용할 변수는 ViewModel 과 같은 방식으로 선언할 수 있다. 바로 다음인 8번에서 바인딩 함수를 구현할 것이다.
8. ViewHolder 의 factory method 구현 및 bind 함수 구현
class ExampleAdapter : RecyclerView.Adapter<ExampleAdapter.ExampleViewHolder>() {
lateinit var exampleList: List<ExampleItem>
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ExampleViewHolder {
TODO("Not yet implemented")
}
override fun onBindViewHolder(holder: ExampleViewHolder, position: Int) {
TODO("Not yet implemented")
}
override fun getItemCount(): Int {
TODO("Not yet implemented")
}
class ExampleViewHolder private constructor(val binding: ItemExampleBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(item: ExampleItem) {
binding.item = item
binding.executePendingBindings()
}
companion object {
fun from(parent: ViewGroup) : ExampleViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = ItemExampleBinding.inflate(layoutInflater, parent, false)
return ExampleViewHolder(binding)
}
}
}
}
companion object 로 생성자를 호출하여 반환하는 from 메서드를 구현한다. 앞으로 ViewHolder 는 이 메서드를 통해서만 생성되므로 주 생성자는 private 으로 막아둔다. 생성자에는 binding 객체가 들어가고, 이 객체를 통해서 bind 함수를 구현한다. bind 함수는 매우 간단하다. 바인딩 해야하는 View 의 수가 늘어도 바뀌지 않을 것이다.
executePendingBindings() 메소드는 지금 상황에선 없어도 되지만, 렌더링 이후에 바뀌는 값이 있다면 바뀐 부분이 적용되지 않을 수 있다. 그때 이 메소드를 사용해주면 해결된다.
9. RecyclerView.Adapter 구현
class ExampleAdapter : RecyclerView.Adapter<ExampleAdapter.ExampleViewHolder>() {
lateinit var exampleList: List<ExampleItem>
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ExampleViewHolder {
return ExampleViewHolder.from(parent)
}
override fun onBindViewHolder(holder: ExampleViewHolder, position: Int) {
holder.bind(exampleList[position])
}
override fun getItemCount() = exampleList.size
class ExampleViewHolder private constructor(val binding: ItemExampleBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(item: ExampleItem) {
binding.item = item
}
companion object {
fun from(parent: ViewGroup) : ExampleViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = ItemExampleBinding.inflate(layoutInflater, parent, false)
return ExampleViewHolder(binding)
}
}
}
}
Adapter 코드를 구현하며 마무리한다. (끝인줄 알았는데 깜빡하고 아답터 연결을 안 해줬다.) Adapter 코드는 매우 간단하여 설명이 필요없다.
10. MainActivity 에서 Adapter 연결
class MainActivity : AppCompatActivity() {
...
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
...
setupAdapter()
}
fun setupAdapter() {
binding.exampleRecyclerView.adapter = ExampleAdapter()
}
}
진짜 끝이다. 이제 실행된다.
이 글에 대한 코드는 아래에서 확인할 수 있다.
https://github.com/HamBP/recyclerview-test
다음 글에서는 같은 코드를 기반으로 이벤트까지 바인딩시키는 방법을 알아본다.
'안드로이드' 카테고리의 다른 글
[안드로이드] 리사이클러뷰 데이터 바인딩 이벤트 (RecyclerView data binding) (0) | 2022.01.27 |
---|---|
[안드로이드] 애플리케이션 아키텍처 (0) | 2022.01.23 |
[안드로이드] ViewModel 에서 이벤트 처리하는 방법 (SingleLiveEvent) (0) | 2022.01.14 |
[안드로이드] Retrofit 을 이용하는 6가지 방법 (수정 : 22-01-13) (0) | 2022.01.10 |
[안드로이드] 보일러 플레이트 (상용구 코드) (0) | 2021.12.31 |