I'm trying to set retrieved data from an API into a LazyColumn, which will be called every time a date is picked from a Material3 date picker, but during the app, these warning traces (#1, #2) in Logcat appeared in every API call. Although I make it inside a LaunchedEffect, The app sometimes takes too much time (up to 15 seconds) in every API call.
I've attached some code samples for more explanation.
@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)
Please make sure first that the API is not the bottlenck here. You can send a request to your API using PostMan or the Thunderclient Visual Studio Code Plugin. With these tools, you can check whether the API itself needs that much time to return the response.
Once you sorted this out as a possible cause, I would suggest that you attach a debugger and set a breakpoint to the code within the
LaunchedEffect. I see no obvious error in your code, but some smells:1.)
LaunchedEffectorviewModelScopeis redundantYou are using both a
viewModelScopeand aLaunchedEffectcombined for calling thegetTodayMatchesfunction. If you use aLaunchedEffect, you can directly call a suspend function without the need for aviewModelScope. So you could update your ViewModel function like this:2.) Callback Handling seems odd
Once the DatePicker is confirmed, you set the
isClickedvariable in the ViewModel to true. Then you use a side effect on another place to execute code when the variable becomes true. Instead of this approach, I would suggest that you directly call the ViewModel function when the selected date changes.3.) LazyColumn seems odd
It seems like you are mixing two overloads of the
itemsfunction. You probably wanted to do4.) Single Source of Truth
It seems like you are having a lot of duplication in your Composable and ViewModel. Either store the date in your ViewModel, or store it in the Composable. But now, you have a
selectedDatevariable in your Composable, and also at the same time you have adialogueDatein your ViewModel. And you need a lot of code to keep these two synchronized. In my suggestion above, I store the date in the Composable.