저번에 했던 스크롤 시 로딩 화면을 추가하는 것에 이어서
Paging3에서 아이템을 삭제하고, 삭제가 처리되는 동안 로딩 화면을 띄우는 것을 해보려고 한다.
Room 삭제 쿼리 추가
@Dao
interface SampleDao {
...
/**
* 아이템 삭제
*/
@Query("DELETE FROM sample WHERE id = :id")
fun itemDelete(id: Int)
}
삭제가 요청되면 id 값을 전달받아서 해당 아이템을 삭제하도록 쿼리를 추가한다.
삭제 결과를 LiveData로 받도록 ViewModel 수정
class MainViewModel: ViewModel() {
...
/**
* Room 삭제 호출
*/
val itemDeleteObserve: MutableLiveData<Unit> = MutableLiveData()
fun setItemDelete(id: Int) {
viewModelScope.launch(Dispatchers.IO) {
itemDeleteObserve.postValue(SampleDatabase.sampleDB!!.getSampleDao().itemDelete(id))
}
}
}
RecyclerView 아이템 레이아웃 수정
<?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="100dp"
android:layout_height="wrap_content" >
<TextView
android:id="@+id/tv_item"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/black"
android:textSize="25dp"
android:textStyle="bold"
android:layout_marginTop="10dp"
android:layout_marginStart="10dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="1번" />
<ImageView
android:id="@+id/iv_item_remove"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/icon_remove"
android:layout_marginEnd="5dp"
app:layout_constraintTop_toTopOf="@id/tv_item"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="@id/tv_item" />
<View
android:layout_width="match_parent"
android:layout_height="2dp"
android:background="#A3A3A3"
android:layout_marginTop="3dp"
app:layout_constraintTop_toBottomOf="@id/tv_item" />
</androidx.constraintlayout.widget.ConstraintLayout>
RecyclerView의 레이아웃에 닫기 버튼을 추가로 넣어준다.
이미지는 Vector Asset에서 구했다.
SampleAdapter에 커스텀 리스너 작성
class SampleAdapter: PagingDataAdapter<SampleData, SampleAdapter.SampleViewHolder>(ARTICLE_DIFF_CALLBACK) {
interface CustomListenerInterface {
fun removeListener(position: Int, sampleData: SampleData)
}
private var onRemoveListener: CustomListenerInterface? = null
fun removeListener(pOnClick: CustomListenerInterface) {
this.onRemoveListener = pOnClick
}
inner class SampleViewHolder(private val mBinding : ListItemMainBinding): RecyclerView.ViewHolder(mBinding.root) {
fun bind(listData: SampleData) {
mBinding.ivItemRemove.setOnClickListener {
...
onRemoveListener?.removeListener(bindingAdapterPosition, listData)
}
}
}
...
}
닫기 버튼을 눌렀을 때 Activity에서 처리가 가능하도록 커스텀 리스너를 만든다.
Activity에서 아이템 삭제 / UI 갱신 / 로딩 처리
mAdapter.removeListener(object : SampleAdapter.CustomListenerInterface {
override fun removeListener(position: Int, sampleData: SampleData) {
mViewModel.setItemDelete(sampleData.id)
}
})
Activity에서 닫기 버튼을 클릭했을 때 ViewModel의 삭제 메서드를 호출한다.
/**
* 아이템 삭제 이후 PagingDataAdapter의 refresh를 호출하여 UI 갱신
*/
mViewModel.itemDeleteObserve.observe(this) {
mAdapter.refresh()
}
데이터가 삭제되면 LiveData의 observe를 이용해 PagingDataAdapter의 refresh 메서드를 호출한다.
refresh를 호출하게 되면 PagingData의 새로운 객체로 항목을 만들도록 동작이 수행된다.
이때 PagingSource의 getRefreshKey 메서드가 호출된다.
/**
* 현재 목록을 대체할 새 데이터를 로드할 때 사용
* - anchorPosition : 가장 최근에 액세스한 인덱스
* - closestPageToPosition : anchorPosition을 토대로 가장 가까운 페이지를 다시 호출
*/
override fun getRefreshKey(state: PagingState<Int, SampleData>): Int? {
println("getRefreshKey $state")
return state.anchorPosition?.let { anchorPosition ->
state.closestPageToPosition(anchorPosition)?.prevKey?.plus(1)
?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(1)
}
}
getRefreshKey의 state 값에는 anchorPosition이 있는데, 이 값은 가장 최근에 액세스한 인덱스를 의미한다.
closesPageToPosition은 anchorPosition을 토대로 가장 가까운 페이지를 다시 호출한다.
(처음에 anchorPosition이 리스트의 인덱스인 줄 알았는데, 값을 비교하니 그건 아닌 것 같다.)
/**
* PagingDataAdapter의 로드 상태를 전달받아 refresh가 로딩 상태이면 로딩 화면 띄우도록 처리
*/
lifecycleScope.launch {
mAdapter.loadStateFlow.collect { loadState ->
mBinding.pbMainLoading.isVisible = loadState.refresh is LoadState.Loading
}
}
PagingDataAdapter의 loadStateFlow는 Adapter의 로드 상태를 전달 받는다.
PagingSource의 getRefreshKey 메서드가 호출되면 로드상태를 loadStateFlow로 받을 수 있다.
이것을 사용해서 Activity의 ProgressbBar의 Visible에 값을 부여한다.
위의 코드를 통해 앱이 켜지고 데이터를 처음 로드할 때, 데이터를 삭제할 때 로딩 화면이 뜰 것이다.
(아래는 Activity의 레이아웃)
<?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/rv_main"
android:layout_width="wrap_content"
android:layout_height="350dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/list_item_main"/>
<ProgressBar
android:id="@+id/pb_main_loading"
android:layout_width="64dp"
android:layout_height="64dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
'Android > Paging3' 카테고리의 다른 글
[Android] Paging3에서 데이터 없을 때 처리하기 (0) | 2023.02.20 |
---|---|
[Android] Paging3, 스크롤 시 로딩 화면 추가하기 (0) | 2023.01.18 |
[Android] Paging3 + Room + Flow 사용하기 (1) | 2023.01.16 |
댓글