Android Paging-3 — что делать с тем, что все элементы загружаются в один момент?

Ссылка скопирована
1 ответ

Вставил код по максимуму, потому что не вижу проблемы - делаю также, как в туториалах, где все работает. Ну приблизительно - там несколько другие адаптеры для Recycler View.
Смотрел на проект с Jetpack Compose (у меня его нет), там data-слой такой же, для presentation есть collectAsLazyPagingItems. Потом смотрел на обычный проект, обновил зависимости, поправил, запустил - прекрасно работает- там запросы к стороннему api, но можно проверить в logcat по "--> GET", и они реально при прокрутке.
Что у меня - у меня свой тестовый бэкенд на nodejs, вроде бы пагинация должна правильно работать - но нет.

В AppealPagingSource сейчас поставил на мету meta = MetaRequest(page, PAGE_SIZE) = page, limit, т.е. мой PAGE_SIZE, а если ставить params.loadSize, то не могу с бэка правильно обработать, - там первый запрос больше, и из-за этого все сбивается, элементы дублируются, если их меньше, чем в 1 запросе.

Но и с PAGE_SIZE в limit, и с params.loadSize на бэк делаются ВСЕ запросы сразу вне зависимости от величины списка на бэке, PAGE_SIZE и тд. Мне удавалось добиться нормальных запросов на МОЙ БЭК только из другого проекта (не который по ссылке, его сложно переписать и я не стал). Но с проекта по ссылке тоже нормально работает, я в логах вижу.
Т.е. еще раз - хочу например ну 1-2 запроса, а получается какое-то такое чудо, это если элементов в списке на бэке 19.

{ limit: 3, page: 1 }     { limit: 3, page: 2 }     { limit: 3, page: 3 }     { limit: 3, page: 4 }     { limit: 3, page: 5 }     { limit: 3, page: 6 }     { limit: 3, page: 7 }     { limit: 3, page: 8 }

{ limit: 3, page: 1 } { limit: 3, page: 2 } { limit: 3, page: 3 } { limit: 3, page: 4 } { limit: 3, page: 5 } { limit: 3, page: 6 } { limit: 3, page: 7 } { limit: 3, page: 8 }

Аналогично

{ limit: 5, page: 1 }     { limit: 5, page: 2 }     { limit: 5, page: 3 }     { limit: 5, page: 4 }     { limit: 5, page: 5 }

{ limit: 5, page: 1 } { limit: 5, page: 2 } { limit: 5, page: 3 } { limit: 5, page: 4 } { limit: 5, page: 5 }

Это было при PAGE_SIZE, а при params.loadSize такое, но это объяснимо константой в библиотеке

{ limit: 15, page: 1 }     { limit: 5, page: 2 }     { limit: 5, page: 3 }     { limit: 5, page: 4 }     { limit: 5, page: 5 }

{ limit: 15, page: 1 } { limit: 5, page: 2 } { limit: 5, page: 3 } { limit: 5, page: 4 } { limit: 5, page: 5 }

Также пробовал различные комбинации во ViewModel/View - LiveData+MutableLiveData+observe(у меня во всем приложении так), делать collect во ViewModel, делать collectLatest и миллион всего. Признаю, плохо разбираюсь в этом, но что есть - то и имеем.

Как обрабатываю на бэке:

function paginateAppeal(meta) {     console.log(meta);     const { page, limit } = meta;     const startIndex = (page - 1) * limit;     const endIndex = Math.min(startIndex + limit, appealList.length);     const paginatedAppealList = appealList.slice(startIndex, endIndex);     return paginatedAppealList; }  app.post('/api/event/get-events', (req, res) => {     const eventList = {};      if (req.body.events.includes("Appeal")) {         eventList.appeals = paginateAppeal(req.body.meta);         // console.log(paginateAppeal(req.body.meta))     } else if (req.body.events.includes("Notification") && req.body.events.includes("Voting")) {         eventList.notifications = notificationList;         eventList.votings = votingList;     }     res.json(eventList); });

function paginateAppeal(meta) { console.log(meta); const { page, limit } = meta; const startIndex = (page - 1) * limit; const endIndex = Math.min(startIndex + limit, appealList.length); const paginatedAppealList = appealList.slice(startIndex, endIndex); return paginatedAppealList; } app.post('/api/event/get-events', (req, res) => { const eventList = {}; if (req.body.events.includes("Appeal")) { eventList.appeals = paginateAppeal(req.body.meta); // console.log(paginateAppeal(req.body.meta)) } else if (req.body.events.includes("Notification") && req.body.events.includes("Voting")) { eventList.notifications = notificationList; eventList.votings = votingList; } res.json(eventList); });

Полный код:

// paging implementation("androidx.paging:paging-common-ktx:3.2.1") implementation("androidx.paging:paging-runtime-ktx:3.2.1")   interface EventApiService {     @POST("event/get-events")     suspend fun listEvent(         @Body request: EventListRequest     ): EventListResponse      @POST("event/update-voting")     suspend fun updateVoting(         @Body request: VotingUpdateRequest     ): VoteListItemResponse      companion object {         private const val PAGE_SIZE = 3     } }  class AppealRepositoryImpl(     private val eventApiService: EventApiService ): AppealRepository {      @OptIn(ExperimentalPagingApi::class)     override fun listAppeal(): Flow<PagingData<AppealListItemResponse>> {         return Pager(             config = PagingConfig(                 pageSize = PAGE_SIZE             ),             pagingSourceFactory = { AppealPagingSource(eventApiService) }         ).flow     }  }  @ExperimentalPagingApi class AppealPagingSource(private val eventApiService: EventApiService) : PagingSource<Int, AppealListItemResponse>() {     companion object {         const val STARTING_PAGE_INDEX = 1     }      override suspend fun load(params: LoadParams<Int>): LoadResult<Int, AppealListItemResponse> {         val page = params.key ?: STARTING_PAGE_INDEX         return try {             val events = eventApiService.listEvent(                 EventListRequest(                     userId = 1,                     events = listOf(EventType.Appeal),                     meta = MetaRequest(page, PAGE_SIZE)                 )             )             LoadResult.Page(                 data = events.appeals,                 prevKey = if (page == STARTING_PAGE_INDEX) null else page - 1,                 nextKey = if (events.appeals.isEmpty()) null else page + 1             )         } catch (exception: IOException) {             return LoadResult.Error(exception)         } catch (exception: HttpException) {             return LoadResult.Error(exception)         }     }      override fun getRefreshKey(state: PagingState<Int, AppealListItemResponse>): Int? {         return state.anchorPosition?.let { anchorPosition ->             state.closestPageToPosition(anchorPosition)?.prevKey?.plus(1)                 ?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(1)         }     } }  @HiltViewModel class AppealListViewModel @Inject constructor(     appealRepository: AppealRepository,     val appealMapper: AppealMapper ) : ViewModel() {     private val dataSource = appealRepository.listAppeal()      @OptIn(ExperimentalCoroutinesApi::class)     val appealList: Flow<PagingData<AppealUiModel>> by lazy {         dataSource             .mapLatest { pagingData -> pagingData.map { appealMapper.appealToUi(it) } }             .cachedIn(viewModelScope)     }  }  @AndroidEntryPoint class AppealListView : Fragment() {     private lateinit var appealListAdapter: CustomPagingAdapter<AppealUiModel, AppealListItemBinding>      override fun onViewCreated(view: View, savedInstanceState: Bundle ?) {         super.onViewCreated(view, savedInstanceState)         lifecycleScope.launch {             viewModel.appealList.collectLatest {                 appealListAdapter.submitData(it)             }         }     } }  open class CustomPagingAdapter<T: Adaptive, B : ViewDataBinding>(     private val itemBindingInflater: (LayoutInflater, ViewGroup, Boolean) -> B,     private val setBinding: (B, T) -> Unit,     private val onItemClick: ((item: T) -> Unit)? ) : PagingDataAdapter<T, CustomPagingAdapter<T, B>.ItemViewHolder>(DiffCallback()) {      override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {         val layoutInflater = LayoutInflater.from(parent.context)         val binding = itemBindingInflater(layoutInflater, parent, false)         return ItemViewHolder(binding)     }      override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {         val item = getItem(position)         item?.run {             holder.bind(this)             holder.itemView.setOnClickListener {                 onItemClick?.invoke(this)             }         }     }      inner class ItemViewHolder(val binding: B) : RecyclerView.ViewHolder(binding.root) {         fun bind(item: T) {             setBinding(binding, item)             binding.executePendingBindings()         }     }      class DiffCallback<T: Adaptive> : DiffUtil.ItemCallback<T>() {         override fun areItemsTheSame(oldItem: T, newItem: T): Boolean {             return oldItem.id == newItem.id         }          override fun areContentsTheSame(oldItem: T, newItem: T): Boolean {             return oldItem.areContentsTheSame(newItem)         }     } }

// paging implementation("androidx.paging:paging-common-ktx:3.2.1") implementation("androidx.paging:paging-runtime-ktx:3.2.1") interface EventApiService { @POST("event/get-events") suspend fun listEvent( @Body request: EventListRequest ): EventListResponse @POST("event/update-voting") suspend fun updateVoting( @Body request: VotingUpdateRequest ): VoteListItemResponse companion object { private const val PAGE_SIZE = 3 } } class AppealRepositoryImpl( private val eventApiService: EventApiService ): AppealRepository { @OptIn(ExperimentalPagingApi::class) override fun listAppeal(): Flow<PagingData<AppealListItemResponse>> { return Pager( config = PagingConfig( pageSize = PAGE_SIZE ), pagingSourceFactory = { AppealPagingSource(eventApiService) } ).flow } } @ExperimentalPagingApi class AppealPagingSource(private val eventApiService: EventApiService) : PagingSource<Int, AppealListItemResponse>() { companion object { const val STARTING_PAGE_INDEX = 1 } override suspend fun load(params: LoadParams<Int>): LoadResult<Int, AppealListItemResponse> { val page = params.key ?: STARTING_PAGE_INDEX return try { val events = eventApiService.listEvent( EventListRequest( userId = 1, events = listOf(EventType.Appeal), meta = MetaRequest(page, PAGE_SIZE) ) ) LoadResult.Page( data = events.appeals, prevKey = if (page == STARTING_PAGE_INDEX) null else page - 1, nextKey = if (events.appeals.isEmpty()) null else page + 1 ) } catch (exception: IOException) { return LoadResult.Error(exception) } catch (exception: HttpException) { return LoadResult.Error(exception) } } override fun getRefreshKey(state: PagingState<Int, AppealListItemResponse>): Int? { return state.anchorPosition?.let { anchorPosition -> state.closestPageToPosition(anchorPosition)?.prevKey?.plus(1) ?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(1) } } } @HiltViewModel class AppealListViewModel @Inject constructor( appealRepository: AppealRepository, val appealMapper: AppealMapper ) : ViewModel() { private val dataSource = appealRepository.listAppeal() @OptIn(ExperimentalCoroutinesApi::class) val appealList: Flow<PagingData<AppealUiModel>> by lazy { dataSource .mapLatest { pagingData -> pagingData.map { appealMapper.appealToUi(it) } } .cachedIn(viewModelScope) } } @AndroidEntryPoint class AppealListView : Fragment() { private lateinit var appealListAdapter: CustomPagingAdapter<AppealUiModel, AppealListItemBinding> override fun onViewCreated(view: View, savedInstanceState: Bundle ?) { super.onViewCreated(view, savedInstanceState) lifecycleScope.launch { viewModel.appealList.collectLatest { appealListAdapter.submitData(it) } } } } open class CustomPagingAdapter<T: Adaptive, B : ViewDataBinding>( private val itemBindingInflater: (LayoutInflater, ViewGroup, Boolean) -> B, private val setBinding: (B, T) -> Unit, private val onItemClick: ((item: T) -> Unit)? ) : PagingDataAdapter<T, CustomPagingAdapter<T, B>.ItemViewHolder>(DiffCallback()) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder { val layoutInflater = LayoutInflater.from(parent.context) val binding = itemBindingInflater(layoutInflater, parent, false) return ItemViewHolder(binding) } override fun onBindViewHolder(holder: ItemViewHolder, position: Int) { val item = getItem(position) item?.run { holder.bind(this) holder.itemView.setOnClickListener { onItemClick?.invoke(this) } } } inner class ItemViewHolder(val binding: B) : RecyclerView.ViewHolder(binding.root) { fun bind(item: T) { setBinding(binding, item) binding.executePendingBindings() } } class DiffCallback<T: Adaptive> : DiffUtil.ItemCallback<T>() { override fun areItemsTheSame(oldItem: T, newItem: T): Boolean { return oldItem.id == newItem.id } override fun areContentsTheSame(oldItem: T, newItem: T): Boolean { return oldItem.areContentsTheSame(newItem) } } }

Дополнительно:

Ответы:

Сам спросил - сам понял...у меня Recycler View был вложен в NestedScrollView

Нужно решить такую задачу?

Опишите проблему, и специалист поможет с настройкой, исправлением ошибки или доработкой сайта. Подберём понятный план работ без лишней переписки.

Заказать помощь
Лучший ответ
1
Никита Орлов Ответ

Для того чтобы решить проблему с тем, что все элементы загружаются в один момент при использовании Android Paging-3, необходимо настроить стратегию загрузки данных.

Android Paging-3 предоставляет различные стратегии загрузки данных, которые позволяют более гибко управлять процессом загрузки элементов. Одной из таких стратегий является использование `PagingConfig` с параметрами `pageSize`, `prefetchDistance` и `enablePlaceholders`.

Например, чтобы загружать элементы постранично, вы можете задать определенный размер страницы с помощью параметра `pageSize`. Это позволит загружать только определенное количество элементов за раз, что уменьшит нагрузку на приложение и повысит производительность.

Также вы можете использовать параметр `prefetchDistance`, который позволяет загружать элементы заранее, до того как они понадобятся. Это поможет избежать ситуации, когда все элементы загружаются в один момент.

Если вы хотите отобразить заглушки вместо элементов, которые еще не загружены, вы можете включить параметр `enablePlaceholders`. Это позволит показывать заглушки вместо элементов, что улучшит пользовательский опыт.

Пример использования `PagingConfig` с заданием параметров:

PagingConfig config = new PagingConfig(
    pageSize = 10,
    prefetchDistance = 20,
    enablePlaceholders = true
);

PagingConfig config = new PagingConfig( pageSize = 10, prefetchDistance = 20, enablePlaceholders = true );

С помощью правильной настройки стратегии загрузки данных в Android Paging-3 вы сможете избежать ситуации, когда все элементы загружаются в один момент, и обеспечить более плавную работу вашего приложения.

Другие ответы (0)

Пока нет других ответов. Будьте первым, кто поможет автору.

Ответить на вопрос

комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Вам также может быть интересно