androidandroid-jetpack-composeandroid-jetpack-compose-lazy-column

Compose: LazyColumn crash in when proguard enabled


I am using the below code to create an infinite scrollable list in compose android. I am detecting the last item and then making a network request to get the next items and then adding them in the list.

Below is the code of the Composable Screen

@Composable
fun SelectLocationScreen(
    selectLocationViewModel: SelectLocationViewModel = hiltViewModel(),
    showSnackBar: (
        message: String?,
        actionLabel: String,
        actionPerformed: () -> Unit,
        dismissed: () -> Unit,
    ) -> Unit,
) {
    val selectLocationUiState by selectLocationViewModel.uiState.collectAsStateWithLifecycle()
    var text by rememberSaveable { mutableStateOf("") }
    var reachedEnd by rememberSaveable { mutableStateOf(false) }
    val list = remember { mutableStateListOf<Place>() }
    val focusRequester = remember { FocusRequester() }

    Box(
        modifier =
            Modifier
                .fillMaxSize()
                .background(MaterialTheme.colorScheme.primary),
    ) {
        Column(
            modifier =
                Modifier
                    .padding(start = 20.dp, top = 20.dp, end = 20.dp),
        ) {
            SearchView(
                focusRequester,
                text,
                onTextChange = { it: String ->
                    text = it
                    list.clear()
                    if (text == "") {
                        list.clear()
                        reachedEnd = false
                    }
                    selectLocationViewModel.onSearchTextChange(text)
                },
                onTextClear = {
                    text = ""
                    list.clear()
                    reachedEnd = false
                    selectLocationViewModel.onSearchTextChange(text)
                },
            )
            LocationList(list, reachedEnd, selectLocationViewModel::getNextPage)

            when (selectLocationUiState.networkResult) {
                is NetworkResult.Loading -> {
                    // LoadingUi()
                }

                is NetworkResult.Success -> {
                    val listJson =
                        (selectLocationUiState.networkResult as NetworkResult.Success).data
                    LaunchedEffect(listJson) {
                        withContext(Dispatchers.IO) {
                            val deserializedList = parsePlacesList(listJson)
                            if (deserializedList.isEmpty()) {
                                reachedEnd = true
                            }
                            list.addAll(deserializedList)
                        }
                    }
                }

                is NetworkResult.Error -> {
                    var errorMessage =
                        (selectLocationUiState.networkResult as NetworkResult.Error).message
                }

                is NetworkResult.Exception -> {
                    var exceptionMessage =
                        (selectLocationUiState.networkResult as NetworkResult.Exception).throwable.localizedMessage
                }
            }
        }
    }
}

Below is the code for the LazyColumn:

@Composable
private fun LocationList(
    list: List<Place>,
    reachedEnd: Boolean,
    nextPage: (Int) -> Unit,
) {
    LazyColumn(modifier = Modifier.padding(top = 10.dp)) {
        itemsIndexed(list, key = { index: Int, place: Place -> place.id }) { index, place ->
            ListItem(
                place = place,
                onLocationItemClick = { clickedPlace ->
                    Timber.e("User clicked on : $clickedPlace")
                },
            )
            HorizontalDivider(color = Color.Black, thickness = 1.dp)
            if (index == list.size - 1 && !reachedEnd) {
                Box(
                    modifier =
                        Modifier
                            .height(70.dp)
                            .fillMaxWidth()
                            .background(Color.White),
                    contentAlignment = Alignment.Center,
                ) {
                    CircularProgressIndicator()
                }
                LaunchedEffect(list.size) {
                    nextPage(list[list.size - 1].id)
                }
            }
        }
    }
}

@Composable
fun ListItem(
    modifier: Modifier = Modifier,
    place: Place,
    onLocationItemClick: (Place) -> Unit,
) {
    Row(
        modifier =
            modifier
                .fillMaxWidth()
                .height(70.dp)
                .background(Color.White)
                .padding(horizontal = 15.dp)
                .clickable { onLocationItemClick(place) },
        verticalAlignment = Alignment.CenterVertically,
    ) {
        Icon(imageVector = Icons.Default.LocationOn, contentDescription = "null")
        Column(modifier = Modifier.padding(start = 15.dp)) {
            Text(text = place.name, style = MaterialTheme.typography.titleMedium, fontSize = 16.sp)
            Text(text = place.country, fontSize = 14.sp, color = Color.Gray)
        }
    }
}

I am gettignt he below crash when I am trying to search in the TextField

java.lang.ClassCastException
                            at androidx.lifecycle.Lifecycling$$ExternalSyntheticThrowCCEIfNotNull0.androidx.activity.ComponentActivity$1$1$$InternalSyntheticThrowCCEIfNotNull$94$ed16a7b4988d2cd3fe198b9b53d97740f5789cd82ed3d7645950bebb0d9b704e$0.m(SourceFile:1)
                            at com.vicky7230.sunny.ui.screens.selectLocation.SelectLocationScreenKt$LocationList$1$1.o(SourceFile:6)
                            at androidx.compose.foundation.BorderModifierNode$drawGenericBorder$1.com.vicky7230.sunny.ui.screens.selectLocation.SelectLocationScreenKt$LocationList$1$invoke$$inlined$itemsIndexed$default$1.invoke(SourceFile:31)
                                                                                                    com.vicky7230.sunny.ui.screens.selectLocation.SelectLocationScreenKt$LocationList$1$invoke$$inlined$itemsIndexed$default$1.invoke
                                                                                                    invoke$bridge
                            at androidx.compose.animation.AnimatedContentMeasurePolicy$measure$3.androidx.compose.foundation.lazy.layout.NearestRangeKeyIndexMap$2$1.invoke(SourceFile:55)
                                                                                                androidx.compose.foundation.lazy.layout.NearestRangeKeyIndexMap$2$1.invoke
                                                                                                invoke$bridge
                            at androidx.compose.foundation.lazy.layout.NearestRangeKeyIndexMap.androidx.compose.foundation.lazy.layout.MutableIntervalList.forEach(SourceFile:89)
                                                                                                <init>
                            at androidx.compose.foundation.lazy.LazyListItemProviderKt$rememberLazyListItemProviderLambda$1$itemProviderState$1.androidx.compose.foundation.lazy.LazyListItemProviderKt$rememberLazyListItemProviderLambda$1$itemProviderState$1.invoke(SourceFile:121)
                                                                                                                                                androidx.compose.foundation.lazy.LazyListItemProviderKt$rememberLazyListItemProviderLambda$1$itemProviderState$1.invoke
                                                                                                                                                invoke$bridge
                            at androidx.compose.runtime.Composer$Companion.androidx.compose.runtime.snapshots.Snapshot$Companion.observe(SourceFile:60)
                            at androidx.compose.runtime.DerivedSnapshotState.currentRecord(SourceFile:294)
                            at androidx.compose.runtime.DerivedSnapshotState.getCurrentRecord(SourceFile:16)
                            at androidx.compose.runtime.snapshots.SnapshotStateObserver$ObservedScopeMap.recordInvalidation(SourceFile:470)
                            at androidx.compose.runtime.snapshots.SnapshotStateObserver.androidx.compose.runtime.snapshots.SnapshotStateObserver.drainChanges(SourceFile:100)
                                                                                        access$drainChanges
                            at androidx.compose.animation.AnimatedContentKt$AnimatedContent$6$1$4$1.androidx.compose.runtime.snapshots.SnapshotStateObserver$applyObserver$1.invoke(SourceFile:62)
                                                                                                    androidx.compose.runtime.Recomposer$recompositionRunner$2$unregisterApplyObserver$1.invoke$bridge
                            at androidx.compose.animation.AnimatedContentKt$AnimatedContent$6$1$4$1.androidx.compose.runtime.snapshots.SnapshotStateObserver$applyObserver$1.invoke(SourceFile:207)
                                                                                                    invoke$bridge
                            at androidx.compose.runtime.snapshots.SnapshotKt.advanceGlobalSnapshot(SourceFile:62)
                            at androidx.compose.runtime.snapshots.SnapshotKt.androidx.compose.runtime.snapshots.SnapshotKt.advanceGlobalSnapshot(SourceFile:3)
                                                                            access$advanceGlobalSnapshot
                            at androidx.compose.ui.platform.GlobalSnapshotManager$ensureStarted$1.androidx.compose.runtime.snapshots.Snapshot$Companion.sendApplyNotifications(SourceFile:111)
                                                                                                    invokeSuspend
                            at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(SourceFile:1)
                            at kotlinx.coroutines.DispatchedTask.run(SourceFile:1)
                            at androidx.compose.ui.platform.AndroidUiDispatcher.androidx.compose.ui.platform.AndroidUiDispatcher.performTrampolineDispatch(SourceFile:24)
                                                                                access$performTrampolineDispatch
                            at androidx.compose.ui.platform.AndroidUiDispatcher$dispatchCallback$1.run(SourceFile:3)
                            at android.os.Handler.handleCallback(Handler.java:942)
                            at android.os.Handler.dispatchMessage(Handler.java:99)
                            at android.os.Looper.loopOnce(Looper.java:211)
                            at android.os.Looper.loop(Looper.java:300)
                            at android.app.ActivityThread.main(ActivityThread.java:8321)
                            at java.lang.reflect.Method.invoke(Native Method)
                            at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:581)
                            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1028)
                            Suppressed: b6.f: [j1.h1@f146fb8, k1{Cancelling}@60dd191, f1@e298f6]

The crash only occurs in the release build when I enable proguard:

isMinifyEnabled = true
isShrinkResources = true

Tried to search google and stackoverflow, been stuck for 2 days. Not sure what is going wrong here


Solution

  • You can use @Keep annotations or add keepclass proguard rules and there will be no errors.

    because R8 also obfuscate the data class and when you parse data from API it will crash.