Jetpack Compose Popup's TextField cannot received keyboard input - android

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

Related

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
}

How to show error message using compose and a viewModel

I want to display an error message with compose, this works the problem is that the viewModel state call always the state function
I have a Textfield like this
class Test {
#Composable
fun Test() {
val viewModel:TestViewModel = viewModel()
var text by rememberSaveable { mutableStateOf("") }
var isError by rememberSaveable { mutableStateOf(false) }
// liveData
val state by viewModel.viewState.observeAsState(EmailViewState.Nothing)
when (state) {
EmailViewState.OnInvalidPassword -> {
shouldDisplayPasswordError = true
}
/*....*/
}
Column {
TextField(
value = text,
singleLine = true,
isError = isError,
onValueChange = {
text = it
isError = false
},
)
if (isError) {
Text(
text = "Error message",
color = MaterialTheme.colors.error,
style = MaterialTheme.typography.caption,
modifier = Modifier.padding(start = 16.dp)
)
}
}
}
class TestViewModel: ViewModel(){
val viewState = MutableLiveData<EmailViewState>()
fun validate(email:String){
//some validations
viewState.postValue(OnInvalidPassword)
}
}
}
The problem is everytime the recomposition happens, the
val state by viewModel.viewState.observeAsState(EmailViewState.Nothing) is called with the latest value (Invalid) and override the behavior of onValueChange where i set isError = false is any way to combine or avoid the viewState being called in every re composition?
Thanks

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

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.

Formatting numbers in compose TextField

I am trying to create a reusable NumberField component:
#Composable
fun NumberField(
value: Number?,
onNumberChange: (Number) -> Unit,
) {
TextField(
value = value?.toString() ?: "",
onValueChange = {
it.toDoubleOrNull()?.let { value ->
if (value % 1.0 == 0.0) {
onNumberChange(value.toInt())
} else {
onNumberChange(value)
}
}
},
singleLine = true,
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
)
}
To be used as:
#Composable
fun NumberContent() {
val number = remember { mutableStateOf<Number?>(null) }
NumberField(value = number.value) {
number.value = it
}
}
I would like the number to be an Int or Double depending on what the user is typing. What I have above works until you try to enter a decimal number, as it seems "5.", does not parse as double. I want to allow the user to type 5. and then fill in rest. As such I don't want to add a zero after decimal automatically because that might not be the next number they want to enter. Is this the best way to go about it? I know that I can just accept any text and then try to format the text they entered later as an int or double and make them fix it then, just thought it would be nice to bundle it all in the composable.
You can use something like:
TextField(
value = text,
onValueChange = {
if (it.isEmpty()){
text = it
} else {
text = when (it.toDoubleOrNull()) {
null -> text //old value
else -> it //new value
}
}
},
singleLine = true,
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
)
Here is an implementation that handles all stated conditions while also exposing the state to parents.
#Composable
fun NumberField(
value: Number?,
onNumberChange: (Number?) -> Unit,
) {
val number = remember { mutableStateOf(value) }
val textValue = remember(value != number.value) {
number.value = value
mutableStateOf(value?.toDouble()?.let {
if (it % 1.0 == 0.0) {
it.toInt().toString()
} else {
it.toString()
}
} ?: "")
}
val numberRegex = remember { "[-]?[\\d]*[.]?[\\d]*".toRegex() }
// for no negative numbers use "[\d]*[.]?[\d]*"
TextField(
value = textValue.value,
onValueChange = {
if (numberRegex.matches(it)) {
textValue.value = it
number.value = it.toDoubleOrNull()
onNumberChange(number.value)
}
},
singleLine = true,
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
)
}
An example usage is shown below.
#Composable
fun DemoUsage() {
Column {
val number = remember { mutableStateOf<Number?>(null) }
NumberField(value = number.value) {
number.value = it
}
Button(onClick = { number.value = number.value?.toDouble()?.plus(1) }) {
Text("Increment")
}
}
}

Categories

Resources