Search code examples
androidandroid-jetpack-composeandroid-alertdialog

Jetpack Compose - imePadding() for AlertDialog


The issue I faced was that I needed AlertDialog with some kind of List items (e. g. LazyColumn) and the TextField to search across these items. I wanted to display all the Dialog layout even when Keyboard is opened. But what I got is a Keyboard that cover some part of Dialog layout itself. I tried to use imePadding() for Dialog's Modifier but seems that Dialog ignoring that. I didn't find any solution for this on the Internet.

My code looks like so:

AlertDialog(
    modifier = Modifier.fillMaxWidth()
        .padding(AppTheme.margins.edge)
        .imePadding(),
    onDismissRequest = {
        searchText = TextFieldValue("")
        viewModel.clearSearchQuery()
        dismissCallback?.invoke()
    },
    text = {
           Column(
                modifier = Modifier.wrapContentHeight()
            ) {
                Text(
                    text = stringResource(R.string.dlg_select_content_title),
                    style = AppTheme.textStyles.hugeTitleText
                )
                OutlinedTextField(
                    modifier = Modifier
                        .fillMaxWidth()
                        .padding(top = AppTheme.margins.divRegular),
                    value = searchText,
                    placeholderText = stringResource(R.string.dlg_select_content_search_placeholder),
                    onValueChange = { newValue ->
                        searchText = newValue
                        viewModel.onSearchTextTyped(newValue.text)
                    }
                )

                RadioGroup(
                    modifier = Modifier
                        .verticalScroll(rememberScrollState()),
                    options = labels.map {
                        RadioOption(
                            title = it.name,
                            description = null,
                            selected = vmState.selectedLabel?.id == it.id,
                            tag = it.id
                        )
                    },
                    onOptionSelected = {
                        searchText = TextFieldValue("")
                        viewModel.clearSearchQuery()
                        viewModel.saveLabelSelection(it.tag as Int) {
                            dismissCallback?.invoke()
                        }
                    }
                )
            }
    },
    properties = DialogProperties(
        usePlatformDefaultWidth = false
    ),
    confirmButton = {
        // Nothing
    }
)

And the result:

enter image description here

I am not able to interact with several last items in list because Keyboard covers it.


Solution

  • I have implemented a solution for this issue. The solution is quite ugly, but working. If someone knows a more elegant solution, feel free to write it in an answer in this question.

    Even though the dialog ignores the imePadding() we still can set the height. So, first of all we should to know what screen height available above keyboard.

    @Composable
    private fun TrickyHeight(
        onHeightChanged: (Dp) -> Unit,
    ) {
        val density = LocalDensity.current
        Box(
            modifier = Modifier
                .fillMaxSize()
                .imePadding()
                .padding(bottom = 30.dp) // additional padding
                .onSizeChanged {
                    onHeightChanged.invoke(with(density) { it.height.toDp() })
                }
        )
    }
    

    Next step is to create wrapper over AlertDialog:

    @Composable
    fun TrickyDialog(
        onDismissRequest: () -> Unit,
        confirmButton: @Composable () -> Unit,
        dismissButton: @Composable (() -> Unit)? = null,
        icon: @Composable (() -> Unit)? = null,
        title: @Composable (() -> Unit)? = null,
        text: @Composable (() -> Unit)? = null,
        shape: Shape = AlertDialogDefaults.shape,
        containerColor: Color = AppTheme.colors.surfaceColor,
        iconContentColor: Color = AlertDialogDefaults.iconContentColor,
        titleContentColor: Color = AlertDialogDefaults.titleContentColor,
        textContentColor: Color = AlertDialogDefaults.textContentColor,
        tonalElevation: Dp = AlertDialogDefaults.TonalElevation,
        properties: DialogProperties = DialogProperties()
    ) {
    
        val maxDialogHeight = remember { mutableStateOf(0.dp) }
        TrickyHeight(onHeightChanged = { maxDialogHeight.value = it })
    
        AlertDialog(
            modifier = Modifier
                .fillMaxWidth()
                .heightIn(0.dp, maxDialogHeight.value)
                .padding(AppTheme.margins.edge),
            onDismissRequest = onDismissRequest,
            confirmButton = confirmButton,
            dismissButton = dismissButton,
            icon = icon,
            title = title,
            text = text,
            shape = shape,
            containerColor = containerColor,
            iconContentColor = iconContentColor,
            titleContentColor = titleContentColor,
            textContentColor = textContentColor,
            tonalElevation = tonalElevation,
            properties = properties
        )
    }
    

    Also, do not forget to add correct android:windowSoftInputMode in Manifest: android:windowSoftInputMode="adjustResize"

    Now you can use TrickyDialog instead of AlertDialog. Again, this solution is not elegant. But maybe it will be helpful for someone who faced the same issue. Also, this solution will not work properly for Landscape Screen Orientation.

    enter image description here