[英]Paging with Jetpack Compose cause infinite retrofit calls
我正在嘗試使用 paging3 和 paging-compose 為 TMDB API 實現分頁。 這里的事實來源是數據庫,api 調用由遠程調解器處理。
存儲庫:
class Repository @Inject constructor(
val database: Database,
private val apiService: ApiService
){
@ExperimentalPagingApi
fun movies(): Flow<PagingData<Movie>> = Pager(
config = PagingConfig(pageSize = 20),
remoteMediator = MovieRemoteMediator(database,apiService),
){
database.movieDao().pagedTopRated()
}.flow
}
遠程調解器:
@ExperimentalPagingApi
class MovieRemoteMediator(
private val database: Database,
private val networkService: ApiService
) : RemoteMediator<Int, Movie>() {
override suspend fun load(loadType: LoadType, state: PagingState<Int, Movie>): MediatorResult {
val page:Int = when(loadType){
LoadType.REFRESH -> {
val remoteKeys = getRemoteKeyClosestToCurrentPosition(state)
remoteKeys?.nextKey?.minus(1) ?: 1
}
LoadType.PREPEND -> {
val remoteKeys = getRemoteKeyForFirstItem(state)
val prevKey = remoteKeys?.prevKey
?: return MediatorResult.Success(endOfPaginationReached = remoteKeys != null)
prevKey
}
LoadType.APPEND -> {
val remoteKeys = getRemoteKeyForLastItem(state)
val nextKey = remoteKeys?.nextKey
?: return MediatorResult.Success(endOfPaginationReached = remoteKeys != null)
nextKey
}
}
return try {
val response
: MovieResponse = networkService.getTopRatedMovies(page)
val toInsert: MutableList<Movie> = mutableListOf();
for (i in response.results)
toInsert.add(i.mapToMovie());
val endOfPaginationReached = response.page + 1 > response.totalPages
database.withTransaction {
if (loadType == LoadType.REFRESH) {
database.movieKeyDao().clearRemoteKeys()
database.movieDao().clearMovies()
}
val prevKey = if (page == 1) null else page - 1
val nextKey = if (endOfPaginationReached) null else page + 1
val keys = response.results.map {
MovieKey(movieId = it.id, prevKey = prevKey, nextKey = nextKey)
}
database.movieDao().insertMovies(toInsert)
database.movieKeyDao().insertAll(keys)
}
MediatorResult.Success(
endOfPaginationReached = endOfPaginationReached
)
} catch (e: IOException) {
MediatorResult.Error(e)
} catch (e: HttpException) {
MediatorResult.Error(e)
}
}
private suspend fun getRemoteKeyForLastItem(state: PagingState<Int, Movie>): MovieKey? {
// Get the last page that was retrieved, that contained items.
// From that last page, get the last item
return state.pages.lastOrNull() { it.data.isNotEmpty() }?.data?.lastOrNull()
?.let { repo ->
// Get the remote keys of the last item retrieved
database.movieKeyDao().remoteKeysMovieId(repo.id)
}
}
private suspend fun getRemoteKeyForFirstItem(state: PagingState<Int, Movie>): MovieKey? {
// Get the first page that was retrieved, that contained items.
// From that first page, get the first item
return state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull()
?.let { repo ->
// Get the remote keys of the first items retrieved
database.movieKeyDao().remoteKeysMovieId(repo.id)
}
}
private suspend fun getRemoteKeyClosestToCurrentPosition(
state: PagingState<Int, Movie>
): MovieKey? {
// The paging library is trying to load data after the anchor position
// Get the item closest to the anchor position
return state.anchorPosition?.let { position ->
state.closestItemToPosition(position)?.id?.let { repoId ->
database.movieKeyDao().remoteKeysMovieId(repoId)
}
}
}
視圖模型:
@HiltViewModel
class MovieViewModel @Inject constructor(
private val movieRepository: Repository
) : ViewModel() {
@ExperimentalPagingApi
fun getMovies() = movieRepository.movies().cachedIn(viewModelScope)
}
界面畫面:
@ExperimentalCoilApi
@ExperimentalPagingApi
@Composable
fun MainScreen(){
val viewModel: MovieViewModel = viewModel()
val movieList = viewModel.getMovies().collectAsLazyPagingItems()
LazyColumn{
items(movieList){ movie ->
if (movie != null) {
Card(movie)
}
}
}
}
@ExperimentalCoilApi
@ExperimentalPagingApi
@Composable
fun Main(){
MainScreen()
}
MainActivity.kt
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
@ExperimentalCoilApi
@ExperimentalPagingApi
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
JetpackComposePagingTheme {
// A surface container using the 'background' color from the theme
Surface(color = MaterialTheme.colors.background) {
Main()
}
}
}
}
}
我正在關注paging3 Codelab來編寫 remoteMediator。 打開應用程序時,它只加載前 2 個頁面,然后無限循環進行無限 retrofit 調用
您並沒有完全按照您引用的 Codelab所建議的那樣做: 他們在init
中創建分頁Flow
,並且僅在查詢字符串更改時才更新它。
另一方面,您在每個導致您的問題的重組上調用viewModel.getMovies()
。 在文檔中查看您應該如何處理 Compose 中的副作用。
在這種特殊情況下,由於您沒有任何參數,您可以像這樣在視圖 model 中簡單地創建一次:
@HiltViewModel
class MovieViewModel @Inject constructor(
private val movieRepository: Repository
) : ViewModel() {
val moviesPagingFlow = movieRepository.movies().cachedIn(viewModelScope)
}
不知道為什么,但在我的情況下,從 LazyColumn 中分離 LazyPagingItems 是有效的。
嘗試以下操作:
@Composable
fun MainScreen(){
val viewModel: MovieViewModel = viewModel()
val movieList = viewModel.getMovies().collectAsLazyPagingItems()
MainScreen(movies = movieList)
}
@Composable
fun MainScreen(movies: LazyPagingItems<Movie>){
LazyColumn{
items(movies){ movie ->
if (movie != null) {
Card(movie)
}
}
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.