[Android] Paging3에서 아이템 삭제하기 + 로딩 처리

2023. 1. 19. 09:23·Android/Paging3
반응형
 

[Android] Paging3, 스크롤 시 로딩 화면 추가하기

[Android] Paging3 + Room + Flow 사용하기 [Android] RecyclerView에서 페이징+삭제 처리하기 #2 (with Room) [Android] RecyclerView에서 페이징 처리하기 #1 RecyclerView에서 리스트를 스크롤하다가 어느 순간에 로딩 화면

ogyong.tistory.com

저번에 했던 스크롤 시 로딩 화면을 추가하는 것에 이어서

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>

2초의 delay 부여

전체 코드

반응형

'Android > Paging3' 카테고리의 다른 글

[Android] Paging3에서 데이터 없을 때 처리하기  (0) 2023.02.20
[Android] Paging3, 스크롤 시 로딩 화면 추가하기  (0) 2023.01.18
[Android] Paging3 + Room + Flow 사용하기  (1) 2023.01.16
'Android/Paging3' 카테고리의 다른 글
  • [Android] Paging3에서 데이터 없을 때 처리하기
  • [Android] Paging3, 스크롤 시 로딩 화면 추가하기
  • [Android] Paging3 + Room + Flow 사용하기
O_Gyong
O_Gyong
안드로이드 기술 정리
  • O_Gyong
    O_Gyong's TECH
    O_Gyong
    • 분류 전체보기 (79)
      • Android (56)
        • ADB (4)
        • AddView (1)
        • Architecture (1)
        • Bluetooth (2)
        • BuildConfig (1)
        • Calendar (3)
        • Camera (2)
        • Cipher (1)
        • Compose (1)
        • ConstraintSet (1)
        • DataStore (1)
        • Dialog (1)
        • DrawerLayout (1)
        • Flow (1)
        • Glide (1)
        • MQTT (2)
        • Paging3 (4)
        • Permission (1)
        • SharedPreferences (3)
        • RecyclerView (5)
        • Room (1)
        • Splash (1)
        • TabLayout (2)
        • TextWatcher (1)
        • Update (1)
        • 이슈 처리 (13)
      • Android Studio (5)
      • Firebase (1)
      • Git (3)
      • 작업 일지 (13)
  • 최근 글

  • 인기 글

  • 태그

    in-app update
    kizitonwose
    paging
    GIT
    hilt
    github
    Kotlin
    issue
    Paging3
    CalendarView
    loading
    Android Studio
    recyclerview
    compose
    TabLayout
    CameraX
    ADB
    Navigation
    Pagination
    webview
    Bluetooth
    MQTT
    BLE
    Android
    Andoird
    SharedPreferences
    firebase
    해상도
    Room
    flow
  • 링크

    • GitHub
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • hELLO· Designed By정상우.v4.10.4
O_Gyong
[Android] Paging3에서 아이템 삭제하기 + 로딩 처리
상단으로

티스토리툴바