Hi all, I'm having an issue with compose, status b...
# compose
m
Hi all, I'm having an issue with compose, status bar padding and the soft keyboard. I'll put the code in the thread, but in short, when the soft keyboard is coming up, in order to keep the focused text field in view, it's shifting all the other content up, and it ends up under the status bar. I have tried adjusting the soft window input mode to other things, but I can't really find a decent option. Ideally i'd want the part under the status bar to not be visible. But in this example, you can see that the blue block (a placeholder for a real image in our production code) is going under the status bar making it unreadable depending on what color that block is.
Copy code
import androidx.activity.*
import androidx.activity.compose.LocalActivity
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Arrangement.spacedBy
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.painter.ColorPainter
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp

@Composable
@Preview
fun ImeStatusBarIssue() {
    MaterialTheme {
        val activity = LocalActivity.current
        SideEffect {
            (activity as? ComponentActivity)?.let {
                it.enableEdgeToEdge(
                    statusBarStyle = SystemBarStyle.light(Color.Transparent.toArgb(), Color.Transparent.toArgb()),
                    navigationBarStyle = SystemBarStyle.light(Color.Transparent.toArgb(), Color.Transparent.toArgb())
                )
            }
        }

        Scaffold { contentPadding ->
            Column(modifier = Modifier.padding(contentPadding)) {
                Column(
                    modifier = Modifier.weight(1f, true),
                    verticalArrangement = spacedBy(16.dp)
                ) {
                    Image(
                        modifier = Modifier.fillMaxWidth().height(400.dp),
                        painter = ColorPainter(Color.Blue),
                        contentDescription = null
                    )

                    Text(text = "Title", style = MaterialTheme.typography.headlineLarge)

                    Text(text = "Subtitle", style = MaterialTheme.typography.bodyLarge)

                    var text1 by remember { mutableStateOf("") }
                    OutlinedTextField(
                        label = { Text("Text1") },
                        placeholder = { Text("Enter Text") },
                        value = text1,
                        onValueChange = { text1 = it }
                    )

                    var text2 by remember { mutableStateOf("") }
                    OutlinedTextField(
                        label = { Text("Text2") },
                        placeholder = { Text("Enter Text") },
                        value = text2,
                        onValueChange = { text2 = it }
                    )

                }

                Button(
                    modifier = Modifier.fillMaxWidth(.5f).align(Alignment.CenterHorizontally),
                    onClick = { }
                ) {
                    Text("Click Me")
                }
            }
        }
    }
}
a
That looks like
adjustPan
behavior - what does
adjustResize
give you?
m
Use imePadding for keyboard and displayCutoutPadding for status bar. These are modifiers you can use anywhere
m
If I use adjustResize (which is deprecated by the way), it doesn't really moving anything, and the text field gets obscured. Also manually using the padding does nothing, as the M3 Saffold function automatically should be handling all the insets for me, and it does, other than the case where the keyboard is on screen
@Alex Vanyo Is there a way to maybe monitor the offset? I presume that the default behavior above is produced by changing the offset of the screen content so that the Y offset is some negative value. I'm wondering if i can monitor that, and detect when the image overlaps the status bar and then set the status bar to dark instead of light. Otherwise, i'm not really sure how to fix this.
m
@mattinger Did you try .imePadding modifier?
a
(which is deprecated by the way)
You should be able to use
adjustNothing
on API 30+, and
adjustResize
on API 29 and below, with the combination of handling insets directly.
Scaffold
does handle most types of insets directly by default, but it explicitly doesn’t handle ime insets by default - the reason for this is that it ends up being weird of bottom bars end up being adjust for insets. Adding `adjustResize`/`adjustNothing` and adding
imePadding()
or including
WindowInsets.ime
in the insets that
Scaffold
does take into account should do the trick
I’m wondering if i can monitor that, and detect when the image overlaps the status bar and then set the status bar to dark instead of light. Otherwise, i’m not really sure how to fix this.
adjustPan
is really hard for apps to handle directly - the best way to do the above is to switch to
adjustResize
and get that information with the inset APIs
m
@Mohit @Alex Vanyo, so
imePadding()
works to some degree, but the issue is that imePadding applies some padding at the bottom as well for the keyboard. We have bottom anchored Buttons in our layout, it's trying to keep that button on screen. I've tried just applying the top of the ime insets but it doesn't do anything:
Copy code
val topImeInsetDp = with (LocalDensity.current) {
            WindowInsets.ime.getTop(this).toDp()
        }

        modifier = Modifier.windowInsetsPadding(insets = WindowInsets(top = topImeInsetDp)),
I've also tried different soft input modes with no success
👍 1
m
Copy code
@Composable
@Preview
fun ImeStatusBarIssue() {
    MaterialTheme {
        val activity = LocalActivity.current
        SideEffect {
            (activity as? ComponentActivity)?.let {
                it.enableEdgeToEdge(
                    statusBarStyle = SystemBarStyle.light(Color.Transparent.toArgb(), Color.Transparent.toArgb()),
                    navigationBarStyle = SystemBarStyle.light(Color.Transparent.toArgb(), Color.Transparent.toArgb())
                )
            }
        }

        Scaffold { contentPadding ->
            Column(modifier = Modifier.padding(contentPadding).imePadding()) {
                Column(
                    modifier = Modifier.weight(1f, true).verticalScroll(
                        rememberScrollState()
                    ),
                    verticalArrangement = spacedBy(16.dp)
                ) {
                    Image(
                        modifier = Modifier.fillMaxWidth().height(400.dp),
                        painter = ColorPainter(Color.Blue),
                        contentDescription = null
                    )

                    Text(text = "Title", style = MaterialTheme.typography.headlineLarge)

                    Text(text = "Subtitle", style = MaterialTheme.typography.bodyLarge)

                    var text1 by remember { mutableStateOf("") }
                    OutlinedTextField(
                        label = { Text("Text1") },
                        placeholder = { Text("Enter Text") },
                        value = text1,
                        onValueChange = { text1 = it },
                    )

                    var text2 by remember { mutableStateOf("") }
                    OutlinedTextField(
                        label = { Text("Text2") },
                        placeholder = { Text("Enter Text") },
                        value = text2,
                        onValueChange = { text2 = it }
                    )
                }
                
                Spacer(Modifier.height(20.dp))

                Button(
                    modifier = Modifier.fillMaxWidth(.5f).align(Alignment.CenterHorizontally),
                    onClick = { }
                ) {
                    Text("Click Me")
                }
            }
        }
    }
}
m
@Mohit That doesn't really solve the issue of the button being visible. What i'd like is for the button to be under the keyboard (well not me, per-se, the team i'm supporting).
a
I think what you’re looking for is something like this (with
adjustResize
/
adjustNothing
):
Copy code
Scaffold { contentPadding ->
    Box(modifier = Modifier.padding(contentPadding).consumeWindowInsets(contentPadding)) {
        var bottomHeight by remember { mutableIntStateOf(0) }

        Column(
            Modifier
                .align(Alignment.BottomCenter)
                .zIndex(1f)
                .layout { measurable, constraints ->
                    val placeable = measurable.measure(constraints)
                    bottomHeight = placeable.height
                    layout(placeable.width, placeable.height) {
                        placeable.placeRelative(0, 0)
                    }
                }
        ) {
            Spacer(Modifier.height(20.dp))

            Button(
                modifier = Modifier.fillMaxWidth(.5f).align(Alignment.CenterHorizontally),
                onClick = { }
            ) {
                Text("Click Me")
            }
        }

        val density = LocalDensity.current
        val bottomPaddingValues = remember(density) {
            object : PaddingValues {
                override fun calculateBottomPadding(): Dp =
                    with(density) { bottomHeight.toDp() }
                override fun calculateLeftPadding(layoutDirection: LayoutDirection): Dp = 0.dp
                override fun calculateRightPadding(layoutDirection: LayoutDirection): Dp = 0.dp
                override fun calculateTopPadding(): Dp = 0.dp
            }
        }

        Column(
            modifier = Modifier
                .fillMaxSize()
                .padding(bottomPaddingValues)
                .consumeWindowInsets(bottomPaddingValues)
                .imePadding()
                .verticalScroll(
                    rememberScrollState()
                ),
            verticalArrangement = spacedBy(16.dp)
        ) {
            Image(
                modifier = Modifier.fillMaxWidth().height(400.dp),
                painter = ColorPainter(Color.Blue),
                contentDescription = null
            )

            Text(text = "Title", style = MaterialTheme.typography.headlineLarge)

            Text(text = "Subtitle", style = MaterialTheme.typography.bodyLarge)

            var text1 by remember { mutableStateOf("") }
            OutlinedTextField(
                label = { Text("Text1") },
                placeholder = { Text("Enter Text") },
                value = text1,
                onValueChange = { text1 = it },
            )

            var text2 by remember { mutableStateOf("") }
            OutlinedTextField(
                label = { Text("Text2") },
                placeholder = { Text("Enter Text") },
                value = text2,
                onValueChange = { text2 = it }
            )
        }
    }
}
The logic goes something like: You want to apply padding to the entire scrollable content, such that it aligns with the top of the bottom section, or the top of the ime insets, whichever is greater. You can do this by measuring the size of the bottom section first, and then applying that as padding to the body section - and consuming that padding from the amount of window insets to apply. Then applying
imePadding()
will pad the body content the remaining amount (if needed)
m
I guess this will work
Copy code
@Composable
@Preview
fun ImeStatusBarIssue() {
    MaterialTheme {
        val activity = LocalActivity.current
        SideEffect {
            (activity as? ComponentActivity)?.let {
                it.enableEdgeToEdge(
                    statusBarStyle = SystemBarStyle.light(Color.Transparent.toArgb(), Color.Transparent.toArgb()),
                    navigationBarStyle = SystemBarStyle.light(Color.Transparent.toArgb(), Color.Transparent.toArgb())
                )
            }
        }
        val focusRequester = remember { FocusRequester() }

        Scaffold { contentPadding ->
            Column(modifier = Modifier.padding(contentPadding)) {
                Column(
                    modifier = Modifier.weight(1f, true)
                        .verticalScroll(rememberScrollState()),
                    verticalArrangement = spacedBy(16.dp)
                ) {
                    Image(
                        modifier = Modifier.fillMaxWidth().height(400.dp),
                        painter = ColorPainter(Color.Blue),
                        contentDescription = null
                    )

                    Text(text = "Title", style = MaterialTheme.typography.headlineLarge)

                    Text(text = "Subtitle", style = MaterialTheme.typography.bodyLarge)
                    
                    var text1 by remember { mutableStateOf("") }
                    OutlinedTextField(
                        label = { Text("Text1") },
                        placeholder = { Text("Enter Text") },
                        value = text1,
                        onValueChange = { text1 = it }
                    )

                    var text2 by remember { mutableStateOf("") }
                    OutlinedTextField(
                        label = { Text("Text2") },
                        placeholder = { Text("Enter Text") },
                        value = text2,
                        onValueChange = { text2 = it }
                    )
                    Spacer(Modifier.height(20.dp))
                }

                Button(
                    modifier = Modifier.fillMaxWidth(.5f).align(Alignment.CenterHorizontally),
                    onClick = { }
                ) {
                    Text("Click Me")
                }
            }
        }
    }
}