我试图将从 API 检索到的数据设置到 LazyColumn 中,每次从 Material3 日期选择器中选取日期时都会调用该数据,但在应用程序中,Logcat 中出现了这些警告跟踪(#1、#2)在每个 API 调用中。虽然我是在 LaunchedEffect 中实现的,但应用程序有时在每次 API 调用中都会花费太多时间(最多 15 秒)。
我附上了一些代码示例以供更多说明。
@Composable
fun TodayMatchesByLeague(matchesList: List<TodayResponseItem?>) {
LazyColumn {
items(matchesList.size) {
TodayMatchItem(matchItem)
}
}
}
@Composable
fun FetchTodayGamesData(
mainViewModel: MainViewModel = hiltViewModel()
) {
val date = mainViewModel.dialogueDate.observeAsState().value!!
if (mainViewModel.isClicked.value!!) {
LaunchedEffect(key1 = null) {
mainViewModel.getTodayMatches(
date, 2023
)
mainViewModel.saveCalendarClicked(false)
}
}
when (val state =
mainViewModel.todayMatchesState.collectAsState().value
) {
is TodayMatchesState.Empty -> {}
is TodayMatchesState.Loading -> {}
is TodayMatchesState.Error -> {}
is TodayMatchesState.Success -> {
TodayMatchesByLeague(
matchesList = state.data.body()!!.response!!
)
}
}
}
//
val selectedDate = rememberSaveable { mutableStateOf<LocalDate?>(LocalDate.now()) }
val localDate = selectedDate.value
viewModel.saveDialogueDate(localDate.toString())
val datePickerState = rememberDatePickerState()
var sheetState by remember{ mutableStateOf(false) }
val selectedDate1 = Instant.ofEpochMilli(datePickerState.selectedDateMillis ?: 0)
.atZone(ZoneId.systemDefault())
.toLocalDate()
if (datePickerState.selectedDateMillis != null) {
selectedDate.value = selectedDate1
viewModel.saveCalendarClicked(true)
}
if (sheetState)
BottomSheetDatePicker(
state = datePickerState,
onDismissRequest = { sheetState = false }
)
IconButton(
onClick = {
sheetState = true
}
) {
Icon(imageVector = Icons.Default.CalendarMonth, contentDescription = "Calendar")
}
//ViewModel
@HiltViewModel
class MainViewModel @Inject constructor(private val matchesRepository: MatchesRepository): ViewModel() {
private var _todayMatchesState = MutableStateFlow<TodayMatchesState>(TodayMatchesState.Empty)
val todayMatchesState: StateFlow<TodayMatchesState> = _todayMatchesState
//Calendar Dialogue Date
private var _dialogueDate = MutableLiveData("")
val dialogueDate: LiveData<String> = _dialogueDate
private var _isClicked = MutableLiveData(true)
val isClicked: LiveData<Boolean> = _isClicked
fun saveCalendarClicked(isClicked: Boolean) {
_isClicked.value = isClicked
}
fun saveDialogueDate(date: String) {
_dialogueDate.value = date
}
fun getTodayMatches(date: String, season: Int) {
_todayMatchesState.value = TodayMatchesState.Loading
viewModelScope.launch(Dispatchers.IO) {
try {
val todayMatchesResponse = matchesRepository.getTodayMatches(date, season)
_todayMatchesState.value = TodayMatchesState.Success(todayMatchesResponse)
}
catch (exception: HttpException) {
_todayMatchesState.value = TodayMatchesState.Error("Something went wrong")
}
catch (exception: IOException) {
_todayMatchesState.value = TodayMatchesState.Error("No internet connection")
}
}
}
}
#1
Event:APP_SCOUT_WARNING Thread:main backtrace:
at java.lang.Thread.currentThread(Native Method)
at java.lang.ThreadLocal.get(ThreadLocal.java:162)
at android.os.Looper.myLooper(Looper.java:323)
at kotlinx.coroutines.android.HandlerContext.isDispatchNeeded(HandlerDispatcher.kt:137)
at kotlinx.coroutines.DispatchedTaskKt.dispatch(DispatchedTask.kt:160)
at kotlinx.coroutines.CancellableContinuationImpl.dispatchResume(CancellableContinuationImpl.kt:474)
at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl(CancellableContinuationImpl.kt:508)
at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl$default(CancellableContinuationImpl.kt:497)
at kotlinx.coroutines.CancellableContinuationImpl.resumeWith(CancellableContinuationImpl.kt:368)
at kotlinx.coroutines.flow.StateFlowSlot.makePending(StateFlow.kt:284)
at kotlinx.coroutines.flow.StateFlowImpl.updateState(StateFlow.kt:349)
at kotlinx.coroutines.flow.StateFlowImpl.setValue(StateFlow.kt:316)
at coil.compose.ConstraintsSizeResolver.measure-3p2s80s(AsyncImage.kt:209)
at androidx.compose.ui.node.BackwardsCompatNode.measure-3p2s80s(BackwardsCompatNode.kt:312)
at androidx.compose.ui.node.LayoutModifierNodeCoordinator.measure-BRTryo0(LayoutModifierNodeCoordinator.kt:186)
at androidx.compose.foundation.layout.SizeNode.measure-3p2s80s(Size.kt:838)
at androidx.compose.ui.node.LayoutModifierNodeCoordinator.measure-BRTryo0(LayoutModifierNodeCoordinator.kt:186)
at androidx.compose.foundation.layout.PaddingNode.measure-3p2s80s(Padding.kt:397)
at androidx.compose.ui.node.LayoutModifierNodeCoordinator.measure-BRTryo0(LayoutModifierNodeCoordinator.kt:186)
at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasureBlock$1.invoke(LayoutNodeLayoutDelegate.kt:255)
at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasureBlock$1.invoke(LayoutNodeLayoutDelegate.kt:254)
at androidx.compose.runtime.snapshots.Snapshot$Companion.observe(Snapshot.kt:488)
at androidx.compose.runtime.snapshots.SnapshotStateObserver$ObservedScopeMap.observe(SnapshotStateObserver.kt:501)
at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads(SnapshotStateObserver.kt:257)
at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui_release(OwnerSnapshotObserver.kt:133)
at androidx.compose.ui.node.OwnerSnapshotObserver.observeMeasureSnapshotReads$ui_release(OwnerSnapshotObserver.kt:113)
at androidx.compose.ui.node.LayoutNodeLayoutDelegate.performMeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:1622)
at androidx.compose.ui.node.LayoutNodeLayoutDelegate.access$performMeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:39)
at androidx.compose.ui.node.LayoutNodeLayoutDelegate$MeasurePassDelegate.remeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:623)
at androidx.compose.ui.node.LayoutNodeLayoutDelegate$MeasurePassDelegate.measure-BRTryo0(LayoutNodeLayoutDelegate.kt:599)
at androidx.compose.foundation.layout.RowColumnMeasurementHelper.measureWithoutPlacing-_EkL_-Y(RowColumnMeasurementHelper.kt:120)
at androidx.compose.foundation.layout.RowColumnMeasurePolicy.measure-3p2s80s(RowColumnImpl.kt:69)
at androidx.compose.ui.node.InnerNodeCoordinator.measure-BRTryo0(InnerNodeCoordinator.kt:134)
at androidx.compose.foundation.layout.PaddingNode.measure-3p2s80s(Padding.kt:397)
at androidx.compose.ui.node.LayoutModifierNodeCoordinator.measure-BRTryo0(LayoutModifierNodeCoordinator.kt:186)
at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasureBlock$1.invoke(LayoutNodeLayoutDelegate.kt:255)
at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasureBlock$1.invoke(LayoutNodeLayoutDelegate.kt:254)
at androidx.compose.runtime.snapshots.Snapshot$Companion.observe(Snapshot.kt:488)
at androidx.compose.runtime.snapshots.SnapshotStateObserver$ObservedScopeMap.observe(SnapshotStateObserver.kt:501)
#2
Event:APP_SCOUT_HANG Thread:main backtrace:
at java.lang.ref.Reference$SinkHolder.-$$Nest$sfgetfinalize_count(Unknown Source:2)
at java.lang.ref.Reference.reachabilityFence(Reference.java:342)
at libcore.util.NativeAllocationRegistry.registerNativeAllocation(NativeAllocationRegistry.java:281)
at android.graphics.ColorFilter.getNativeInstance(ColorFilter.java:68)
at android.graphics.Paint.getNativeInstance(Paint.java:738)
at android.graphics.BaseRecordingCanvas.drawBitmap(BaseRecordingCanvas.java:95)
at androidx.compose.ui.graphics.AndroidCanvas.drawImageRect-HPBpro0(AndroidCanvas.android.kt:275)
at androidx.compose.ui.graphics.drawscope.CanvasDrawScope.drawImage-AZ2fEMs(CanvasDrawScope.kt:257)
at androidx.compose.ui.node.LayoutNodeDrawScope.drawImage-AZ2fEMs(Unknown Source:24)
at androidx.compose.ui.graphics.drawscope.DrawScope.drawImage-AZ2fEMs$default(DrawScope.kt:566)
at androidx.compose.ui.graphics.vector.DrawCache.drawInto(DrawCache.kt:102)
at androidx.compose.ui.graphics.vector.VectorComponent.draw(Vector.kt:181)
at androidx.compose.ui.graphics.vector.VectorPainter.onDraw(VectorPainter.kt:248)
at androidx.compose.ui.graphics.painter.Painter.draw-x_KDEd0(Painter.kt:212)
at androidx.compose.ui.draw.PainterNode.draw(PainterModifier.kt:342)
at androidx.compose.ui.node.LayoutNodeDrawScope.drawDirect-x_KDEd0$ui_release(LayoutNodeDrawScope.kt:105)
at androidx.compose.ui.node.LayoutNodeDrawScope.draw-x_KDEd0$ui_release(LayoutNodeDrawScope.kt:86)
at androidx.compose.ui.node.NodeCoordinator.drawContainedDrawModifiers(NodeCoordinator.kt:376)
at androidx.compose.ui.node.NodeCoordinator.draw(NodeCoordinator.kt:365)
at androidx.compose.ui.node.LayoutModifierNodeCoordinator.performDraw(LayoutModifierNodeCoordinator.kt:265)
at androidx.compose.ui.node.NodeCoordinator.drawContainedDrawModifiers(NodeCoordinator.kt:373)
at androidx.compose.ui.node.NodeCoordinator.draw(NodeCoordinator.kt:365)
at androidx.compose.ui.node.LayoutNode.draw$ui_release(LayoutNode.kt:962)
at androidx.compose.ui.node.InnerNodeCoordinator.performDraw(InnerNodeCoordinator.kt:183)
at androidx.compose.ui.node.LayoutNodeDrawScope.drawContent(LayoutNodeDrawScope.kt:66)
at androidx.compose.material.ripple.AndroidRippleIndicationInstance.drawIndication(Ripple.android.kt:270)
at androidx.compose.foundation.IndicationModifier.draw(Indication.kt:346)
at androidx.compose.ui.node.BackwardsCompatNode.draw(BackwardsCompatNode.kt:350)
at androidx.compose.ui.node.LayoutNodeDrawScope.drawDirect-x_KDEd0$ui_release(LayoutNodeDrawScope.kt:105)
at androidx.compose.ui.node.LayoutNodeDrawScope.performDraw(LayoutNodeDrawScope.kt:76)
at androidx.compose.ui.node.LayoutNodeDrawScope.drawContent(LayoutNodeDrawScope.kt:55)
at androidx.compose.foundation.BackgroundNode.draw(Background.kt:159)
at androidx.compose.ui.node.LayoutNodeDrawScope.drawDirect-x_KDEd0$ui_release(LayoutNodeDrawScope.kt:105)
at androidx.compose.ui.node.LayoutNodeDrawScope.draw-x_KDEd0$ui_release(LayoutNodeDrawScope.kt:86)
at androidx.compose.ui.node.NodeCoordinator.drawContainedDrawModifiers(NodeCoordinator.kt:376)
at androidx.compose.ui.node.NodeCoordinator.access$drawContainedDrawModifiers(NodeCoordinator.kt:56)
at androidx.compose.ui.node.NodeCoordinator$drawBlock$1$1.invoke(NodeCoordinator.kt:395)
at androidx.compose.ui.node.NodeCoordinator$drawBlock$1$1.invoke(NodeCoordinator.kt:394)
at androidx.compose.runtime.snapshots.Snapshot$Companion.observe(Snapshot.kt:488)
at androidx.compose.runtime.snapshots.SnapshotStateObserver$ObservedScopeMap.observe(SnapshotStateObserver.kt:501)
at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads(SnapshotStateObserver.kt:257)
at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui_release(OwnerSnapshotObserver.kt:133)
at androidx.compose.ui.node.NodeCoordinator$drawBlock$1.invoke(NodeCoordinator.kt:394)
请首先确保 API 不是这里的瓶颈。您可以使用 PostMan 或 Thunderclient Visual Studio Code 插件向您的 API 发送请求。使用这些工具,您可以检查 API 本身是否需要那么多时间来返回响应。
一旦您解决了这个可能的原因,我建议您附加一个调试器并在
LaunchedEffect
中的代码中设置一个断点。我在你的代码中没有看到明显的错误,但有些味道:
1.)
LaunchedEffect
或 viewModelScope
是多余的
您同时使用
viewModelScope
和 LaunchedEffect
来调用 getTodayMatches
函数。如果使用 LaunchedEffect
,则可以直接调用挂起函数,而不需要 viewModelScope
。所以你可以像这样更新你的 ViewModel 函数:
suspend fun getTodayMatches(date: String, season: Int) {
_todayMatchesState.value = TodayMatchesState.Loading
try {
val todayMatchesResponse = matchesRepository.getTodayMatches(date, season)
_todayMatchesState.value = TodayMatchesState.Success(todayMatchesResponse)
}
catch (exception: HttpException) {
_todayMatchesState.value = TodayMatchesState.Error("Something went wrong")
}
catch (exception: IOException) {
_todayMatchesState.value = TodayMatchesState.Error("No internet connection")
}
}
2.) 回调处理看起来很奇怪
确认 DatePicker 后,将 ViewModel 中的
isClicked
变量设置为 true。然后,当变量变为 true 时,您可以在另一个地方使用副作用来执行代码。我建议您在所选日期更改时直接调用 ViewModel 函数,而不是这种方法。
val selectedDate by rememberSaveable { mutableStateOf<LocalDate?>(LocalDate.now()) }
val datePickerState = rememberDatePickerState()
var sheetState by remember{ mutableStateOf(false) }
// Whenever selected date changes, call getTodayMatches
LaunchedEffect(datePickerState.selectedDateMillis) {
if (datePickerState.selectedDateMillis != null) {
selectedDate = Instant.ofEpochMilli(datePickerState.selectedDateMillis ?: 0)
.atZone(ZoneId.systemDefault())
.toLocalDate()
viewModel.getTodayMatches(selectedDate.toString, 2023)
}
}
if (sheetState)
BottomSheetDatePicker(
// ...
)
// ...
}
3.) LazyColumn 看起来很奇怪
items
函数的两个重载。你可能想做
LazyColumn {
items(matchesList) { matchItem ->
TodayMatchItem(matchItem)
}
}
4.) 单一事实来源
您的 Composable 和 ViewModel 似乎有很多重复。将日期存储在 ViewModel 中,或者将其存储在 Composable 中。但现在,您的 Composable 中有一个
selectedDate
变量,同时您的 ViewModel 中有一个 dialogueDate
。