The error message was on screen. The keyboard was on top of it.
An automated UI test found the wrong-password message and passed, over and over. On a real phone, nobody could see it.
A password screen showed an error when the password was wrong. The emulator test suite covered eleven scenarios and passed four runs in a row. UI Automator reads the view hierarchy, the error text was in the hierarchy, so every assertion was green.
The first screenshot from a real Galaxy S25 told a different story. The message sat entirely behind the soft keyboard. From the user's side, the field cleared and nothing else happened.
Sign in
Welcome back
Enter your password to continue.
Incorrect password. Try again.
Sign in
Welcome back
Enter your password to continue.
Incorrect password. Try again.
imePadding() and bring-into-view.The first fix, adjustResize, worked on the S25 and did nothing on an Android 10 phone from another manufacturer. How the window reacts to the keyboard is still one of the places Android behaves differently across OS versions and OEMs. So we stopped relying on the window mode and handled the keyboard inset ourselves:
// In the Activity: draw edge-to-edge and own the insets,
// instead of hoping windowSoftInputMode behaves the same everywhere.
enableEdgeToEdge()
val requester = remember { BringIntoViewRequester() }
LaunchedEffect(error) {
if (error != null) requester.bringIntoView()
}
Column(
Modifier
.fillMaxSize()
.imePadding() // keep content above the keyboard
.verticalScroll(rememberScrollState())
) {
PasswordField(state)
if (error != null) {
Text(error, Modifier.bringIntoViewRequester(requester))
}
}Then we checked it the way a user would: screenshots from real devices, split by OS version and manufacturer, opened and looked at.
This comes from our mobile apps work.