簡體   English   中英

使用 Jetpack Compose 進行分頁會導致無限的 retrofit 調用

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM