requestFocus() also triggers onClick() - android

I have a TextField and a Button. First on screen load TextField is focused. When I press ENTER while I am on the TextField, the button gets focused. The problem is that when the button gets focused, it also triggers its onClick() method, thus "Clicked on Button" is printed.
How to prevent when calling focusRequester[1].requestFocus() to trigger Button's onClick() method? I only need to focus that button.
val focusRequester = remember { listOf(FocusRequester(), FocusRequester()) }
val focusedState = remember { mutableStateListOf(true, false) }
Column {
OutlinedTextField(
value = TextFieldValue("test"),
onValueChange = { },
modifier = Modifier
.onKeyEvent {
if (it.type == KeyEventType.KeyDown) {
if (it.key.keyCode == Key.Enter.keyCode) {
focusRequester[1].requestFocus()
return#onKeyEvent true
}
}
false
}
.focusRequester(focusRequester[0])
.onFocusChanged {
focusedState[0] = it.isFocused
}
.focusable(),
colors = textFieldColors(focusedState[0]))
OutlinedButton(modifier = Modifier
.focusRequester(focusRequester[1])
.onFocusChanged {
focusedState[1] = it.isFocused
}
.focusable(),
colors = getColorByFocusedState(focusedState[1]),
onClick = { println("Clicked on Button.") }) {
Text(text = "Button")
}
}
LaunchedEffect(Unit) {
focusRequester[0].requestFocus()
}

Related

Jetpack Compose Popup's TextField cannot received keyboard input

I have the below code where I have TextField in the ComposeView as well as the Popup.
#Composable
fun MyCompose() {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
var popupControl by remember { mutableStateOf(false) }
Button(onClick = { popupControl = true }) {
Text("Add Record")
}
if (popupControl) {
Popup(onDismissRequest = { popupControl = false }) {
var popupText by rememberSaveable { mutableStateOf("") }
TextField(
value = popupText,
onValueChange = { popupText = it },
label = { Text("Title") }
)
}
}
var mainText by rememberSaveable { mutableStateOf("") }
TextField(
value = mainText,
onValueChange = { mainText = it },
label = { Text("Title") }
)
}
}
The main TextField works fine able to receive keyboard input.
However when I get the Popup open up, although I can focus on the Popup's TextField, it won't receive the Keyboard input.
Instead, all input continue to be received by the Main TextField
How to fix this issue?
Looks like I need to setup the Popup property to make it focusable
Popup(
onDismissRequest = { popupControl = false },
properties = PopupProperties(focusable = true)
) { ... }

Animate visibility in compose

I have a text which need to be animated to show and hide with the value is null or not. it would have been straight forward if the visibility is separately handle, but this is what I got.
In the bellow code the enter animation works but the exit animation dont as the text value is null.
I can think of something with remembering the old value but not sure how.
#Composable
fun ShowAnimatedText(
text : String?
) {
Column(
modifier = Modifier.fillMaxWidth()
) {
AnimatedVisibility(
visible = text != null,
enter = fadeIn(animationSpec = tween(2000)),
exit = fadeOut(animationSpec = tween(2000))
) {
text?.let {
Text(text = it)
}
}
}
}
I think the fade-out animation is actually working "per-se".
I suspect the parameter text: String? is a value coming from a hoisted "state" somewhere up above ShowAnimatedText, and since you are directly observing it inside the animating scope, when you change it to null it instantly removes the Text composable, and your'e not witnessing a slow fade out.
AnimatedVisibility(
...
) {
text?.let { // your'e directly observing a state over here
Text(text = it)
}
}
This is my attempt completing your snippet based on my assumption and making it work, the fade-in works, but the desired fade-out is instantly happening.
#Composable
fun SomeScreen() {
var text by remember {
mutableStateOf<String?>("Initial Value")
}
Row(
modifier = Modifier.fillMaxWidth()
) {
Button(onClick = {
text = "New Value"
}) {
Text("Set New Value")
}
Button(onClick = {
text = null
}) {
Text("Remove Value")
}
AnimatedText(text = text)
}
}
#Composable
fun ShowAnimatedText(
text : String?
) {
Column(
modifier = Modifier.fillMaxWidth()
) {
AnimatedVisibility(
visible = text != null,
enter = fadeIn(animationSpec = tween(2000)),
exit = fadeOut(animationSpec = tween(2000))
) {
text?.let {
Text(text = it)
}
}
}
}
You can solve it by modifying the text to a non-state value and change your visibility logic from using a nullability check to some "business logic" that would require it to be visible or hidden, modifying the codes above like this.
#Composable
fun SomeScreen() {
var show by remember {
mutableStateOf(true)
}
Row(
modifier = Modifier.fillMaxWidth()
) {
Button(onClick = {
show = !show
}) {
Text("Set New Value")
}
AnimatedText(text = "Just A Value", show)
}
}
#Composable
fun ShowAnimatedText(
text : String?,
show: Boolean
) {
Column(
modifier = Modifier.fillMaxWidth()
) {
AnimatedVisibility(
visible = show,
enter = fadeIn(animationSpec = tween(2000)),
exit = fadeOut(animationSpec = tween(2000))
) {
text?.let {
Text(text = it)
}
}
}
}
I fixed it by remembering the previous state (Or don't set the null value) until the exit animation is finished, if the text is null.
Thank you z.y for your suggestion.
#Composable
fun ShowAnimatedText(
text : String?,
show: Boolean
) {
var localText by remember {
mutableStateOf<String?>(null)
}
AnimatedContent(show, localText)
LaunchedEffect(key1 = text, block = {
if(text == null){
delay(2000)
}
localText = text
})
}
#Composable
private fun AnimatedContent(show: Boolean, localText: String?) {
Column(
modifier = Modifier.fillMaxWidth()
) {
AnimatedVisibility(
visible = show,
enter = fadeIn(animationSpec = tween(2000)),
exit = fadeOut(animationSpec = tween(2000))
) {
localText?.let {
Text(text = it)
}
}
}
}

Why doesn't the edit box dialog fill in initial value when I use Jetpack Compose?

I use the following code to show a edit box dialog with initial value on which a use can input a new description and save it.
I think that the initial value "Hello" will be shown on TextField when I click the "Edit Description" button, but in fact, none is shown on TextField.
What's wrong with my code?
#Composable
fun ScreenDetail(
) {
var editDialog by remember { mutableStateOf(false) }
var description by remember {mutableStateOf("") }
editDialog(
isShow = editDialog,
onDismiss = { editDialog =false },
onConfirm ={ ... },
editFieldContent=description
)
Button(
modifier = Modifier,
onClick = {
description = "Hello"
editDialog = true
}
) {
Text("Edit Description")
}
}
#Composable
fun editDialog(
isShow: Boolean,
onDismiss: () -> Unit,
onConfirm: (String) -> Unit,
saveTitle: String = "Save",
cancelTitle:String = "Cancel",
dialogTitle:String ="Edit",
editFieldTitle:String ="Input description",
editFieldContent:String ="",
) {
var mText by remember { mutableStateOf(editFieldContent) }
if (isShow) {
AlertDialog(
confirmButton = {
TextButton(onClick = { onConfirm(mText) })
{ Text(text = saveTitle) }
},
dismissButton = {
TextButton(onClick = onDismiss)
{ Text(text = cancelTitle) }
},
onDismissRequest = onDismiss,
title = { Text(text = dialogTitle) },
text = {
Column() {
Text(text = editFieldTitle)
TextField(
value = mText,
onValueChange = { mText = it }
)
}
}
)
}
}
var mText by remember { mutableStateOf(editFieldContent) }
it doesn't get updated when editFieldContent changes because remember{} stores value on composition or when keys change.
Then you change mText via delegation or using mText.value = newValue if you don't use by keyword.
If you set a key, block inside remember will be recalculated when editFieldContent parameter of editDialog changes.
var mText by remember(editFieldContent) { mutableStateOf(editFieldContent) }

#Composable funck. into onClick event - Jetpack Compose

I try show AlertDialog when press a button.
For AlertDialog i have a composable function - showDialog.
It is clear that this function calls a dialog.
I have another composable function which displays some window with text and buttons.
When the button is clicked, I want to call a function that stores the AlertDialog.
AlertDialog body:
fun monthDialog() {
val openDialog = remember { mutableStateOf(true) }
if (openDialog.value) {
AlertDialog(
onDismissRequest = {
openDialog.value = false
},
title = {
Text(text = "Title")
},
text = {
Text(
"This area typically contains the supportive text " +
"which presents the details regarding the Dialog's purpose."
)
},
buttons = {
Row(
modifier = Modifier.padding(all = 8.dp),
horizontalArrangement = Arrangement.Center
) {
Button(
modifier = Modifier.fillMaxWidth(),
onClick = { openDialog.value = false }
) {
Text("Dismiss")
}
}
}
)
}
first I tried the simplest solution that came to my mind:
IconButton(onClick = monthDialog())
and got error (#Composable invocations can only happen from the context of a #Composable function)
after i tried a normal trigger using mutablestateof and following condition:
val showDialog = remember { mutableStateOf(false)}
if(showDialog.value == true) monthDialog()
and put some like this into onClick event:
monthHeader(onClick = {showDialog.value = !showDialog.value})
and this is work....but ugly and badly.
for a first time this is worf fine. but after the first click, I need to click two more times to trigger that event again.
button code snippet:
fun Calendar (){
//...
val showDialog = remember { mutableStateOf(false)}
if(showDialog.value == true) monthDialog()
Card(
){
Column(){
IconButton(onClick ={
monthDialog() // error here
//showDialog.value = !showDialog.value
}
}
}
after few hours for searching in google
i try my own solution
val clicked = remember { mutableStateOf(false)}
val showDialog = remember { mutableStateOf(false)}
if(showDialog.value) monthDialog()
else if(clicked.value) monthDialog()
Card(){
Column(){
monthHeader(onClick = {
clicked.value = showDialog.value
showDialog.value = !clicked.value
})
}
}
but in my opinion this is crutch/kludge
Leaving a better solution here (imho):
Hoist the state of your dialog.
#Composable
fun MonthDialog(onClose: () -> Unit) {
AlertDialog(
onDismissRequest = onClose,
title = {
Text(text = "Title")
},
text = {
Text(
"This area typically contains the supportive text " +
"which presents the details regarding the Dialog's purpose."
)
},
buttons = {
Row(
modifier = Modifier.padding(all = 8.dp),
horizontalArrangement = Arrangement.Center
) {
Button(
modifier = Modifier.fillMaxWidth(),
onClick = onClose
) {
Text("Dismiss")
}
}
}
)
Noticed that I removed the state from this component and make it stateless. This component will just notify when the dialog is closed.
Now you can call the dialog like this:
var showDialog by remember { mutableStateOf(false) }
if (showDialog) {
MonthDialog(onClose = { showDialog = false })
}
Card {
MonthHeader( // use upper case for naming your composables
onClick = {
showDialog = true
}
)
}
One can also use Modifier / PointerInputScope:
modifier = Modifier.pointerInput(Unit) {
detectTapGestures(
onPress = { },
onTap = { },
onDoubleTap = { },
onLongPress = { }
)

Enable and focus Textfield at once in Jetpack Compose

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.

Categories

Resources