android-jetpack-composeandroid-jetpack-compose-lazy-columnsticky-header

Sticky header doesn't stay at bottom of screen when LazyColum list of items is small


I am implementing a chat app using a LazyColumn. I've created this with reverseLayout = true because I want the LazyColumn to automatically scroll to the last message when the view and keyboard first appear.

I want the TextField to stay at the bottom of the screen so I've implemented a stickyHeader. When my upperLimit is set to a high number where the view needs to scroll, the TextField stays at the bottom of the screen. However when upperLimit is set to a much smaller number like 3, the TextField no longer stays at the bottom of the screen.

What do I need to edit to fix this?


@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class)
@Composable
fun ChatView() {

    val lazyColumnState = rememberLazyListState()
    var textFieldText by remember { mutableStateOf("") }

    val upperLimit = 30

    LazyColumn(
        state = lazyColumnState,
        contentPadding = PaddingValues(40.dp),
        reverseLayout = true
    ) {

        stickyHeader {
            TextField(
                value = textFieldText,
                onValueChange = { inputTextField ->
                    textFieldText = inputTextField
                }
            )
        }


        item {
            for (i in upperLimit downTo 0) {
                Text(
                    text = "This is message no. $i",
                    modifier = Modifier.padding(10.dp)
                )
            }
        }
    }
}


Solution

  • Please try to add the fillMaxHeight() Modifier to the LazyColumn:

    LazyColumn(
        modifier = Modifier.fillMaxHeight(),
        state = lazyColumnState,
        contentPadding = PaddingValues(40.dp),
        reverseLayout = true
    ) {
        //...
    }
    

    However, then the first few messages will be attached to the bottom of the screen. If you don't want this, you can try to use reverseLayout = false and programmatically handle scrolling using lazyColumnState.scrollToItem() like shown at this sample repository from @Thracian, it gives a great example how to implement a chat app in Jetpack Compose.

    @Composable
    fun ChatView() {
    
        val lazyColumnState = rememberLazyListState()
        var textFieldText by remember { mutableStateOf("") }
        val chatList = remember {
            mutableStateListOf(
                "Hello, how are you?",
                "I am fine, thanks.",
                "I am learning Jetpack Compose, and you?",
                "Bro why do you do this, it's so boring!",
                "The only thing that's boring is having a conversation with you...",
                "Bro that's so mean!",
                "I did not mean it in a mean way :)",
                "Aw you're such a nice guy... NOT",
                "I appreciate you seeing my whole potential",
                "Well, that's what friends are for.\nYou should feel honored for having me as your friend!",
                "Be assured that my gratitude is on the same level as your arrogance."
            )
        }
    
        LaunchedEffect(chatList.size) {
            lazyColumnState.animateScrollToItem(chatList.size - 1)
        }
    
        Column(
            modifier = Modifier.fillMaxSize()
        ) {
            LazyColumn(
                modifier = Modifier
                    .fillMaxWidth()
                    .weight(1f),
                state = lazyColumnState,
                contentPadding = PaddingValues(16.dp),
            ) {
                itemsIndexed(
                    items = chatList
                ) { chatIndex, chatMessage,  ->
                    Row(
                        modifier = Modifier.fillMaxWidth(),
                        horizontalArrangement = if (chatIndex % 2 == 0) Arrangement.Start else Arrangement.End  // simulate chat conversation
                    ) {
                        Text(
                            text = chatMessage,
                            textAlign = if (chatIndex % 2 == 0) TextAlign.Start else TextAlign.End,
                            modifier = Modifier.padding(10.dp)
                        )
                    }
                }
            }
            Row {
                TextField(
                    modifier = Modifier.weight(1f),
                    value = textFieldText,
                    onValueChange = { inputTextField ->
                        textFieldText = inputTextField
                    }
                )
                IconButton(
                    onClick = {
                        chatList.add(textFieldText)
                        textFieldText = ""
                    }
                ) {
                    Icon(Icons.AutoMirrored.Filled.Send, "")
                }
            }
        }
    }
    

    Also note that you are actually creating one giant item in your LazyColumn, which kills any benefit of a lazy layout. You need to change your code like this:

    for (i in upperLimit downTo 0) {
        item {
            Text(
                text = "This is message no. $i",
                modifier = Modifier.padding(10.dp)
            )
        }
    }