Jetpack Compose π Keyboard Types switch on focus change
If you have ever tried to create your own PIN or OTP view you might have faced pain.
Jetpack compose even though is been stabilised for production use, some functional bugs are still expected to exist.
One of the same bugs which I like to discuss in this article is about keyboard type switching from alphanumeric to numeric type whenever focused and could be faced easily while making UI design such as an OTP or PIN view in Compose.
Preview of Bug :
You can see this issue on Google Issue Tracker :
https://issuetracker.google.com/issues/187746439
On publishing of this article status of the bug is to be resolved is unknown, this means you will be stuck with this behavior until the google team pick it up.
It won't entirely fix the bug but at least put a bandage over it.
Heads up! I didn't come up with the hack, the owner of the idea is Β
Chris Hatton
checkout his LinkedIn from here.
Chris Suggested :We can sidestep a lot of such issues by making the input part a large, invisible, single text field. The large visual digits merely reflect the content of the single invisible field.
Which Translated to :
1.We can create a composable which is just the UI representation of the OTP character.
2.This Composable Would receive its value from a hidden TextField
Context behind which this suggestion was to have only one TextField
instead of 4 which will avoid switching between TextFields thus Keyboard switching.
Enjoying the Post?
a clap is much appreciated if you enjoyed. No sign up or cost associated :)
If you want to support my content do consider dropping a tip/coffee/donationπ°
You can see my implementation Below :
OTP Cell UI Composable
@Composable
fun OtpCell(
modifier: Modifier,
value: String,
isCursorVisible: Boolean = false
) {
val scope = rememberCoroutineScope()
val (cursorSymbol, setCursorSymbol) = remember { mutableStateOf("")
// Cursor blinking logic
LaunchedEffect(key1 = cursorSymbol,isCursorVisible) {
if (isCursorVisible){
scope.launch {
delay(350)
setCursorSymbol(if (cursorSymbol.isEmpty()) "|" else "")
}
}
}
// UI for OTP Character
Box(
modifier = modifier
) {
Text(
text = if (isCursorVisible) cursorSymbol else value,
style = MaterialTheme.typography.body1,
modifier = Modifier.align(Alignment.Center)
)
}
}
This composable is how a OTP cell looks in the UI, next below is the logic of controller for updating this
OTP Controller Composable
@ExperimentalComposeUiApi
@Composable
fun OtpBugView() {
val (editValue, setEditValue) = remember { mutableStateOf("") }
val otpLength = remember { 4 }
val focusRequester = remember { FocusRequester() }
val keyboard = LocalSoftwareKeyboardController.current
// hidden TextField for controlling OTP Cells UI
TextField(
value = editValue,
onValueChange = {
if (it.length <= otpLength) {
setEditValue(it)
}
},
modifier = Modifier
.size(0.dp)
.focusRequester(focusRequester),
keyboardOptions = KeyboardOptions.Default.copy(
keyboardType = KeyboardType.Number
)
)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Center
) {
(0 until otpLength).map { index ->
OtpCell(
modifier = Modifier
.size(36.dp)
.clickable {
focusRequester.requestFocus()
// this is required so if keyboard is dissmissed
// then could be open again with focus
keyboard?.show()
}
.border(1.dp, Color.DarkGray),
value = editValue.getOrNull(index)?.toString() ?: "",
isCursorVisible = editValue.length == index
)
Spacer(modifier = Modifier.size(8.dp))
}
}
}
Enjoying the Post?
a clap is much appreciated if you enjoyed. No sign up or cost associated :)
If you want to support my content do consider dropping a tip/coffee/donationπ°
This would result in the Following Behaviour :
Fix Preview :
Vola! its working Wonderfully , Checkout the complete code on the github :
Conclusion ππ»ββοΈ
Thatβs all fokes! Hope this help someone.
P.S. Thanks Chris for logic again!
Hope you find it informative and if you have any feedback
or post request
or want to subscribe to my mailing list
forms are below.
Do consider Clap
to show your appreciation, until next time. Happy Hacking! π©βπ»
Enjoying the Post?
a clap is much appreciated if you enjoyed. No sign up or cost associated :)
If you want to support my content do consider dropping a tip/coffee/donationπ°
Reach me out :
