2

I have a layout where each item in a LazyColumn can display an item-specific dialog. The Dialogs showed and worked fine except for some of the larger ones where whenever I would try to edit text in one of the dialog's TextFields it would trigger the dialog to disappear.

I tried a lot of different approaches and the weirdest thing was that the dialog works fine and allows me do edit the textfields if I only scroll the LazyColumn by even 1 item before attempting to show the dialog.

It has proven hard to reproduce the issue in a simple setting as changing even basic elements of my code makes the bug disappear but I finally managed. The appearance of the bug seems to depend on the LazyColumn being Scrollable and on the KeyBoard to be of type text. Next a somewhat long code sample and a GIF showing the issue:

@Composable
fun JointCourseTemplate(courseTemplateDto: CourseTemplateDto) {
    LazyColumn(
        //state= LazyListState()
    ){
        for(i in 0..4){
            item { TestPart(i = i) }
            for (j in 0..8){
                item { TestItem(i = j) }
            }
        }

    }
}

@Composable
fun TestPart(i:Int) {
    var showDialog by remember {
        mutableStateOf(false)
    }
    Row(Modifier.padding(8.dp)) {
        Text("part"+i)
        Button(onClick = { showDialog=true }) {
            Text("show dialog")
        }
    }
    
    PartDialog(showDialog = showDialog, hideDialog = {showDialog=false}) 
}

@Composable
fun TestItem(i:Int) {
    Row(Modifier.padding(8.dp)) {
        Text("item"+i)
        Button(onClick = { /*TODO*/ }) {
            Text("show dialog")
        }
    }
}

@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun PartDialog(showDialog:Boolean, hideDialog: () -> Unit) {
    var text by remember {
        mutableStateOf("")
    }
    if (showDialog) {
        Dialog(properties = DialogProperties(
            dismissOnClickOutside = false,
            usePlatformDefaultWidth = false
        ),
            onDismissRequest = { hideDialog() }) {
            Surface(
                modifier = Modifier
                    //.wrapContentHeight()
                    //.fillMaxWidth(0.9f)
                    .width(400.dp)
                    .height(600.dp),
                shape = RoundedCornerShape(10.dp)
            ) {
                Column {
                    Text(text = "Some Dialog")
                    OutlinedTextField(value = text, onValueChange = {text=it}, label = { Text(text = "TroubleMaker")})
                }
            }
        }
    }
}

enter image description here

Phil Dukhov
  • 67,741
  • 15
  • 184
  • 220
quealegriamasalegre
  • 2,887
  • 1
  • 13
  • 35
  • please provide a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example). If that's a bug, it should be reported. Your fix is far from perfect, `LazyColumn` should not work correctly with it – Phil Dukhov Feb 11 '22 at 09:36
  • @Philip Dukhov I'm on it. The thing is my code is quite long so i will have to workon it a bit. however I do not understand why LazyColumn should not work If I give it an instance of LazyListState, this is after all an argument that it accepts – quealegriamasalegre Feb 11 '22 at 09:55
  • You gonna create a new list state on each recomposition. Check out [this](https://gist.github.com/PhilipDukhov/5b1ed0cd925dc3a88a7392cbd4b2d1a1) example, and try to scroll the list - scroll position is gonna be reset on each recomposition. – Phil Dukhov Feb 11 '22 at 10:17
  • @Philip Dukhov You are correct, It didnt affect me as my lazyColumn was only composed once but I have extracted and persisted my liststate with remember so it solves the issue in a more general setting. let me know if its documented enough for you to reproduce. Unless you see any mistake from my side I will report this later on – quealegriamasalegre Feb 11 '22 at 10:52
  • With this solution it's not gonna save scroll position after device rotation, and if you made it `rememberSaveable` you'll end up duplicating `rememberLazyListState`, which is the default value and should be used at the end. Checking out on your code – Phil Dukhov Feb 11 '22 at 10:55
  • in fact I just realized that in the test code I provided not even my solution is working. not sure what changed. It only affects the second visible TestPart item before you scroll as seen in the gif – quealegriamasalegre Feb 11 '22 at 11:08

2 Answers2

3

This problem is due to the fact that you open the dialog from a lazy cell, which is located at the bottom of the screen. Therefore, when the keyboard appears, this cell becomes invisible and is removed from the view tree. I would say this is intended behavior.

I would suggest that you move the dialog from LazyColumn. Perhaps you need information about a particular item to be displayed inside the dialog, you can store the selected item instead of a boolean value:

val (selectedDialogItem, setSelectedItem) = remember { mutableStateOf<Int?>(null) }
LazyColumn {
    items(4) { i ->
        Row(Modifier.padding(8.dp)) {
            Text("part"+i)
            Button(onClick = {
                setSelectedItem(i)
            }) {
                Text("show dialog")
            }
        }
    }
}
if (selectedDialogItem != null) {
    Dialog(
        // ...
    )
}

p.s. I'm not using delegation here to allow selectedDialogItem smart cast inside if block

Phil Dukhov
  • 67,741
  • 15
  • 184
  • 220
0

I am convinced this is a bug that needs fixing by google however the simple solution is to add the state argument to the LazyColumn as shown.

val listState by remember{mutableStateOf(LazyListState())}
LazyColumn(state = listState  ){...}

I realize this is quite a niche bug but maybe it helps someone else save time

EDIT

This was clearly wrong and dindt always work. not sure why it worked the first time around

quealegriamasalegre
  • 2,887
  • 1
  • 13
  • 35