@yggthris

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

Вставил код по максимуму, потому что не вижу проблемы - делаю также, как в туториалах, где все работает. Ну приблизительно - там несколько другие адаптеры для 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: 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 }


Также пробовал различные комбинации во 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);
});


Полный код:

// 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)
        }
    }
}
  • Вопрос задан
  • 44 просмотра
Пригласить эксперта
Ответы на вопрос 1
@yggthris Автор вопроса
Сам спросил - сам понял...у меня Recycler View был вложен в NestedScrollView
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы