android Jetpack Compose with Paging 3发出太多网络请求

oknrviil  于 2023-04-10  发布在  Android
关注(0)|答案(1)|浏览(230)

我正在使用Jetpack Compose与导航组件,Retrofit和Paging 3。我遇到的问题是,当我导航到具有分页的屏幕时,它会发出3个连续的网络请求,当我导航到另一个屏幕时,它会再次发出请求,因此,它不会缓存。从性能Angular 来看,这不太好。我尝试在composable中使用LauncdedEffect,但它抛出了一个编译错误。我可以做些什么来防止这种多个请求?

我的viewModel:

class CoursesViewModel(private val courseDataRepository: CourseDataRepository): ViewModel() {
    // ......
    fun getCourses(primaryCategory: String): Flow<PagingData<CourseData>> =
        courseDataRepository.getCourses(primaryCategory).cachedIn(viewModelScope)
    // ......
}

我的仓库:

interface CourseDataRepository {
    // ....
     fun getCourses(queryParam: String): Flow<PagingData<CourseData>>
    // ....
}

 class DefaultCourseDataRepository(private val retrofitService: EskoolApiService): CourseDataRepository { 
    // .....
      override fun getCourses(queryParam: String): Flow<PagingData<CourseData>> {
         return Pager(
             config = PagingConfig(pageSize = PAGE_SIZE),
             pagingSourceFactory = {
                 CourseDataSource(queryParam, retrofitService)
             }
         ).flow
     }
    // .....
}

我的数据源:

class CourseDataSource(
    private val queryIdentifier: String,
    private val apiService: EskoolApiService
): PagingSource<Int, CourseData>() {

    override fun getRefreshKey(state: PagingState<Int, CourseData>): Int? {
        return state.anchorPosition?.let { anchorPosition ->
            state.closestPageToPosition(anchorPosition)?.prevKey?.plus(1)
                ?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(1)
        }
    }

    override suspend fun load(params: LoadParams<Int>): LoadResult<Int, CourseData> {

        return try {
            val nextPage = params.key ?: 1
            val filters = mapOf("page" to nextPage, "page_size" to PAGE_SIZE)

            val data: Courses = if (queryIdentifier == defaultCoursesArg) {
                            apiService.getAllCourses(filters, null)
                        } else {
                            apiService.getAllCourses(filters, queryIdentifier)
                        }

            LoadResult.Page(
                data = data.courses,
                prevKey = if (nextPage == 1) null else nextPage - 1,
                nextKey = if (data.courses.isEmpty()) null else nextPage + 1
            )
        } catch (e: IOException) { // ************
            return LoadResult.Error(e)
            } catch (exception: HttpException) {
                return LoadResult.Error(exception)
            }
        }
}

CoursesScreen

@SuppressLint("MutableCollectionMutableState")
@Composable
fun CoursesScreen(
    modifier: Modifier = Modifier,
    coursesViewModel: CoursesViewModel,
    coursesParam: String? = defaultCoursesArg,
    onClickCourseDetailsBySpan: (Int) -> Unit = {},
    onClickCourseDetailsByTitle: (Int) -> Unit = {},
) {

    val courseItems: LazyPagingItems<CourseData> =
        coursesViewModel.getCourses(coursesParam!!).collectAsLazyPagingItems()

    Surface(
        modifier = modifier
            .padding(vertical = CARD_PADDING),
        color = colors.background
    ) {
        CourseCards(
            courses = courseItems,
            onClickCourseDetailsBySpan = onClickCourseDetailsBySpan,
            onClickCourseTitle = onClickCourseDetailsByTitle,
        )
    }
}

主活动

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            val coursesViewModel: CoursesViewModel = viewModel(factory = CoursesViewModel.Factory)
            // app content
            ESchoolApp(viewModel = coursesViewModel)
        }
    }
}
qc6wkl3g

qc6wkl3g1#

你已经把你的方法写成:

fun getCourses(primaryCategory: String): Flow<PagingData<CourseData>> =
    courseDataRepository.getCourses(primaryCategory).cachedIn(viewModelScope)

这意味着每次调用此方法时,您都会返回一个新的Flow分支,您在其上调用了cachedIn-实际上并没有将该Flow保存在ViewModel层的任何位置,因此每次屏幕重组或返回该屏幕时,您都会获得一个全新的Flow。
相反,您需要:
1.将primaryCategory状态保存在ViewModel中。公开一个setter,以便UI可以设置您正在查找的类别。
1.使用该primaryCategory状态作为对courseDataRepository.getCourses调用的输入
1.使用cachedIn将最终状态公开给UI,以确保始终返回单个Flow对象,该对象在配置更改时保留缓存的数据。
这意味着您的ViewModel看起来更像:

class CoursesViewModel(
    private val courseDataRepository: CourseDataRepository
): ViewModel() {
  // Keep your internal state as an observable MutableStateFlow
  // here defaulting to null
  private val primaryCategoryFlow = MutableStateFlow<String?>(null)

  // Allow your UI to change the primary category
  public fun setPrimaryCategory(primaryCategory: String) {
    primaryCategoryFlow.value = primaryCategory
  }

  // Transform each output of primaryCategoryFlow into a
  // new Flow from the courseDataRepository
  public val coursesFlow: Flow<PagingData<CourseData>> = primaryCategoryFlow
      .filterNotNull() // Skip our default value
      .distinctUntilLatest() // Don't return a new value if the category hasn't changed
      .flatMapLatest { primaryCategory ->
          courseDataRepository.getCourses(primaryCategory)
      }.cachedIn(viewModelScope)
}

您的Composable更改为设置主类别并使用新的,现在实际缓存的Flow:

if (coursesParam != null) {
  coursesViewModel.setPrimaryCategory(coursesParam)
}
val courseItems: LazyPagingItems<CourseData> =
        coursesViewModel.coursesFlow.collectAsLazyPagingItems()

但是,您还没有包括从何处获取coursesParam-如果它是应用程序的Navigation Compose写入屏幕之一的参数,则它也可以通过SavedStateHandle参数直接用于ViewModel,这将允许您通过savedStateHandle.getStateFlow<String>("COURSES_PARAM")直接从那里读取它(例如,使用创建该参数时使用的任何键),而根本不需要setPrimaryCategory方法。

相关问题