Search code examples
androidkotlinandroid-jetpack-composeandroid-jetpack

App crashes when there is no delay in LaunchedEffect


Why my app crashes when I run this code:

val scrollState = rememberLazyListState(0)
var isLastItemVisible by remember { mutableStateOf(false) }

LaunchedEffect(scrollState) {
    while (!isLastItemVisible) {
        val lastVisibleItemIndex = scrollState.layoutInfo.visibleItemsInfo.lastOrNull()?.index
        val totalItemsCount = scrollState.layoutInfo.totalItemsCount
        isLastItemVisible = lastVisibleItemIndex != null && lastVisibleItemIndex == totalItemsCount - 1
    }

    onLastItemSeen()
}

When I add any delay at last line in loop there is everything right. I display items in LazyRow.


Solution

  • It's giving ANR not crashing because you are in an infinite loop you can't scroll.

    You can use derivedStateOf to check if last item is visible as

    val isLastItemVisible by remember {
        derivedStateOf {
            val lastVisibleItemIndex = scrollState.layoutInfo.visibleItemsInfo.lastOrNull()?.index
            val totalItemsCount = scrollState.layoutInfo.totalItemsCount
            lastVisibleItemIndex != null && lastVisibleItemIndex == totalItemsCount - 1
        }
    }
    
    LaunchedEffect(isLastItemVisible){
        if (isLastItemVisible){
            // Send event here
        }
    }
    

    or snapshotFlow inside LaunchedEffect that sends event only once

    // Sends event only once
    LaunchedEffect(scrollState) {
        snapshotFlow {
            scrollState.layoutInfo
        }
            .map {
                val lastVisibleItemIndex = it.visibleItemsInfo.lastOrNull()?.index
                val totalItemsCount = it.totalItemsCount
                lastVisibleItemIndex != null && lastVisibleItemIndex == totalItemsCount - 1
            }
            // If it's a one time event filtering it
            .filter { it }
            .distinctUntilChanged()
            .collect {
                println("New Event sent...")
            }
    }
    

    Or one that sends event every time last item is visible

    // Sends event every time last item is seen
    LaunchedEffect(scrollState) {
        snapshotFlow {
            scrollState.layoutInfo
        }
            .map {
                val lastVisibleItemIndex = it.visibleItemsInfo.lastOrNull()?.index
                val totalItemsCount = it.totalItemsCount
                lastVisibleItemIndex != null && lastVisibleItemIndex == totalItemsCount - 1
            }
            .distinctUntilChanged()
            .collect {visible->
                println("Scroll visible: $visible")
                if (visible){
                    println("New Event sent...")
                }
    
            }
    }
    

    Also i add a sample to display button on bottom end when last item is visible can be implemented such as

    @Preview
    @Composable
    private fun ListSample() {
        val scrollState = rememberLazyListState(0)
    
        val isLastItemVisible by remember {
            derivedStateOf {
                val lastVisibleItemIndex = scrollState.layoutInfo.visibleItemsInfo.lastOrNull()?.index
                val totalItemsCount = scrollState.layoutInfo.totalItemsCount
                lastVisibleItemIndex != null && lastVisibleItemIndex == totalItemsCount - 1
            }
        }
    
        Box {
            LazyColumn(
                state = scrollState
            ) {
                items(20) {
                    Text(
                        text = "Row $it",
                        modifier = Modifier.fillMaxWidth().padding(8.dp),
                        fontSize = 20.sp
                    )
                }
            }
    
            if (isLastItemVisible) {
                FloatingActionButton(
                    modifier = Modifier.align(Alignment.BottomEnd).padding(8.dp),
                    onClick = {
    
                    },
                    content = {
                        Icon(imageVector = Icons.Default.ArrowUpward, contentDescription = null)
    
                    }
                )
            }
        }
    }