Jetpack Compose πŸ› Keyboard Types switch on focus change

jetpackcompose Nov 12, 2021

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 Hattoncheckout 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.



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))
        }
    }

}



This would result in the Following Behaviour :

Fix Preview :


Vola! its working Wonderfully , Checkout the complete code on the github :

GitHub - ch8n/Compose-OtpView: Jetpack Compose OTP view implementation
Jetpack Compose OTP view implementation. Contribute to ch8n/Compose-OtpView development by creating an account on 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! πŸ‘©β€πŸ’»


Enjoy the article?

a clap is much appreciated if you enjoyed. No sign up or cost associated :)




Chetan gupta

Hi there! call me Ch8n, I'm a mobile technology enthusiast! love Android #kotlinAlltheWay, CodingMantra: #cleanCoder #TDD #SOLID #designpatterns

Great! You've successfully subscribed.
Great! Next, complete checkout for full access.
Welcome back! You've successfully signed in.
Success! Your account is fully activated, you now have access to all content.