Hi, I am working on an AutoCompleteTextView/Expose...
# compose-desktop
t
Hi, I am working on an AutoCompleteTextView/ExposedDropdownMenuBox in Jetpack compose for Desktop. For some reason with my current implementation whenever I get a lot of items from my API returned the DropdownMenu overlaps with my TextField, how can I prevent this behaviour? I have already tried to calculate the height of the screen and subtract the TextField height but without much success. Is there a way I can force my Dropdown to always be below the TextField, maybe with some kinds of menuAnchor() behaviour like Android has?
Copy code
@Composable
fun TextFieldWithDropdown(
    modifier: Modifier = Modifier,
    value: String,
    onValueChange: (String) -> Unit,
    options: List<String>,
    onOptionClick: (String) -> Unit,
) {
    val density = LocalDensity.current

    var text by remember {
        mutableStateOf(value)
    }

    var expanded by remember(options) { mutableStateOf(options.isNotEmpty()) }

    var anchorSize by remember {
        mutableStateOf(DpSize.Zero)
    }

    Box(modifier) {
        TextField(
            modifier = Modifier
                .fillMaxWidth()
                .onGloballyPositioned {
                    anchorSize = with(density) {
                        DpSize(
                            width = it.size.width.toDp(),
                            height = it.size.height.toDp()
                        )
                    }
                }
                .onFocusChanged { focusState ->
                    if (!focusState.isFocused) {
                        expanded = false
                    }
                },
            singleLine = true,
            value = text,
            onValueChange = {
                text = it
                onValueChange(it)
            },
            colors = TextFieldDefaults.textFieldColors(
                backgroundColor = MaterialTheme.colors.surface,
                textColor = MaterialTheme.colors.onSurface,
                cursorColor = MaterialTheme.colors.onSurface,
            ),
        )


        DropdownMenu(
            modifier = Modifier
                .width(anchorSize.width),
            offset = DpOffset(0.dp, Dimens.normal),
            focusable = false,
            expanded = expanded,
            onDismissRequest = { expanded = false }
        ) {
            options.forEach { text ->
                DropdownMenuItem(
                    onClick = {
                        onOptionClick(text)
                    }
                ) {
                    Text(text = text)
                }
            }
        }
    }
}
a
You can look at the implementation of
ExposedDropdownMenuBox
and see how it does that.
This is how it computes the menu height:
Copy code
val windowInfo = LocalWindowInfo.current
    Box(
        modifier.onGloballyPositioned {
            width = it.size.width
            val boundsInWindow = it.boundsInWindow()
            val visibleWindowBounds = windowInfo.containerSize.toIntRect()
            val heightAbove = boundsInWindow.top - visibleWindowBounds.top
            val heightBelow = visibleWindowBounds.height - boundsInWindow.bottom
            menuHeight = max(heightAbove, heightBelow).toInt() - verticalMarginInPx
        }
    )
t
Thank you. I will look into that a bit more
a
I implemented this with my own Popup, though.
DropdownMenu
will refuse to position too close to the top/bottom of the window, which will look weird if your textfield is smaller than the standard material one.
image.png
and yeah looks like it will overlap the textfield if it’s too tall
There’s a bug in it.
The desired height is computed as
max(heightAbove, heightBelow).toInt() - verticalMarginInPx
but the popup positioning requires that it doesn’t extend into the vertical margin both at the top and the bottom.
Hmm, somehow it’s not happening on Android. I’ll look into it.
t
If you find anything then please let me know because its quite annoying as I have tried forcing its height and even that is being ignored somehow, making it impossible for me to properly calculate it. The only way that I can limit it is by hardcoding the value but that is not something I intent on doing
a
The height computation simply needs to be done differently.
Should be
Copy code
val visibleWindowBounds = IntRect(
                left = 0,
                top = verticalMarginInPx,
                right = windowInfo.containerSize.width,
                bottom = windowInfo.containerSize.height - 2*verticalMarginInPx
            )
Other than that, the above code should work.
Use the value of
menuHeight
above, convert it to
dp
and pass it to
Modifier.heightIn(max = menuHeightDp)
on the
DropdownMenu
t
Incredible. That does work indeed.
final code.kt
Just in case somebody ever needs it 🙂
Thanks for your help Alexander!
Do you maybe also know where I can read more about this
boundsInWindow
extension function? Looks quite interesting
a
cmd-click it
t
I have, of course 🙂 But I was looking for more of a written guide maybe?
a
It just converts local coordinates to window bounds
But anyway, why are you not simply using
ExposedDropdownMenuBox
?
t
I thought that didn't exist yet for Desktop?
a
It does, since latest release.
(but it will have that bug, until we fix it)
t
The fact that I missed it 🤦‍♂️
Though for now I'll probably be better of using this custom implementation until it is fixed 🙂