This his how my composable looks like
#Composable
fun MyComposable(
email:String?,
onEmailChanged:(email)-> Unit,
buttonClicked:()->Unit,
validEmail:Boolean
){
val focusRequester = remember { FocusRequester() }
val focusManager = LocalFocusManager.current
val keyboardController = LocalSoftwareKeyboardController.current
TextField(
modifier = Modifier
.fillMaxWidth()
.focusRequester(focusRequester = focusRequester),
value = email ?: "",
status = someStatus // Default, Error, Success
onValueChange = { email -> onEmailChanged(email)},
)
Button(
onClick = {
focusManager.clearFocus()
buttonClicked()
if(!validEmail) focusRequester.requestFocus()
},
) {
Text(text = "Button")
}
}
}
I have added the basic code for explanation, i am facing issue with focusRequester when a button is clicked.
For accessibility reasons once the button is clicked, and if the email is invalid, i would like the focus to go back to TextField so that accessibility can announce it's state and label.
I tried clearing the focus and then requesting it again, but it only works for the first time. And i would like the TextField to gain focus every-time a button is clicked and email is invalid.
Am i missing something here?
try this it works totally fine.
#Composable
fun Test() {
val context = LocalContext.current
val emailState = remember { mutableStateOf("") }
val focusRequester = remember { FocusRequester() }
val focusManager = LocalFocusManager.current
Column {
TextField(
modifier = Modifier
.focusRequester(focusRequester)
.fillMaxWidth(),
value = emailState.value,
onValueChange = {
emailState.value = it
},
)
Button(onClick = {
focusManager.clearFocus()
if (emailState.value.isEmpty() ) {
focusRequester.requestFocus()
Toast.makeText(context, "error", Toast.LENGTH_SHORT).show()
}
}) { Text(text = "Button")
}
}
}
I couldn't find any way to get the focus back on TextField if it was already focused, once the button was clicked.
I ended up using live-region for announcement.
TextField(
modifier = Modifier
.fillMaxWidth()
.semantics {
liveRegion = LiveRegionMode.Assertive
}),
value = email ?: "",
status = someStatus // Default, Error, Success
onValueChange = { email -> onEmailChanged(email)},
)
Button(
onClick = {
focusManager.clearFocus()
buttonClicked()
},
) {
Text(text = "Button")
}
}
Related
I have an OutlineTextField where I am editing and updating it by calling an API on click of Save button.
Now what I want if the user doesn't change the text, api call should not happen and then onclick of save , there should not be any api call and it should got to the previous screen.
Below is my code snippet:
OutlinedTextField(
value = value,
modifier = modifier,
onValueChange = onValueChange,
placeholder = PlaceholderComponent
)
I solved it by checking initial viewmodel text.
If I've understood the question clearly, as #Gabriele Mariotti suggested, you can store the previous value of the text field and then compare it with the actual in the text field itself on the button click.
You can arrange the code with a Composable function like so:
#Composable
fun SaveButton(
modifier: Modifier = Modifier
) {
var currentValue by remember { mutableStateOf("") }
var previousValue by remember { mutableStateOf(currentValue) }
val context = LocalContext.current
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceEvenly,
modifier = Modifier.fillMaxSize()
) {
OutlinedTextField(
value = currentValue,
modifier = modifier,
onValueChange = {
currentValue = it
}
)
Button(onClick = {
if (currentValue != previousValue) {
Toast.makeText(context, "API request started", Toast.LENGTH_SHORT).show()
previousValue = currentValue
// Handle API request
} else {
Toast.makeText(context, "The text has not changed. Returning to the previous screen...", Toast.LENGTH_LONG).show()
// Handle on back screen
}
}) {
Text(text = "Save")
}
}
}
Upon clicking a FAB, the app opens a Dialog with a camera icon in it, including text fields. Clicking the icon does not open the ModalBottomSheetLayout inside the Dialog as it should; It opens it in the parent screen that contains the FAB. How can make it open inside the Dialog?
Screen Recording
Main Screen Composable:
#OptIn(ExperimentalMaterialApi::class)
#Composable
fun AddItemDialogWithModalSheet(
onDismiss: () -> Unit,
) {
val sheetState = rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden)
val scope = rememberCoroutineScope()
ModalBottomSheetLayout(
sheetContent = {
Column() {
Text(text = "Testing")
Text(text = "Testing")
Text(text = "Testing")
Text(text = "Testing")
Text(text = "Testing")
Text(text = "Testing")
}
},
sheetState = sheetState,
) {
AddItemDialog(
onConfirmClicked = { /*TODO*/ },
onDismiss = onDismiss,
onCameraClick = {
scope.launch {
sheetState.show()
}
}
)
}
}
Dialog Composable:
#OptIn(ExperimentalComposeUiApi::class)
#Composable
fun AddItemDialog(
onConfirmClicked: () -> Unit,
onDismiss: () -> Unit,
onCameraClick: () -> Unit
) {
val nameText = rememberSaveable { mutableStateOf("") }
val quantityText = rememberSaveable { mutableStateOf("") }
val unitText = rememberSaveable { mutableStateOf("") }
val ppuText = rememberSaveable { mutableStateOf("") }
val notesText = rememberSaveable { mutableStateOf("") }
var categoryText = rememberSaveable { mutableStateOf("") }
Dialog(
onDismissRequest = onDismiss,
properties = DialogProperties(usePlatformDefaultWidth = false),
) {
Surface(
shape = MaterialTheme.shapes.medium,
color = MaterialTheme.colors.surface,
modifier = Modifier
.width(LocalConfiguration.current.screenWidthDp.dp * 0.96f)
.padding(4.dp)
) {
...
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
modifier = Modifier
.fillMaxWidth()
.requiredHeight(70.dp)
.padding(10.dp)
) {
Icon(
painterResource(id = R.drawable.ic_baseline_photo_camera_24),
contentDescription = "Add Item Picture",
modifier = Modifier
.size(40.dp).clickable(onClick = {
onCameraClick() //this code does not open the bottom sheet
})
)
}
}
}
}
I don't think this is a good idea in terms of UX.
My suggestion is follow the Material Design guidelines and use the Full Screen Dialog. See what the documentation says:
Full-screen dialogs may be used for content or tasks that meet any of
these criteria:
Dialogs that include components which require keyboard input, such as form fields;
When changes aren’t saved instantly;
When components within the dialog open additional dialogs.
Your UI matches all the criteria to use a full-screen dialog.
I'm trying to implement a TextField in Jetpack Compose with the following functionality: at first it is disabled, but when a user presses the Button, it gets enabled and at the same moment receives focus. This was my approach:
var text by remember { mutableStateOf("text") }
var enabled by remember { mutableStateOf(false)}
val focusRequester = remember { FocusRequester() }
Column {
TextField(
value = text,
onValueChange = { text = it },
enabled = enabled,
modifier = Modifier.focusRequester(focusRequester),
textStyle = TextStyle(fontSize = 24.sp)
)
Button(onClick = {
enabled = true
focusRequester.requestFocus()
}) {
Text("Enable and request focus")
}
But when the button is pressed, the TextField only gets enabled, not focused. To focus it, user has to click it once again. What am I doing wrong and what is the possible workaround?
You have to listen the change of the enabled parameter to give the focus to the TextField.
You can change your code to:
Button(onClick = {
enabled = true
}) {
Text("Enable and request focus")
}
LaunchedEffect(enabled) {
if (enabled){
focusRequester.requestFocus()
}
}
Simply add the delay after enabling textfield like this:
var text by remember { mutableStateOf("text") }
var enabled by remember { mutableStateOf(false)}
val focusRequester = remember { FocusRequester() }
val scope = rememberCoroutineScope()
Column {
TextField(
value = text,
onValueChange = { text = it },
enabled = enabled,
modifier = Modifier.focusRequester(focusRequester),
textStyle = TextStyle(fontSize = 24.sp)
)
Button(onClick = {
scope.launch {
enabled = true
delay(100)
focusRequester.requestFocus()
}
}) {
Text("Enable and request focus")
}
}
I once had a similar issue and I solved it by using interactionSource. The code looked something like this:
var text by remember { mutableStateOf("text") }
var enabled by remember { mutableStateOf(false)}
val focusRequester = remember { FocusRequester() }
val interactionSource = remember { MutableInteractionSource() } // add this
Column {
TextField(
value = text,
onValueChange = { text = it },
enabled = enabled,
modifier = Modifier
.focusable(interactionSource = interactionSource) // and this
.focusRequester(focusRequester),
textStyle = TextStyle(fontSize = 24.sp)
)
...
IIRC it was important to have .focusable() and .focusRequester() in the right order, but cannot remember which exactly, so try to swap them if it won't work immediately.
I want the keyboard to pop up by an auto requesting focus on a text field in jetpack compose when the user navigates to a composable. As of now, this is what I have tried but it doesn't seem to work
val feedbackContent = remember { mutableStateOf(TextFieldValue()) }
val focusRequester = remember { FocusRequester() }
OutlinedTextField(
modifier = Modifier
.clickable {
focusRequester.requestFocus()
}
.fillMaxWidth()
.focusRequester(focusRequester)
.focusable()
)
You can use something like:
val focusRequester = FocusRequester()
val keyboardController = LocalSoftwareKeyboardController.current
OutlinedTextField(
value = text,
onValueChange = { text = it},
modifier = Modifier
.fillMaxWidth()
.focusRequester(focusRequester)
.onFocusChanged {
if (it.isFocused) {
keyboardController?.show()
}
}
)
DisposableEffect(Unit) {
focusRequester.requestFocus()
onDispose { }
}
I have textField composable and button composable. I want that clicking at the button would erase the text in the textField composable.
example:
var text by remember
mutableStateOf(TextFieldValue(""))}
TextField(
value = text,
onValueChange = { newValue -> text = newValue },
modifier = Modifier
.padding(8.dp),
)
Button(
onClick = {
//TODO: clean the text in textFiled
},
modifier = Modifier
.size(200.dp, 40.dp)
) {
Text(text = "erase textField"
}
thanks
You can just simply reset the value of the text mutableState:
Button(onClick = { text = TextFieldValue("") })
Create a mutableState as follows -> var textState by remember { mutableStateOf("") }
Create Textfield -> TextField(value = textState, onValueChange = { textState = it })
In the onClick of the button invoke the textState -> textState = ""