I'm using the below code to try and request focus to a textfield and have the keyboard come up. Currently the textfield does request focus but the keyboard fails to show. This same code works in another project im working on, but the difference here is this code is inside a Dialog composable, and the other code isn't, so I'm not sure if its the Dialog making the keyboard fail to show?
val textField = remember { FocusRequester() }
Dialog(onDismissRequest = {
openDialog.value = false
dialogInput.value = ""
}) {
Column(
modifier = Modifier
.height(274.dp)
.background(Color.Transparent)
.clickable {
openDialog.value = false
dialogInput.value = ""
}
) {
OutlinedTextField(
modifier = Modifier
.height(64.dp)
.background(Color.White)
.focusRequester(textField),
label = {
Text(
text = label,
style = MaterialTheme.typography.body2.copy(color = Color.Black)
)
},
value = dialogInput.value,
onValueChange = {
dialogInput.value = it
events.filterPlayers(it)
},
textStyle = MaterialTheme.typography.body2.copy(color = Color.Black),
colors = TextFieldDefaults.textFieldColors(
backgroundColor = Color.White,
unfocusedIndicatorColor = Color.White,
focusedIndicatorColor = Color.White
)
)
DisposableEffect(Unit) {
textField.requestFocus()
onDispose {}
}
}
val focusRequester = FocusRequester()
LocalView.current.viewTreeObserver.addOnWindowFocusChangeListener {
if (it) focusRequester.requestFocus()
}
this work for me, after window for dialog get focused, request focus for TextField would show soft keyboard automatically.
Related
I' ve been making a dictionary app for a while and I added that users can create own dictionaries on my app. I show users dictionary on the screen and users can delete their dictionaries whatever they want. so I am trying to make alert dialog for this because I want users not to delete their dictionaries when they press the delete icon directly. An alert dialog will appear on the screen and there should be two buttons such as cancel and accept in that alert dialog. If the user presses accept, that is, if he wants to delete, I want the dictionary to be deleted.
However, the problem is that it is difficult to implement this in compose and in the codes I wrote because I encountered many problems for some reason, whereas it should have been easy. What I did in my codes is that if user clicks delete icon onDeleteClick works and showAlertDialog becomes true in onDeleteClick. When true, it goes inside the top if block and calls the alert dialog component. When the alert dialog compo is called, CustomDialogUI opens. I send two parameters to CustomDialogUI, one is a showAlertDialog mutablestate that controls the opening and closing of the alert dialog, and the second one is deleteDicState if the user says allow in the alert dialog that opens, deleteDicState becomes true and if deleteDicState is true, the deletion must occur.
Since deleteDicState is false the first time, it does not delete, but when the alert dialog opens for the second time and I say delete, it deletes it for some reason. How can i solve this problem help.
my code
#Composable
fun CreateYourOwnDictionaryScreen(
navController: NavController,
viewModel: CreateYourOwnDictionaryViewModel = hiltViewModel()
) {
val scaffoldState = rememberScaffoldState()
val state = viewModel.state.value
val scope = rememberCoroutineScope()
val context = LocalContext.current
val showAlertDialog = remember { mutableStateOf(false) }
val deleteDicState = remember { mutableStateOf(false) }
if(showAlertDialog.value){
Dialog(onDismissRequest = { showAlertDialog.value = false }) {
CustomDialogUI(openDialogCustom = showAlertDialog,deleteDicState)
}
}
Scaffold(
scaffoldState = scaffoldState,
topBar = {
TopAppBar(
backgroundColor = bar,
title = {
androidx.compose.material3.Text(
text = "your dictionaries",
modifier = Modifier.fillMaxWidth(),
color = Color.White,
fontSize = 22.sp
)
},
navigationIcon = {
IconButton(onClick = {
navController.navigate(Screen.MainScreen.route)
}) {
Icon(
imageVector = Icons.Filled.ArrowBack,
contentDescription = "Go Back"
)
}
}
)
},
floatingActionButtonPosition = FabPosition.Center,
floatingActionButton = {
FloatingActionButton(
onClick = { navController.navigate(Screen.CreateDicScreen.route) },
backgroundColor = bar,
) {
Icon(Icons.Filled.Add, "fab")
}
}
) {
Box(modifier = Modifier.background(MaterialTheme.colors.background)) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
LazyColumn(
modifier = Modifier.fillMaxSize()
) {
items(state.dictionaries) { dictionary ->
CreateYourOwnDictionaryItem(
dictionary = dictionary,
modifier = Modifier
.fillMaxWidth()
.clickable {
navController.navigate(Screen.MyWordsScreen.passDicId(dictionary.uid))
},
onAddClick = {
navController.navigate(
Screen.MakeYourDictionaryScreen.passDicId(
dictionary.uid
)
)
},
onDeleteClick = {
if(deleteDicState.value){
viewModel.onEvent(
CreateYourOwnDictionaryEvents.DeleteDictionary(dictionary)
)
scope.launch {
val result = scaffoldState.snackbarHostState.showSnackbar(
message = "dictionary is deleted",
/*actionLabel = "Undo",*/
duration = SnackbarDuration.Short
)
}
}
},
onEditClick = {
navController.navigate(
Screen.UpdateOwnDictionaryScreen.passDicIdAndDicName(
dictionary.uid,
dictionary.creationTime,
)
)
}
)
}
}
}
}
}
}
#Composable
fun CustomDialogUI(
openDialogCustom: MutableState<Boolean>,
deleteDicState : MutableState<Boolean>
) {
Card(
//shape = MaterialTheme.shapes.medium,
shape = RoundedCornerShape(10.dp),
// modifier = modifier.size(280.dp, 240.dp)
modifier = Modifier.padding(10.dp, 5.dp, 10.dp, 10.dp),
elevation = 8.dp
) {
Column(
modifier = Modifier
.background(Color.White)
) {
//.......................................................................
Image(
painter = painterResource(id = R.drawable.ic_baseline_warning),
contentDescription = null, // decorative
/*contentScale = ContentScale.Fit,
colorFilter = ColorFilter.tint(
color = bar
),*/
modifier = Modifier
.padding(top = 35.dp)
.height(70.dp)
.fillMaxWidth(),
)
Column(modifier = Modifier.padding(16.dp)) {
androidx.compose.material3.Text(
text = "Warning !",
textAlign = TextAlign.Center,
modifier = Modifier
.padding(top = 5.dp)
.fillMaxWidth(),
style = MaterialTheme.typography.body2,
maxLines = 2,
overflow = TextOverflow.Ellipsis
)
androidx.compose.material3.Text(
text = "Are you sure that your previously created dictionary will be deleted?",
textAlign = TextAlign.Center,
modifier = Modifier
.padding(top = 10.dp, start = 25.dp, end = 25.dp)
.fillMaxWidth(),
)
}
//.......................................................................
Row(
Modifier
.fillMaxWidth()
.padding(top = 10.dp)
.background(bar),
horizontalArrangement = Arrangement.SpaceAround
) {
TextButton(onClick = {
openDialogCustom.value = false
}) {
Text(
"Not Now",
fontWeight = FontWeight.Bold,
color = Color.Black,
modifier = Modifier.padding(top = 5.dp, bottom = 5.dp)
)
}
TextButton(onClick = {
openDialogCustom.value = false
deleteDicState.value = true
}) {
Text(
"Allow",
fontWeight = FontWeight.ExtraBold,
color = Color.Black,
modifier = Modifier.padding(top = 5.dp, bottom = 5.dp)
)
}
}
}
}
}
I cannot call the CustomDialogUI in onDeleteClick . If I call it, it gives the following error #Composable invocations can only happen from the context of a #Composable function.
for example like this
CreateYourOwnDictionaryScreen
onDeleteClick = {
Dialog(onDismissRequest = { showAlertDialog.value = false }) {
CustomDialogUI(openDialogCustom = showAlertDialog,deleteDicState)
}
....
I cannot call like this.
So I call it outside of onDeleteClick. or directly in CustomDialogUI if the user presses the delete button, I cannot delete it there because I can't access viewmodel and dictionary there
for example like this
CustomDialogUI
TextButton(onClick = {
openDialogCustom.value = false
viewModel.onEvent(
CreateYourOwnDictionaryEvents.DeleteDictionary(dictionary)
)
scope.launch {
val result = scaffoldState.snackbarHostState.showSnackbar(
message = "dictionary is deleted",
/*actionLabel = "Undo",*/
duration = SnackbarDuration.Short
)
}
}) {
Text(
"Allow",
fontWeight = FontWeight.ExtraBold,
color = Color.Black,
modifier = Modifier.padding(top = 5.dp, bottom = 5.dp)
)
}
}
I cannot call like this.
Passing MutableStates as composable parameters is considered a bad practice, you should pass raw values and callbacks instead. In your case, you can implement it like this:
#Composable
fun CreateYourOwnDictionaryScreen() {
val showDeleteDialogForItem = remember { mutableStateOf<DictionaryItem?>(null) }
showDeleteDialogForItem.value?.let { itemToDelete ->
DeleteDialog(
onDeleteConfirm = {
viewModel.onEvent()
showDeleteDialogForItem.value = null
},
onCancel = { showDeleteDialogForItem.value = null },
)
}
...
items.forEach { item ->
CreateYourOwnDictionaryItem(
onDeleteClick = { showDeleteDialogForItem.value = item }
)
}
}
#Composable
fun DeleteDialog(
onDeleteConfirm: () -> Unit,
onCancel: () -> Unit,
) {
...
Button(onClick = onCancel) { Text("Cancel") }
Button(onClick = onDeleteConfirm) { Text("Delete") }
}
I have a Dialog that I cant close when I press the button this is my code, I believe that is for the if cycle, but I need it to make it true under those circumstances, any idea that could help me here?
#Composable
fun PopupWindowDialog(
parentUiState: ParentHomeUiState,
) {
val openDialog = remember { mutableStateOf(false) }
var sliderPosition by remember { mutableStateOf(0f) }
if (!parentUiState.showInAppFeedback){
openDialog.value = true
}
val recommend = sliderPosition.toInt()
Column(
) {
Box {
if (openDialog.value) {
Dialog(
onDismissRequest = { openDialog.value = false },
properties = DialogProperties(),
){
Box(
Modifier
.fillMaxWidth()
.fillMaxHeight()
//.padding(vertical = 70.dp, horizontal = 10.dp)
.padding(vertical = 70.dp )
.background(Color.White, RoundedCornerShape(10.dp))
//.border(1.dp, color = Color.Black, RoundedCornerShape(20.dp))
.border(1.dp, color = Color.White, RoundedCornerShape(20.dp))
) {
Button(
modifier = Modifier
.fillMaxWidth()
.padding(10.dp),
onClick = {
openDialog.value = !openDialog.value
}
) {
Text(
text = "¡Contesta y gana +20 puntos!",
style = MaterialTheme.typography.subtitle2,
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(3.dp))
}
}
}
}
}
}
}
I have a Dialog that I cant close when I press the button
Since you didn't show how your posted code (composable) is being called, ill assume the .showInAppFeedback is evaluating true.
Pay attention to these parts of your codes
val openDialog = remember { mutableStateOf(false) }
...
...
if (!parentUiState.showInAppFeedback) {
openDialog.value = true
}
, openDialog is a state and when it changes value it will re-compose your entire composable, so when the dialog is visible, and when you set it to false via button click, the entire composable will re-compose and re-evaluate your if condition, setting openDialog.value to true again, and dialog will never close.
I don't know your use-case but you can wrap it inside a LaunchedEffect
LaunchedEffect(parentUiState.showInAppFeedback) {
openDialog.value = true
}
Since parentUiState.showInAppFeedback is not updated, openDialog.value will always be set to true when the view recomposes. You should use parentUiState.showInAppFeedback in the initial remember block but not for future recomposition.
#Composable
fun PopupWindowDialog(parentUiState: ParentHomeUiState) {
val openDialog = remember { mutableStateOf(!parentUiState.showInAppFeedback) }
var sliderPosition by remember { mutableStateOf(0f) }
val recommend = sliderPosition.toInt()
Column {
Box {
if (openDialog.value) {
Dialog(
onDismissRequest = { openDialog.value = false },
properties = DialogProperties(),
) {
Box(
Modifier
.fillMaxWidth()
.fillMaxHeight()
//.padding(vertical = 70.dp, horizontal = 10.dp)
.padding(vertical = 70.dp)
.background(Color.White, RoundedCornerShape(10.dp))
//.border(1.dp, color = Color.Black, RoundedCornerShape(20.dp))
.border(1.dp, color = Color.White, RoundedCornerShape(20.dp))
) {
Button(
modifier = Modifier
.fillMaxWidth()
.padding(10.dp),
onClick = {
openDialog.value = !openDialog.value
}
) {
Text(
text = "¡Contesta y gana +20 puntos!",
style = MaterialTheme.typography.subtitle2,
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(3.dp)
)
}
}
}
}
}
}
}
I have a lazy column that contains a number of Text(string = "...") and a textfield pinned to the bottom of the screen below the LazyColumn. when the textfield is focused, the keyboard shows up and it pushes the textfield above the keyboard which is expected. however, when the items in the LazyColumn r too much, the textfield goes below the keyboard instead of allowing the user to scroll the lazy column. any reason for this? I already have set
android:windowSoftInputMode="adjustResize" in my manifest's activity. here's the full code:
Column(modifier = Modifier
.fillMaxSize()
.padding(16.dp),
verticalArrangement = Arrangement.SpaceBetween) {
LazyColumn {
items(viewModel.options) { option ->
Text(option.message)
}
}
Row(modifier = Modifier.fillMaxWidth()) {
TextField(
modifier = Modifier.weight(1f),
value = optionsText,
onValueChange = {optionsText = it },
singleLine = true
)
Icon(
Icons.Filled.Send,
contentDescription = "",
modifier = Modifier.align(Alignment.CenterVertically).clickable {...}
)
}
}
Solution, WEIGHTS 🤪 !
LazyColumn(Modifier.weight(9f)) {
items(listItems) {
Text(it)
}
}
Row(modifier = Modifier.fillMaxWidth().weight(1f, /*false*/)) {
var loremText by remember { mutableStateOf("") }
TextField(
modifier = Modifier.weight(1f),
value = loremText,
onValueChange = { loremText = it },
singleLine = true
)
Icon(
Icons.Filled.Send,
contentDescription = "",
modifier = Modifier
.align(Alignment.CenterVertically)
.clickable { }
)
}
You may remove the adjustResize from your manifest.
I implemented a simple dialog with Jetpack Compose on Android.
I am trying to show a caution message when isRehearsal is true.
The variable isRehearsal is toggled when the user clicks buttons, and changing button works fine when the user clicks the buttons and toggles isRehearal.
The problem is, that the caution text does not appear when the initial value of isRehearsal is false and later the variable becomes true. When I change the initial value of isRehearsal to true, then the text disappears/appears fine when isRehearsal becomes false or true.
var isRehearsal by remember { mutableStateOf(false) }
Dialog(
onDismissRequest = { dismiss() },
DialogProperties(dismissOnBackPress = true, dismissOnClickOutside = true)
) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.background(White, shape = RoundedCornerShape(8.dp))
.fillMaxWidth()
.padding(12.dp)
) { // the box does not resize when the caution text appears dynamically.
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
Text(text = "Set speed")
Row(
horizontalArrangement = Arrangement.SpaceEvenly,
modifier = Modifier.fillMaxWidth()
) {
if (isRehearsal) {
Button(
onClick = { isRehearsal = false },
colors = ButtonDefaults.buttonColors(
backgroundColor = Colors.Red400
)
) {
Text(text = "Rehearsal ON")
}
} else {
Button(
onClick = { isRehearsal = true },
colors = ButtonDefaults.buttonColors(
backgroundColor = Colors.Green400
)
) {
Text(text = "Rehearsal OFF")
}
}
Button(onClick = { onClickStart(pickerValue) }) {
Text(text = "Start")
}
}
if (isRehearsal) { // BUG!! when the intial value of isRehearsal is false, then the text never appears even when the value becomes true.
Text(text = "Rehearsal does not count as high score") // <- The caution text
}
}
}
}
How should I change the Box block to make the box's height extend properly to be able to wrap caution text when a view appears dynamically?
Edit
If I change the catuion message part to like below, the text appears fine even when the initial value of isRehearsal is false. Therefore, I think that the issue is with the height of the Box composable.
if (isRehearsal) {
Text(text = "Rehearsal does not count as high score")
} else {
Spacer(modifier = Modifier.height(100.dp))
}
You can use DialogProperties() with
usePlatformDefaultWidth = false
as a workaround. Example:
Dialog(
onDismissRequest = {},
properties = DialogProperties(usePlatformDefaultWidth = false)
) {
Card(modifier = Modifier.padding(8.dp).wrapContentSize().animateContentSize()) {
// content
}
}
It looks like the topmost Dialog view size is not getting updated after the first recomposition. It's a known bug.
As a workaround until it's fixed, you can make a transparent topmost view take all the size available, and handle clicks same as dismissOnClickOutside option does:
Dialog(
onDismissRequest = { },
) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.fillMaxSize()
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = null,
) {
// same action as in onDismissRequest
}
) {
// content
}
}
I have the following code:
Box(
Modifier.fillMaxWidth(),
contentAlignment = Alignment.Center
) {
OutlinedTextField(
value = text,
onValueChange = {
text = it
if (text.length >= 3) {
viewModel.getSuggestions(text)
}
},
label = { Text("Search") },
modifier = Modifier.fillMaxWidth(),
singleLine = true,
leadingIcon = {
Icon(
painter = painterResource(id = R.drawable.ic_search),
contentDescription = null,
modifier = Modifier.padding(16.dp, 0.dp, 8.dp, 0.dp),
tint = Color.Unspecified
)
},
shape = RoundedCornerShape(50)
)
DropdownMenu(expanded = suggestions.value.isNotEmpty(),
modifier = Modifier
.fillMaxWidth(0.92f),
onDismissRequest = { }) {
for (suggestion in suggestions.value) {
DropdownMenuItem(onClick = {
viewModel.searchWord(suggestion)
}) {
Text(suggestion)
}
}
}
}
It's a dictionary, on top of the screen there is this OutlinedTextField.
When I search for a word I get suggestions based on the input and show them in a DropdownMenu.
The problem I am facing is that when the DropdownMenu is shown, the keyboard disappears but the focus remains on the Text field. How can I solve this problem and most importantly, why is this happening? I know it's redrawing the UI based on the status change but why it's not keeping the keyboard opened.
DropdownMenu(properties = PopupProperties(focusable = false))
Check this