일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- Kotlin
- 추상 팩토리
- Abstract Factory
- 디자인패턴 #
- 코틀린
- ㅋㅁ
- 빌터패턴
- builderPattern
- Singleton
- 함수형프로그래밍
- 옵저버 패턴
- El
- Observer Pattern
- a
- F
- 디자인패턴
- 팩토리 메소드
- ㅓ
- Design Pattern
- designPattern
- Functional Programming
- r
- PrototypePattern
- 추상팩토리패턴
- factory method
- 싱글톤
- 프로토타입 패턴
- Today
- Total
오늘도 더 나은 코드를 작성하였습니까?
android paging3 basic codelab 정리1 본문
페이징을 해야하는 이유
- ViewModel은 메모리에 로드된 모든 항목을 items StateFlow에 저장한다, 그런데 데이터가 너무 커지면 성능에 영향을 줄 수 있다는 의미이기에 매우 중요한 문제입니다.
- 데이터가 변경되었을 때 List에서 하나 이상 업데이트하는 작업은 List가 클수록 커질수록 비용이 더 많이 생긴다.
Paging 라이브러리는 위 문제를 해결하는 동시에 앱에서 점진적으로 데이터를 가져오는(페이지로 나누기) 일관된 API를 제공
Paging 라이브러리의 핵심 구성요소
PagingSource
- 특정 페이지 쿼리의 데이터 청크를 로드하는 기본 클래스입니다.
- 데이터 레이어의 일부이며 일반적으로 DataSource 클래스에서 노출되고 이후에 ViewModel에서 사용하기 위해 Repository에 의해 노출됩니다.
PagingConfig
- 페이징 동작을 결정하는 매개변수를 정의하는 클래스입니다.
- 페이지 크기, 자리표시자의 사용 설정 여부 등이 포함됩니다.
Pager
- PagingData 스트림을 생성하는 클래스입니다.
- PagingSource에 따라 다르게 실행되며 ViewModel에서 생성
PagingData
- 페이지로 나눈 데이터의 컨테이너입니다.
- 데이터를 새로고침할 때마다 자체 PagingSource로 지원되는 상응하는 PagingData emit 별도로 생성됩니다.
PagingDataAdapter
- RecyclerView에 PagingData를 표시하는 RecyclerView.Adapter 서브클래스입니다.
- PagingDataAdapter는 팩토리 메서드를 사용하여 Kotlin Flow나 LiveData, RxJava Flowable, RxJava Observable 또는 정적 목록에도 연결할 수 있습니다.
- PagingDataAdapter는 내부 PagingData 로드 이벤트를 수신 대기하고 페이지가 로드될 때 UI를 효율적으로 업데이트
페이지로 나누기를 구현할 때 다음 조건을 충족하는지 확인
- UI의 데이터 요청을 올바르게 처리하여 동일한 쿼리에 여러 요청이 동시에 트리거되지 않아야 한다.
- 관리 가능한 양의 가져온 데이터를 메모리에 유지합니다.
- 이미 가져온 데이터를 보완하기 위해 추가 데이터를 가져오라는 요청을 트리거합니다
PagingSource는 추가적 데이터를 가져오는 방법을 지정하여 데이터 소스를 정의합니다.
PagingData 객체는 사용자가 RecyclerView에서 스크롤할 때 생성되는 힌트가 로드되면 PagingSource에서 데이터를 가져옵니다.
Paging key의 유형
- 추가 데이터를 요청하는 데 사용하는 페이지 쿼리 type의 정의입니다.
- 특정 항목의 ID 앞이나 뒤에 기사를 가져옵니다. ID가 정렬되고 증가한다고 보장된다면,
load된 데이터의 유형
- 각 페이지가 반환하는 List<type> 유형
데이터를 가져오는 위치
- 일반적으로 데이터베이스 / 네트워크 리소스 / 미리 페이지로 나누어논 데이터
class ArticlePagingSource : PagingSource<Int, Article>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Article> {
TODO("Not yet implemented")
}
override fun getRefreshKey(state: PagingState<Int, Article>): Int? {
TODO("Not yet implemented")
}
}
load() 함수
- 사용자가 스크롤할 때 표시할 더 많은 데이터를 비동기식으로 가져오기 위해 Paging 라이브러리에서 load() 함수를 호출한다.
- LoadParams 객체에는 다음 항목을 포함하여 로드 작업과 관련된 정보가 저장
load할 페이지의 키(params.key)
- load()가 처음 호출되는 경우 LoadParams.key는 null
- 여기서는 초기 페이지 키를 정의해야 합니다.
- 이 프로젝트에서는 항목 ID를 키로 사용합니다. 초기 페이지 키의 ArticlePagingSource 파일 상단에 STARTING_KEY 상수 0도 추가해 보겠습니다.
load size
- 로드 요청된 항목의 수입니다.
load() 함수의 반환값 LoadResult
LoadResult.Page - 로드에 성공한 경우
LoadResult.Error - 오류가 발생한 경우
LoadResult.Invalid - PagingSource가 더 이상 결과의 무결성을 보장할 수 없으므로 무효화되어야 하는 경우
LoadResult.Page에는 다음과 같은 세 가지 필수 인수
- data: 가져온 항목의 List입니다.
- prevKey: 현재 페이지 앞에 항목을 가져와야 하는 경우 load() 메서드에서 사용하는 키입니다.
- nextKey: 현재 페이지 뒤에 항목을 가져와야 하는 경우 load() 메서드에서 사용하는 키입니다
선택적 인수 두 개도 있습니다.
- itemsBefore: 로드된 데이터 앞에 표시할 자리표시자의 수입니다.
- itemsAfter: 로드된 데이터 뒤에 표시할 자리표시자의 수입니다.
load key는 Article.id 필드입니다.
이를 키로 사용할 수 있는 이유는 기사마다 Article ID가 1씩 증가하기 때문입니다.
상응하는 방향(위/아래 스크롤)으로 로드할 데이터가 더 이상 없는 경우 nextKey(아래방향)또는 prevKey(윗 방향)는 null입니다.
prevKey
- startKey가 STARTING_KEY와 같은 경우 null이 반환됩니다. 이 키 앞에 항목을 더 로드할 수 없기 때문입니다.
- 그 외의 경우에는 목록의 첫 번째 항목을 가져와 앞에 LoadParams.loadSize를 로드하여 STARTING_KEY보다 작은 키가 반환되지 않도록 합니다.
- ensureValidKey() 메서드를 정의
rivate fun ensureValidKey(key: Int) = max(STARTING_KEY, key)
nextKey
- 무한 항목 로드를 지원하므로 range.last + 1
private val firstArticleCreatedTime = LocalDateTime.now()
class ArticlePagingSource : PagingSource<Int, Article>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Article> {
// Start paging with the STARTING_KEY if this is the first load
val start = params.key ?: STARTING_KEY
// Load as many items as hinted by params.loadSize
val range = start.until(start + params.loadSize)
return LoadResult.Page(
data = range.map { number ->
Article(
// Generate consecutive increasing numbers as the article id
id = number,
title = "Article $number",
description = "This describes article $number",
created = firstArticleCreatedTime.minusDays(number.toLong())
)
},
// Make sure we don't try to load items behind the STARTING_KEY
prevKey = when (start) {
STARTING_KEY -> null
else -> ensureValidKey(key = range.first - params.loadSize)
},
nextKey = range.last + 1
)
}
...
}
getRefreshKey()
- Paging 라이브러리가 UI 관련 항목을 새로고침해야 할 때호출됩니다.
- PagingSource의 데이터가 변경되었기 때문입니다.
- PagingSource의 기본 데이터가 변경되었으며 UI에서 업데이트해야 하는 이 상황을 무효화라고 합니다.
- 무효화되면 Paging 라이브러리가 데이터를 새로고침할 새 PagingSource를 만들고 새 PagingData를 내보내 UI에 알립니다.
PagingSource에서 로드할 때는 사용자가 새로고침 후 목록에서 현재 위치를 잃지 않도록
새로운 PagingSource가 로드를 시작해야 하는 키를 제공하기 위해 getRefreshKey()가 호출
Paging 라이브러리에서 무효화가 발생하는 이유는 다음 두 가지 중 하나입니다.
- PagingAdapter에서 refresh()를 호출
- PagingSource에서 invalidate()를 호출
return 값인 key(여기서는 Int)는 LoadParams 인수를 통해 새 PagingSource의 다음 load() 메서드 호출에 전달됩니다.
무효화 후 리스트에서 항목이 이동하지 않도록 하려면 반환된 키가 화면을 채울 만큼 충분한 항목을 로드하도록 해야 합니다.
이렇게 하면 새 항목 집합에 무효화된 데이터에 있던 항목이 포함될 가능성이 커지므로 현재 스크롤 위치를 유지하는 데 도움이 됩니다.
// The refresh key is used for the initial load of the next PagingSource, after invalidation
override fun getRefreshKey(state: PagingState<Int, Article>): Int? {
// In our case we grab the item closest to the anchor position
// then return its id - (state.config.pageSize / 2) as a buffer
val anchorPosition = state.anchorPosition ?: return null
val article = state.closestItemToPosition(anchorPosition) ?: return null
return ensureValidKey(key = article.id - (state.config.pageSize / 2))
}
PagingState.anchorPosition을 이용한다.
UI가 PagingData에서 항목을 읽으려고 하면 특정 index에서 읽으려고 합니다. 데이터를 읽은 경우 이 데이터가 UI에 표시됩니다.
하지만 데이터가 없으면 Paging 라이브러리는 실패한 읽기 요청을 처리하기 위해 데이터를 가져와야 한다는 것을 인식합니다.
읽을 때 데이터를 성공적으로 가져온 마지막 index은 anchorPosition입니다.
새로고침할 때는 anchorPosition에 가장 가까운 Article key를 가져와 load key로 사용합니다.
이렇게 하면 새 PagingSource에서 로드를 다시 시작할 때 가져온 항목 집합에 이미 로드된 항목이 포함되므로 원활하고 일관된 사용자 환경이 보장됩니다.
'Android Jetpack Architecture > Paging3' 카테고리의 다른 글
android paging3 basic codelab 정리2 (0) | 2022.10.10 |
---|---|
REFRESH 배치 (0) | 2021.08.23 |
paging3 race conditions 처리와 remote keys 관리하기 (0) | 2021.08.19 |
RemoteMediator 구현하기 (0) | 2021.08.19 |
네트워크 및 데이터베이스의 페이징. (0) | 2021.08.19 |