경험의 기록

2021.06.15 - [안드로이드/AAC, MVVM] - [Android] Paging 3 라이브러리 사용해보기 - (1) 데이터 페이징하기

 

[Android] Paging 3 라이브러리 사용해보기 - (1) 데이터 페이징하기

❓ Paging이란 ▶ 로컬 저장소에서나 네트워크를 통해 대규모 데이터 세트의 데이터 페이지를 로드할 때 일정한 덩어리로 쪼개서 로드하는 것 인터넷의 페이지를 생각하면 된다. 🔴 Paging3 아키텍

hanyeop.tistory.com

에서 이어지는 글입니다.

 

 


addLoadStateListenerLoadStateAdapter 를 사용하여

데이터의 로딩 상태를 얻어 UI에 나타낼 수 있다.

 

로드 상태 표시

LoadState 객체를 통해 상태를 나타내며, 세가지 상태가 존재한다.

 

  • LoadState.NotLoading : 활성 로드 작업이 없고 오류가 없음
  • LoadState.Loading : 활성 로드 작업이 있음
  • LoadState.Error : 오류가 있음

 

LoadStateListener 사용하기

 

<?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">

    <EditText
        android:id="@+id/editTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="24dp"
        android:ems="10"
        android:inputType="number"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.497"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="28dp"
        android:text="Button"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.498"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/editTextView" />

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/button">

    </androidx.recyclerview.widget.RecyclerView>

    <ProgressBar
        android:id="@+id/progressBar"
        style="?android:attr/progressBarStyle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:visibility="gone"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        tools:visibility="visible" />

    <Button
        android:id="@+id/retryButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button"
        android:visibility="gone"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        tools:visibility="visible" />

    <TextView
        android:id="@+id/errorText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="데이터 로드에 실패하였습니다."
        android:textSize="24sp"
        android:visibility="gone"
        app:layout_constraintBottom_toTopOf="@+id/retryButton"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="1.0"
        tools:visibility="visible" />

    <TextView
        android:id="@+id/emptyText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="데이터가 없습니다."
        android:textSize="24sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:visibility="gone"
        tools:visibility="visible"/>

</androidx.constraintlayout.widget.ConstraintLayout>

로드 상태를 나타낼 수 있는 xml을 만들어놓고

// 로딩 상태 리스너
        myAdapter.addLoadStateListener { combinedLoadStates ->
            binding.apply {
                // 로딩 중 일 때
                progressBar.isVisible = combinedLoadStates.source.refresh is LoadState.Loading

                // 로딩 중이지 않을 때 (활성 로드 작업이 없고 에러가 없음)
                recyclerView.isVisible = combinedLoadStates.source.refresh is LoadState.NotLoading

                // 로딩 에러 발생 시
                retryButton.isVisible = combinedLoadStates.source.refresh is LoadState.Error
                errorText.isVisible = combinedLoadStates.source.refresh is LoadState.Error

                // 활성 로드 작업이 없고 에러가 없음 & 로드할 수 없음 & 개수 1 미만 (empty)
                if(combinedLoadStates.source.refresh is LoadState.NotLoading
                    && combinedLoadStates.append.endOfPaginationReached
                    && myAdapter.itemCount < 1){
                    recyclerView.isVisible = false
                    emptyText.isVisible = true
                }
                else{
                    emptyText.isVisible = false
                }
            }
        }

addLoadStateListener 로

상태에 따른 UI visible 여부를 선언할 수 있다.

 

// 다시 시도하기 버튼
        binding.retryButton.setOnClickListener {
            myAdapter.retry()
        }

또한 retry() 를 사용하면 데이터를 다시 불러올 수 있다.

 

로딩중일때 프로그래스바가 뜨는것을 확인할 수 있고

 

로드 실패 ( 네트워크 미연결 등) 되었을 때 화면이 뜨는 것을 확인할 수 있으며

 

uiserId아이템이 10까지만 있기때문에

없는 데이터를 호출했을 때 데이터가 없다는 화면이 뜨는 것을 확인할 수 있다.

 

 

LoadStateAdapter 사용하기

LoadStateAdapter 으로 header와 footer를 설정할 수 있다.

<?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="wrap_content"
    android:gravity="center_horizontal"
    android:orientation="vertical"
    android:padding="10dp">

    <ProgressBar
        android:id="@+id/progressBar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/errorText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:text="데이터 로드 실패"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/retryButton" />

    <Button
        android:id="@+id/retryButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:text="다시 시도하기"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/progressBar" />
</androidx.constraintlayout.widget.ConstraintLayout>

헤더와 푸터로 사용할 레이아웃을 만들어주고

 

class MyLoadStateAdapter(private val retry: () -> Unit)
    : LoadStateAdapter<MyLoadStateAdapter.LoadStateViewHolder>() {


    override fun onBindViewHolder(
        holder: MyLoadStateAdapter.LoadStateViewHolder,
        loadState: LoadState
    ) {
        holder.bind(loadState)
    }

    override fun onCreateViewHolder(
        parent: ViewGroup,
        loadState: LoadState
    ): MyLoadStateAdapter.LoadStateViewHolder {
        val binding = LoadStateBinding.inflate(LayoutInflater.from(parent.context),parent,false)
        return LoadStateViewHolder(binding)
    }

    inner class LoadStateViewHolder(private val binding: LoadStateBinding) :
        RecyclerView.ViewHolder(binding.root) {

        init {
            // 버튼 클릭 시 Fragment 에서 받아온 동작 호출 (retry)
            binding.retryButton.setOnClickListener {
                retry.invoke()
            }
        }

        fun bind(loadState : LoadState){
            binding.apply {
                progressBar.isVisible = loadState is LoadState.Loading
                retryButton.isVisible = loadState !is LoadState.Loading
                errorText.isVisible = loadState !is LoadState.Loading
            }
        }
    }
}

LoadStateAdapter 를 상속받은 클래스를 추가해준다.

여기서도 동일하게 LoadState 에 따른 UI를 지정해주면 된다.

 

파라미터는 람다식으로 함수를 작성하여

binding.apply {
            recyclerView.setHasFixedSize(true) // 사이즈 고정
            // header, footer 설정
            recyclerView.adapter = myAdapter.withLoadStateHeaderAndFooter(
                header = MyLoadStateAdapter { myAdapter.retry() },
                footer = MyLoadStateAdapter { myAdapter.retry() }
            )
        }

사용하는 곳에서 retry() 함수를 받아온다.

 

LoadResult.Page(
                data = post!!,
                prevKey = if (position == STARTING_PAGE_INDEX) null else position - 1,
                nextKey = position + 1 // 계속 반복되게 해둠
            )

페이지를 계속해서 불러오기 위해 nextKey를 변경하였다.

 

데이터를 다시 불러와야 할때 네트워크가 꺼져있다면

하단에서 잘 표시해주는 것을 볼 수 있고

 

maxsize를 20으로 해두었기 때문에

네트워크를 끄고 위로 스크롤하여 20개 이상의 데이터를 불러오면

상단에서 로드 실패를 잘 표시해주는 것을 볼 수 있다.

 

 

 

https://github.com/HanYeop/AndroidStudio-Practice2/tree/master/PagingEx

 

HanYeop/AndroidStudio-Practice2

(2021.05.20~) 안드로이드 학습 내용 저장소. Contribute to HanYeop/AndroidStudio-Practice2 development by creating an account on GitHub.

github.com

 

 

 

 

 

 

 

 

반응형

공유하기

facebook twitter kakaoTalk kakaostory naver band
loading