我正在使用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)
}
}
}
1条答案
按热度按时间qc6wkl3g1#
你已经把你的方法写成:
这意味着每次调用此方法时,您都会返回一个新的Flow分支,您在其上调用了
cachedIn
-实际上并没有将该Flow保存在ViewModel层的任何位置,因此每次屏幕重组或返回该屏幕时,您都会获得一个全新的Flow。相反,您需要:
1.将
primaryCategory
状态保存在ViewModel中。公开一个setter,以便UI可以设置您正在查找的类别。1.使用该
primaryCategory
状态作为对courseDataRepository.getCourses
调用的输入1.使用
cachedIn
将最终状态公开给UI,以确保始终返回单个Flow对象,该对象在配置更改时保留缓存的数据。这意味着您的
ViewModel
看起来更像:您的Composable更改为设置主类别并使用新的,现在实际缓存的Flow:
但是,您还没有包括从何处获取
coursesParam
-如果它是应用程序的Navigation Compose写入屏幕之一的参数,则它也可以通过SavedStateHandle
参数直接用于ViewModel,这将允许您通过savedStateHandle.getStateFlow<String>("COURSES_PARAM")
直接从那里读取它(例如,使用创建该参数时使用的任何键),而根本不需要setPrimaryCategory
方法。