Paging 3

나고수·2022년 3월 13일
0

1일1공부

목록 보기
15/68

공식문서
참고블로그

//MyPagingSource.kt
private const val GITHUB_STARTING_PAGE_INDEX = 1

// PagingSource - 데이터 소스를 정의하고 이 소스에서 데이터를 가져오는 방법을 정의
class GithubPagingSource(
    private val service: GithubService,
    private val query: String
) : PagingSource<Int, Repo>() { //인덱스를 구분하는 값(보통 1,2,3페이지 등등 int) , 로드할 아이템의 유형

    //load 함수는 사용자가 스크롤 할 때마다 데이터를 비동기적으로 가져온다.
    //loadParams : 로드 작업과 관련된 정보
    //LoadResult : 로드 결과 (LoadResult.Page: 로드에 성공한 경우 . 데이터와 이전 다음 페이지 Key가 포함 / LoadResult.Error: 오류가 발생한 경우)
    override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Repo> {
        val position = params.key ?: GITHUB_STARTING_PAGE_INDEX //로드할 페이지의 키==현재 페이지 인덱스.
        // 로드가 처음 호출되는 경우에는 LoadParams.key가 null입니다. 그렇기 때문에 (1페이지 부터 로드하고 싶다면) 1 을 넣어줍니다.
        val apiQuery = query + IN_QUALIFIER
        return try {
            val response = service.searchRepos(
                apiQuery,
                position,
                params.loadSize
            ) //params.loadSize - 가져올 데이터의 갯수를 관리한다.
            //API에 따라 limit(한 번에 보여줄 데이터의 수 - params.loadsize), offset==cursor(데이터의 인덱스 - position) 등으로 페이징 처리 되어 있습니다.

            val repos = response.items
            val nextKey = if (repos.isEmpty()) {
                null //네트워크 응답에 성공했지만 목록이 비어 있는 경우에는 로드할 데이터가 없는 것으로 간주할 수 있습니다. 따라서 nextKey가 null일 수 있습니다.
            } else {
                // initial load size = 3 * NETWORK_PAGE_SIZE
                // ensure we're not requesting duplicating items, at the 2nd request
                position + (params.loadSize / NETWORK_PAGE_SIZE)
            }
            //리턴값
            LoadResult.Page(
                data = repos, //로드된 데이터
                prevKey = if (position == GITHUB_STARTING_PAGE_INDEX) null else position - 1,
                nextKey = nextKey
            )
        } catch (exception: IOException) {
            return LoadResult.Error(exception)
        } catch (exception: HttpException) {
            return LoadResult.Error(exception)
        }
    }

    //스와이프 Refresh나 데이터 업데이트 등으로 현재 목록을 대체할 새 데이터를 로드할 때 사용된다.
    //PagingData는 Component에서 설명한 것처럼 새로고침 될 때마다 상응하는 PagingData를 생성해야한다.
    //즉, 수정이 불가능하고 새로운 인스턴스를 만들어야한다.
    //가장 최근에 접근한 인덱스인 anchorPosition으로 주변 데이터를 다시 로드한다.
    override fun getRefreshKey(state: PagingState<Int, Repo>): Int? {

        return state.anchorPosition?.let { anchorPosition ->
            state.closestPageToPosition(anchorPosition)?.prevKey?.plus(1)
                ?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(1)
        }
    }

}
//MyRepository.kt
class MyRepository(private val service: GithubService) {

    fun getSearchResultStream(query: String): Flow<PagingData<Repo>> {//flow : 코루틴에서 리액티브 프로그래밍(데이터가 변경 될 때 이벤트를 발생시켜 데이터를 계속해서 전달하도록 하는 방식)을 구현하기 위해 사용합니다.
        return Pager(
            //PagingConfig 클래스는 로드 대기 시간, 초기 로드의 크기 요청 등 PagingSource에서 콘텐츠를 로드하는 방법에 관한 옵션을 설정합니다.
            //정의해야 하는 유일한 필수 매개변수는 각 페이지에 로드해야 하는 항목 수를 가리키는 페이지 크기입니다.
            config = PagingConfig(
                pageSize = NETWORK_PAGE_SIZE, //각 페이지에 로드해야 하는 항목 수
                //PagingConfig.pageSize는 여러 화면의 항목이 포함될 만큼 충분히 커야 합니다. 페이지가 너무 작으면 페이지의 콘텐츠가 전체 화면을 가리지 않기 때문에 목록이 깜박일 수 있습니다. 
                //페이지 크기가 클수록 로드 효율이 좋지만 목록이 업데이트될 때 지연 시간이 늘어날 수 있습니다. 
                enablePlaceholders = false // 플레이스 홀더 사용 여부 사용하면 아직 로드되지 않은 데이터에 대해 null 값이 들어간 뷰가 만들어 진다.
            ),
            pagingSourceFactory = { GithubPagingSource(service, query) } //pagingSource 인스턴스 생성
        ).flow
    }

    companion object {
        const val NETWORK_PAGE_SIZE = 50
    }
}
//SearchRepositoriesViewModel.kt
class SearchRepositoriesViewModel(private val repository: GithubRepository) : ViewModel() {

    fun searchRepo(queryString: String): Flow<PagingData<Repo>> {
        val newResult: Flow<PagingData<Repo>> = repository.getSearchResultStream(queryString)
            .cachedIn(viewModelScope) //cachedIn(viewModelScope)를 사용하여 캐싱을 해줄 수 있습니다.
        return newResult
    }
}
//ReposAdapter.kt
// PagingData 콘텐츠가 로드될 때마다 PagingDataAdapter에서 알림을 받은 다음 RecyclerView에 업데이트하라는 신호를 보냅니다.
class ReposAdapter : PagingDataAdapter<Repo, RepoViewHolder>(DIFF_CALLBACK) {
// body는 recyclerviewAdapter처럼 사용하면 된다.
}
//MyActivity.kt
private var searchJob: Job? = null

private fun search(query: String) {
//Flow<PagingData>를 사용하려면 새 코루틴을 실행해야 합니다. 이 작업은 활동이 다시 생성될 때 요청을 취소하는 lifecycleScope에서 실행됩니다.
   // Make sure we cancel the previous job before creating a new one
   searchJob?.cancel()
   searchJob = lifecycleScope.launch {
       viewModel.searchRepo(query).collectLatest {
           adapter.submitData(it)
       }
   }
}
profile
되고싶다

0개의 댓글