Enable and focus Textfield at once in Jetpack Compose - android

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.

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

Mutable state of value from viewModel is not working

I have a mutablestate in my ViewModel that I'm trying to set and access in my composable. When I use delegated Property of remember it is working but after creating it in viewModel and accessing it in compose the state of the variable is empty how to use the state with viewModel
Everything is working fine when the state is inside the compose
var mSelectedText by remember { mutableStateOf("") }
But when i use it from viewModel change and set my OutlinedTextField value = mainCatTitle and onValueChange = {mainCatTitle = it} the selected title is not Showing up in the OutlinedTextField is empty
private val _mainCatTitle = mutableStateOf("")
val mainCatTitle: State<String> = _mainCatTitle
my Composable
var mSelectedText by remember { mutableStateOf("") }
var mainCatTitle = viewModel.mainCatTitle.value
Column(Modifier.padding(20.dp)) {
OutlinedTextField(
value = mainCatTitle,
onValueChange = { mainCatTitle = it },
modifier = Modifier
.fillMaxWidth()
.onGloballyPositioned { coordinates ->
mTextFieldSize = coordinates.size.toSize()
},
readOnly = true,
label = { Text(text = "Select MainCategory") },
trailingIcon = {
Icon(icon, "contentDescription",
Modifier.clickable { mExpanded = !mExpanded })
}
)
DropdownMenu(expanded = mExpanded,
onDismissRequest = { mExpanded = false },
modifier = Modifier.width(with(
LocalDensity.current) {
mTextFieldSize.width.toDp()
})) {
selectCategory.forEach {
DropdownMenuItem(onClick = {
mainCatTitle = it.category_name.toString()
mSelectedCategoryId = it.category_id.toString()
mExpanded = false
Log.i(TAG,"Before the CategoryName: $mainCatTitle " )
}) {
Text(text = it.category_name.toString())
}
}
}
}
Log.i(TAG,"Getting the CategoryName: $mainCatTitle " )
}
in my First log inside the DropDownMenuItem the log is showing the Selected field but second log is empty
Have attached the image
Your'e directly modifying the mainCatTitle variable from onClick, not the state hoisted by your ViewMoel
DropdownMenuItem(onClick = {
mainCatTitle = it.category_name.toString()
...
and because you didn't provide anything about your ViewModel, I would assume and suggest to create a function if you don't have one in your ViewModel that you can call like this,
DropdownMenuItem(onClick = {
viewModel.onSelectedItem(it) // <-- this function
...
}
and in your ViewModel you update the state like this
fun onSelectedItem(item: String) { // <-- this is the function
_mainCatTitle.value = item
}

Request Focus in Jetpack compose in TextField

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

Avoid accidental tap/touch in a Jetpack Compose Slider which is placed inside in a scrollable column

I have a Slider which is placed inside a Column which is scrollable. When i scroll through the components sometimes accidentally slider value changes because of accidental touches. How can i avoid this?
Should i be disable taps on slider? If yes how can i do it?
Is there any alternate like Nested scroll instead of Column which can prevent this from happening?
#Composable
fun ColumnScope.FilterRange(
title: String,
range: ClosedFloatingPointRange<Float>,
rangeText: String,
valueRange: ClosedFloatingPointRange<Float>,
onValueChange: (ClosedFloatingPointRange<Float>) -> Unit,
) {
Spacer(modifier = Modifier.height(Size_Regular))
Text(
text = title,
style = MaterialTheme.typography.h6
)
Spacer(modifier = Modifier.height(Size_X_Small))
Text(
text = rangeText,
style = MaterialTheme.typography.subtitle1
)
RangeSlider(
modifier = Modifier.fillMaxWidth(),
values = range,
valueRange = valueRange,
onValueChange = {
onValueChange(it)
})
Spacer(modifier = Modifier.height(Size_Small))
Divider(thickness = DividerSize)
}
I would disable the RangeSlider and only enable it when you tap on it. You disable it by tapping anywhere else within the Column. This is a similar behavior used to mimic losing focus. Here's an example:
class MainActivity : ComponentActivity() {
#ExperimentalMaterialApi
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
startActivity(intent)
setContent {
var rangeEndabled by remember { mutableStateOf(false)}.apply { this.value }
var sliderPosition by remember { mutableStateOf(0f..100f) }
Text(text = sliderPosition.toString())
Column(modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
.pointerInput(Unit) {
detectTapGestures(
onTap = {
rangeEndabled = false
}
)
}) {
repeat(30) {
Text(it.toString())
}
RangeSlider(
enabled = rangeEndabled,
values = sliderPosition,
onValueChange = { sliderPosition = it },
valueRange = 0f..100f,
onValueChangeFinished = {
// launch some business logic update with the state you hold
// viewModel.updateSelectedSliderValue(sliderPosition)
},
modifier = Modifier.pointerInput(Unit) {
detectTapGestures(
onTap = {
rangeEndabled = true
}
)
}
)
repeat(30) {
Text(it.toString())
}
}
}
}
}

How to manage Focus state in Jetpack's Compose

I have a custom composable View (Surface + Text essentially) and I want to change the color of the surface depending on the focus state. The FocusManager#FocusNode is marked internal and I am unaware of any way to achieve this. Is this simply just not available yet? Any one else have to tackle this?
With 1.0.x you can use the Modifier.onFocusChanged to observe focus state events.
Something like:
var color by remember { mutableStateOf(Black) }
val focusRequester = FocusRequester()
Text(
modifier = Modifier
.focusRequester(focusRequester)
.onFocusChanged { color = if (it.isFocused) Green else Black }
.focusModifier()
.pointerInput(Unit) { detectTapGestures { focusRequester.requestFocus() } },
text = "Text",
color = color
)
As of dev11, FocusManagerAmbient has been deprecated in favor of FocusModifier. For more examples check out the KeyInputDemo, ComposeInputFieldFocusTransition or FocusableDemo.
#Composable
private fun FocusableText(text: MutableState<String>) {
val focusModifier = FocusModifier()
Text(
modifier = focusModifier
.tapGestureFilter { focusModifier.requestFocus() }
.keyInputFilter { it.value?.let { text.value += it; true } ?: false },
text = text.value,
color = when (focusModifier.focusState) {
Focused -> Color.Green
NotFocused -> Color.Black
NotFocusable -> Color.Gray
}
)
}
Compose has since updated and made the FocusManager members public; although, I'm not exactly sure how final the api is as of dev10. But as of now you can create a FocusNode and listen for changes using FocusManager.registerObserver.
val focusNode = remember {
FocusNode().apply {
focusManager.registerObserver(node = this) { fromNode, toNode ->
if (toNode == this) {
// has focus
} else {
// lost focus
}
}
}
}
If you'd like to gain focus, you can now call FocusManager.requestFocus:
val focusManager = FocusManagerAmbient.current
focusManager.requestFocus(focusNode)
You can also set a focusIdentifier on your FocusNode:
val focusNode = remember {
FocusNode().apply {
...
focusManager.registerFocusNode("your-focus-identifier", node = this)
}
}
To gain focus for a specific identifier, you just call FocusManager.requestFocusById
Using that you can easily create a Composable that can provide and request focus for you, for instance:
#Composable
fun useFocus(focusIdentifier: String? = null): Pair<Boolean, () -> Unit> {
val focusManager = FocusManagerAmbient.current
val (hasFocus, setHasFocus) = state { false }
val focusNode = remember {
FocusNode().apply {
focusManager.registerObserver(node = this) { fromNode, toNode ->
setHasFocus(toNode == this)
}
focusIdentifier?.let { identifier ->
focusManager.registerFocusNode(identifier, node = this)
}
}
}
onDispose {
focusIdentifier?.let { identifier ->
focusManager.unregisterFocusNode(identifier)
}
}
return hasFocus to {
focusManager.requestFocus(focusNode)
}
}
val (hasFocus, requestFocus) = useFocus("your-focus-identifier")
You could also compose children along with it:
#Composable
fun FocusableTextButton(
text: String,
focusedColor: Color = Color.Unset,
unFocusedColor: Color = Color.Unset,
textColor: Color = Color.White,
focusIdentifier: String? = null
) {
val (hasFocus, requestFocus) = useFocus(focusIdentifier)
Surface(color = if (hasFocus) focusedColor else unFocusedColor) {
TextButton(onClick = requestFocus) {
Text(text = text, color = textColor)
}
}
}
Alternatively, there's also FocusModifier, which as of now is:
/**
* A [Modifier.Element] that wraps makes the modifiers on the right into a Focusable. Use a
* different instance of [FocusModifier] for each focusable component.
*
* TODO(b/152528891): Write tests for [FocusModifier] after we finalize on the api (API
* review tracked by b/152529882).
*/
But I don't think you can apply an identifier with it right now.
val focusModifier = FocusModifier()
val hasFocus = focusModifier.focusDetailedState == FocusDetailedState.Active
Surface(
modifier = focusModifier,
color = if (hasFocus) focusedColor else unFocusedColor
) {
TextButton(onClick = { focusModifier.requestFocus() }) {
Text(text = text, color = textColor)
}
}
All that being said, I'm not 100% sure this is the intended way to handle focus right now. I referenced CoreTextField a lot to see how it was being handled there.
Example:
#Composable
fun FocusTest() {
val focusManager = FocusManagerAmbient.current
val selectRandomIdentifier: () -> Unit = {
focusManager.requestFocusById(arrayOf("red,", "blue", "green", "yellow").random())
}
Column(verticalArrangement = Arrangement.SpaceBetween) {
FocusableTextButton(
text = "When I gain focus, I turn red",
focusedColor = Color.Red,
focusIdentifier = "red"
)
FocusableTextButton(
text = "When I gain focus, I turn blue",
focusedColor = Color.Blue,
focusIdentifier = "blue"
)
FocusableTextButton(
text = "When I gain focus, I turn green",
focusedColor = Color.Green,
focusIdentifier = "green"
)
FocusableTextButton(
text = "When I gain focus, I turn yellow",
focusedColor = Color.Yellow,
focusIdentifier = "yellow"
)
Button(onClick = selectRandomIdentifier) {
Text(text = "Click me to randomly select a node to focus")
}
}
}
this code works for me
var haveFocus by remember { mutableStateOf(false) }
Surface(
modifier = Modifier.onFocusEvent {
haveFocus = it.isFocused
},
shape = RectangleShape
){...}
This answer is base on Gabriele Mariotti answer but I have change
Use remember { FocusRequester() } to prevent crash FocusRequester is not initialized after do 2nd click on Text
Use focusTarget instead of focusModifier because deprecated
.
var color by remember { mutableStateOf(Color.Black) }
val focusRequester = remember { FocusRequester() }
Text(
text = "Hello",
modifier = Modifier
.focusRequester(focusRequester)
.onFocusChanged { color = if (it.isFocused) Color.Green else Color.Black }
.focusTarget()
.pointerInput(Unit) { detectTapGestures { focusRequester.requestFocus() } }
)

Categories

Resources