마스터Q&A 안드로이드는 안드로이드 개발자들의 질문과 답변을 위한 지식 커뮤니티 사이트입니다. 안드로이드펍에서 운영하고 있습니다. [사용법, 운영진]

paging3에서 받아오는 데이터가 없을때 loading 그만하는법

0 추천
안녕하세요 paging3으로 게시물 받아오는거 구현하고 있습니다. 만약 게시물이 없을때 Loading을 멈추고싶은데 서버랑 통신을 게속 하더라고요 (okHTTP에 로그 찍힙니다.) 어떻게 하면 될까요?
hifl (290 포인트) 님이 9월 4일 질문
hifl님이 9월 5일 수정

1개의 답변

0 추천
 
채택된 답변

그건 아마도 PagingSource 에 page number 처리를 처리를 제대로 하지 않고 있기 때문일 것 같습니다. 이게 원인이라면, 이건 refresh 기능에도 영향을 미칠 겁니다. 

 

class MoviePagingSource(
    val movieApiService: MovieApiService,
) : PagingSource<Int, DataType>() {
  
  override suspend fun load(params: LoadParams<Int>): LoadResult<Int, DataType> {
    try {
      // Start refresh at page 1 if undefined.
      val nextPage = params.key ?: 1
      val response = ...

      
      return LoadResult.Page(
        data = response.movies,
        prevKey = if (nextPage == 1) null else nextPage - 1,
        nextKey = if (noMoreData) null else response.page + 1
      )
    } catch (e: Exception) {
        return LoadResult.Error(e)
    }
  }
}

 

위의 예제 코드에서 마지막에 LoadResult.Page 부분 처리가 중요합니다. 만약 nextKey를 제대로 처리하지 않으면 님이 말씀하신 것과 같은 증상이 일어납니다. 더이상 처리할 데이터가 없다면 null을 리턴하시면 됩니다.

spark (79,410 포인트) 님이 9월 5일 답변
hifl님이 9월 6일 채택됨
그런가요'? 위의 예제를 참고해서 다시햐보겠습니다. 님 말대로 nextKey에 page.plus(20) 만해주고있었는데.. 흠..
와 진짜네요 감사합니다 !!
nextKey 는 페이지번호에요. 혹시 nextKey = currentPageNo.plus(20) 이렇게 하고 계시다면 잘못된 로직입니다. currentPageNo.inc() 로 하나만 증가시켜 주시는게 맞아요.
params.key ?: 1 를 1 대신 20으로 하고있었는데 1로 바꿔줘야겠네요 그쵸?
한번에 게시물을 20개씩 받아와도 1로 해줘야하나요??
그건 Pager에서 파라미터 넘길 때 pageSize를 세팅하도록 되어 있어요. API 문서를 확인해 보세요.
params.key의 key페이지 번호이므로, 서버의 응답값에 따라 적절하게 처리해주셔야 합니다.
다음페이지에 데이터가 존재하지 않으면 null 있으면 현재페이지번호+1. 아마 page에 관련된 정보(일반적으로는 전체 데이터 갯수, 현재 페이지 번호, 페이지 크기 등) 가 리턴될 겁니다. 이걸 기반으로 로직을 구현하시면 됩니다.
네 pageSize 20으로 했어요

val acceptData = Pager(

        PagingConfig(
            pageSize = 20,
        )
    ) {
        AcceptPagingSource(
            adminApi,
            token.value.toString(),
            cursor.value.toString(),

            )

    }.flow
        .cachedIn(viewModelScope)
class AcceptPagingSource @Inject constructor(
    private val adminApi: AdminApi,
    private val token: String,
    private val cursor: String?,
//    private val sampleRepository: SampleRepository,


) : PagingSource<Int, Admin.Accept>() {
    companion object {
        const val TAG = "PostPagingSource"
        const val UNSPLASH_STARTING_PAGE_INDEX = 20

    }

    override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Admin.Accept> {
        return try {
//            val page = sampleRepository.getNextPage(lastSeenId = params.key ?: 0)
            val page = params.key ?: 0


//           val totalCount = adminApi.getCount(token)
//
//
//
//            val countData=totalCount.body()?.filter {it._id== ACCEPTED }.apply{
//
//
//
//            }
//            Log.d(TAG, "totalCount accept: $countData ")

            Log.d(TAG, "page size   : ${page} loadSize : ${params.loadSize}")

            val response = adminApi.getAcceptPost(token, page, cursor, ACCEPTED)


            val data = response.body()?.posts ?: emptyList()
            data.sortedBy { it.number }

            LoadResult.Page(
                data = data.sortedByDescending { it.number },
                prevKey = null,
                nextKey =  if (data.isEmpty()) null else page.inc(),
            )


        } catch (e: Exception) {
            Log.d(TAG, "error: $e")
            return LoadResult.Error(e)
        } catch (e: HttpException) {
            Log.d(TAG, "HttpException: $e")
            return LoadResult.Error(e)
        } catch (e: IOException) {
            Log.d(TAG, "IOException: $e")
            return LoadResult.Error(e)
        }


    }


    override fun getRefreshKey(state: PagingState<Int, Admin.Accept>): Int? {
        Log.d(TAG, "getRefreshKey:${state.anchorPosition} ")
        return state.anchorPosition?.let { anchorPosition ->
            state.closestPageToPosition(anchorPosition)?.prevKey?.plus(20)
                ?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(20)
        }

    }

}


이렇게 바꿧습니다
두가지 정도를 좀 더 테스트를 해보시는 게 좋을 것같아요.
1. 데이터 정렬하는 부분
페이징 처리이므로, 서버에서 정렬된 데이터가 리턴되는게 아니라 로컬에서 하게될 때, 거기에 따른 문제가 있는지. 왜냐하면 이 두 데이터는 다를 수 있는 가능성이 보입니다.

2. 페이지번호 처리가 여전히 의심스러운 부분이 있습니다.
val page = params.key ?: 0

nextKey =  if (data.isEmpty()) null else page.inc()
위 두 코드에서 page값이 제대로 세팅이 될지 좀 의문이 드네요. 디버깅해서 확인해 보세요. 서버 API가 현재 페이지 번호를 리턴한다면 그걸 사용하셔야 해요.
보니까 서버의 count값이 0만 뜨네요 .. 문제가 있는것 같아요
서버 API가 현재 페이지 번호를 리턴 못하네요..
그럼 서버쪽 API부터 고쳐나가면 되겠네요. 혹 서버 API가 업데이트 되기 전에 테스트 해보시려면 WireMock 같은 프로그램을 로컬로 돌리시고 가짜 응답 파일 제공해주면 가능합니다. 아니면 Burp나 Charlse 같은 proxy 툴로 응답을 바꿔치기 할 수도 있구요.
val page = params.key ?: 1 여기서 page가 1더해졌을 때 state가 Loading이 되는건가요?
LoadingState 의 변경은 PagingAapdter에서 refresh, retry또는 adapter의 마지막부분에서 scroll up을 할 때 변경될 겁니다.  params.key는 다음 페이지를 로딩하기 위해서 필요한 값입니다. 로딩할 페이지가 존재한다면 로딩상태로 바뀌겠죠.
...