我正在使用带有导航组件、改进和分页3的Jetpack Compose.我遇到的问题是,当我导航到有分页的屏幕时,它会连续发出3个网络请求,而当我导航到另一个屏幕时,它会再次发出请求,因此,它不会缓存.从业绩的Angular 来看,这并不好.我try 在Composable中使用LauncdedEffect,但引发了编译错误.我可以做些什么来防止这种多个请求?

My viewModel:

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

My Repository:

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
     }
    // .....
}

My DataSource:

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,
        )
    }
}

MainActivity

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

推荐答案

您已经将您的方法编写为:

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

这意味着每次调用此方法时,您都会返回一个调用了cachedIn的分支新流--您实际上并没有将该流保存在您的ViewModel层的任何地方,所以每次您的屏幕重组或返回到该屏幕时,您都会得到一个全新的流.

相反,您需要:

  1. primaryCategory状态保留在您的ViewModel中.公开一个设置器,以便您的用户界面可以设置您正在寻找的类别.
  2. 使用primaryCategory状态作为对courseDataRepository.getCourses的呼叫的输入
  3. 使用cachedIn将最终状态公开给您的UI,以确保您始终返回单个流对象,该对象在配置更改期间保留缓存数据.

这意味着你的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更改以设置主要类别并使用新的、现在实际缓存的流:

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

你还没有包括你的coursesParam是从哪里得到的--如果它是你的应用程序的Navigation Compose个书面屏幕中的一个的参数,那么它也可以通过SavedStateHandle参数直接提供给你的ViewModel,这将允许你通过savedStateHandle.getStateFlow<String>("COURSES_PARAM")直接从那里读取它(例如,使用你在创建参数时使用的任何键),而不需要setPrimaryCategory方法.

Android相关问答推荐

以正确的方式从房间收集流量

广播接收者意图从服务内设置,而不被其他服务接收

在Android应用程序上使用Room数据库时,是否可以预先填充数据

使用Kotlin的SD卡

在Jetpack Compose中的隐藏状态栏后面绘制

从片段导航回来

如何防止在Android Studio中设置kotlin断点时优化变量

Android Kotlin - 计费 - 从应用内购买获取productId

Android Compose - 为什么 Canvas 中的drawText在底部被切断而不是在顶部?

是否可以在 Android 应用程序的 Wifi 设置中为 DNS 服务器设置自定义 IP?

Android Studio 在 list 文件中已经声明了活动类,但仍出现无法找到明确的活动类的错误

在 kotlin 中动态添加 GridView

根据另一个数组的值对数组进行排序

我该怎么做文本计时器

如何在 Jetpack Compose 中设置行宽等于 TextField 的宽度?

Jetpack Compose - 每次点击按钮都不起作用

java.lang.String 类型的值 Forbidden 无法转换为 JSONObject

自定义 Recyclerview [Android & Kotlin]

为什么按钮没有拉伸到屏幕边缘?

可组合的 fillMaxSize 和旋转不起作用