when I open DropdownMenu, my keyboard auto-close. This behavior is not my expect.
I expect my keyboard to remain open when I open the DropdownMenu.
current behavior:
want behavior:
Thanks to my friends in the compose community for helping me with this on slack.
Just set the properties.focusable property to false
var content by remember { mutableStateOf("") }
var deadLineMenuExpanded by remember { mutableStateOf(false) }
Column(modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally) {
TextField(value = content, onValueChange = {content = it})
Box() {
TextButton(onClick = { deadLineMenuExpanded = true }) {
Text("open Menu")
}
DropdownMenu(
expanded = deadLineMenuExpanded,
onDismissRequest = { deadLineMenuExpanded = false },
properties = PopupProperties(focusable = false)
) {
DropdownMenuItem(onClick = {
deadLineMenuExpanded = false
}) {
androidx.compose.material.Text("today")
}
}
}
}
Related
I'm making a TextField with ExposedDropDownMenu for selecting a Country.
When i select something i use mutable state variable 'isReadOnly' for setting the readOnly parameter of TextField composable.
So when I select a country, i want it to be readOnly, but when I click on dismiss icon, I want it to not be readOnly.
I works very well, except one part, when I click on dismiss icon, I can type to my TextField, but software keyboard is not showing up. Do you know the solution to this problem?
val selectedFromController = selected.value
var selectedItem by remember {
mutableStateOf(
selectedFromController.ifEmpty { "" }
)
}
var isReadOnly by remember { mutableStateOf(false) }
ExposedDropdownMenuBox(
expanded = expanded.value,
onExpandedChange = { expanded.value = !expanded.value }
) {
TextField(
value = selectedItem,
onValueChange = {
selectedItem = it
expanded.value = true
},
readOnly = isReadOnly,
modifier = Modifier
.menuAnchor()
.fillMaxWidth(),
label = { Text(stringResource(id = label)) },
trailingIcon = { IconButton(
onClick = {
selectedItem = ""
selected.value = ""
savedId.value = -1
isReadOnly = false
keyboardController.show()
},
content = {
Icon(Icons.Filled.Clear, null)
}
) },
colors = ExposedDropdownMenuDefaults.textFieldColors()
)
val filteringOptions =
menuItems.filter { it.contains(selectedItem, ignoreCase = true) }
if (filteringOptions.isNotEmpty()) {
ExposedDropdownMenu(
expanded = expanded.value,
onDismissRequest = {
expanded.value = false
}
) {
filteringOptions.take(5).map {
DropdownMenuItem(
text = { Text(text = it) },
onClick = {
selectedItem = it
selected.value = it
expanded.value = false
isReadOnly = true
},
contentPadding = ExposedDropdownMenuDefaults.ItemContentPadding
)
}
}
}
}
I tried to bring it up with keyboardController, but it didn't help.
I want to disable a whole view from any interaction (e.g. button presses) when a Boolean in my view model is true. How can I do this in Jetpack Compose without having to disable each of the elements within the view?
See example below as to what I'm trying to do.
#Composable
fun MyView(alertViewModel: AlertViewModel = viewModel()) {
var text by remember { mutableStateOf(TextFieldValue("")) }
Column(
/*
Disable all elements in the column so I don't need to disable each element individually for example:
modifier = Modifier
.disabled(
if (alertViewModel.showAlert == true) {
true
} else {
false
}
*/
) {
Text(text = "My View")
TextField(
value = text,
onValueChange = { newText ->
text = newText
}
)
Button(onClick = { /*TODO*/ }) {
}
}
}
Proceed like this :
#Composable
fun MyView(alertViewModel: AlertViewModel = viewModel()) {
var text by remember { mutableStateOf(TextFieldValue("")) }
if (alertViewModel.showAlert == true) {
Text(text = "Nothing to show")
} else {
Column(modifier = Modifier) {
Text(text = "My View")
TextField(
value = text,
onValueChange = { newText ->
text = newText
}
)
Button(onClick = { /*TODO*/ }) {
}
}
}
}
Example:
#OptIn(ExperimentalMaterial3Api::class)
#Composable
fun MyView(alertViewModel: AlertViewModel = viewModel()) {
var text by remember { mutableStateOf(TextFieldValue("")) }
Box() {
Column() {
Text(text = "My View")
TextField(
value = text,
onValueChange = { newText ->
text = newText
}
)
Button(onClick = { /*TODO*/ }) {
}
}
Box(
modifier = Modifier
.then(
if(alertViewModel.showAlert == true){
Modifier.fillMaxSize().disabled(true)
}
)
)
}
}
Seems as though it isn't possible to disable a whole view in Jetpack Compose. All interact-able elements such as Button and TextField and have to be set to enabled = false individually.
Buttons: Jetpack Compose: How to disable FloatingAction Button?
TextFields: Jetpack Compose: Disable Interaction with TextField
I want to show a circular progress bar while the shopping list items are being retrieved from the database. I have a Lazycolumn that displays the retrieved shopping list items, but the circular progress bar is never displayed, and the message "You don't have any items in this shopping list." is displayed briefly before the list is shown. This behavior is not desired. In the viewmodel, placing loading.value = false after the database call in the viewModelScope coroutine does not work. How can I fix this?
ShoppingListScreen Composable
fun ShoppingListScreen(
navController: NavHostController,
shoppingListScreenViewModel: ShoppingListScreenViewModel,
sharedViewModel: SharedViewModel
) {
val scope = rememberCoroutineScope()
val focusManager = LocalFocusManager.current
val screenHeight = LocalConfiguration.current.screenHeightDp.dp
val allItems = shoppingListScreenViewModel.shoppingListItemsState.value?.collectAsLazyPagingItems()
val showProgressBar = shoppingListScreenViewModel.loading.value
Scaffold(
topBar = {
CustomAppBar(
title = "Shopping List Screen",
titleFontSize = 20.sp,
appBarElevation = 4.dp,
navController = navController
)
},
floatingActionButton = {
FloatingActionButton(
onClick = {
shoppingListScreenViewModel.setStateValue(SHOW_ADD_ITEM_DIALOG_STR, true)
},
backgroundColor = Color.Blue,
contentColor = Color.White
) {
Icon(Icons.Filled.Add, "")
}
},
backgroundColor = Color.White,
// Defaults to false
isFloatingActionButtonDocked = false,
bottomBar = { BottomNavigationBar(navController = navController) }
) {
Box {
LazyColumn(
modifier = Modifier
.fillMaxWidth()
.height(screenHeight)
) {
if (allItems?.itemCount == 0) {
item { Text("You don't have any items in this shopping list.") }
}
items(
items = allItems!!,
key = { item ->
item.id
}
) { item ->
ShoppingListScreenItem(
navController = navController,
item = item,
sharedViewModel = sharedViewModel
) { isChecked ->
scope.launch {
shoppingListScreenViewModel.changeItemChecked(item!!, isChecked)
}
}
}
item { Spacer(modifier = Modifier.padding(screenHeight - (screenHeight - 70.dp))) }
}
ConditionalCircularProgressBar(isDisplayed = showProgressBar)
}
}
}
ShoppingListScreenViewModel
#HiltViewModel
class ShoppingListScreenViewModel #Inject constructor(
private val getAllShoppingListItemsUseCase: GetAllShoppingListItemsUseCase
) {
private val _shoppingListItemsState = mutableStateOf<Flow<PagingData<ShoppingListItem>>?>(null)
val shoppingListItemsState: State<Flow<PagingData<ShoppingListItem>>?> get() = _shoppingListItemsState
val loading = mutableStateOf(false)
init {
loading.value = true
getAllShoppingListItemsFromDb()
}
private fun getAllShoppingListItemsFromDb() {
viewModelScope.launch {
_shoppingListItemsState.value = getAllShoppingListItemsUseCase().distinctUntilChanged()
loading.value = false
}
}
}
put the CircularProgressIndicator inside a Surface or Box, maybe you are using Scaffold with navigation, in my case I was using padding top 60.dp in my circular progrees then it was kind of hinding.
Perhaps this is the normal behaviour, but i wish it was different. I had tried to google the solution, but did not find anything suitable (or merely missed it).
Sample code (for simplicity i hold mutable states right here, not using ViewModel):
#Composable
fun Greeting() {
Scaffold(topBar = {
TopAppBar(title = { Text(text = "Some title") })
}) {
val focusManager = LocalFocusManager.current
LazyColumn(
contentPadding = PaddingValues(all = 16.dp),
verticalArrangement = Arrangement.spacedBy(space = 16.dp)
) {
items(count = 20) { index ->
val (value, onValueChange) = rememberSaveable { mutableStateOf("Some value $index") }
TextField(
value = value,
onValueChange = onValueChange,
modifier = Modifier.fillMaxWidth(),
label = { Text(text = "Some label $index") },
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
keyboardActions = KeyboardActions(onNext = {
if (!focusManager.moveFocus(FocusDirection.Down))
focusManager.clearFocus()
}),
singleLine = true
)
}
}
}
}
Compose version 1.0.5
You could try just hiding the keyboard whenever scrolling occurs. This is okay as long as you don't have a large set of items. But since you're using TextFields, it isn't likely that you'll have such a large number. This sample illustrates hiding the keyboard when scrolling occurs:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
startActivity(intent)
setContent {
Greeting(this)
}
}
}
#Composable
fun Greeting(activity: Activity) {
Scaffold(topBar = {
TopAppBar(title = { Text(text = "Some title") })
}) {
val lazyListState = rememberLazyListState()
val ctx = LocalContext.current
LaunchedEffect(lazyListState.firstVisibleItemIndex) {
val inputMethodManager = ctx.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
inputMethodManager.hideSoftInputFromWindow(activity.window?.decorView?.windowToken, 0)
}
LazyColumn(
state = lazyListState,
contentPadding = PaddingValues(all = 16.dp),
verticalArrangement = Arrangement.spacedBy(space = 16.dp)
) {
items(
count = 20,
key = { index ->
// Return a stable + unique key for the item
index
}
) { index ->
val (value, onValueChange) = rememberSaveable { mutableStateOf("Some value $index") }
TextField(
value = value,
onValueChange = onValueChange,
modifier = Modifier
.fillMaxWidth(),
label = { Text(text = "Some label $index") },
singleLine = true
)
}
}
}
}
I am trying to make autocomplete feature by using OutlinedTextField, DropdownMenu.
When I programmatically open the DropdownMenu, I can then no longer type into the OutlinedTextField.
var expanded by remember { mutableStateOf(false) }
val suggestions = remember { mutableStateListOf ("karan", "karanx", "karany") }
var selectedText by remember { mutableStateOf("") }
var dropDownWidth by remember { mutableStateOf(0) }
val focusManager = LocalFocusManager.current
Box() {
OutlinedTextField(
value = selectedText,
onValueChange = {
selectedText = it
expanded = true
},
modifier = Modifier.fillMaxWidth()
.onSizeChanged {
dropDownWidth = it.width
} .onFocusChanged {
expanded = it.isFocused
},
//why cant i do expanded = true on cliackable modifier of outlinetextfield
label = { Text("Label") }
)
DropdownMenu(
expanded = expanded,
onDismissRequest = {
expanded = false
focusManager.clearFocus()
},
modifier = Modifier
.width(with(LocalDensity.current) { dropDownWidth.toDp() }).border(width = 2.dp,color = Color.Blue)
) {
suggestions.forEach { label ->
DropdownMenuItem(onClick = {
selectedText = label
expanded = false
focusManager.clearFocus()
}) {
Text(text = label)
}
}
}
}
This got solved by adding focusable = false to PopupProperties for the DropdownMenu like shown below:
DropdownMenu(
expanded = suggestions.isNotEmpty(),
onDismissRequest = { suggestions.removeAll{true} },
properties = PopupProperties(focusable = false),
modifier = Modifier.fillMaxWidth()
)
Full working code for autocomplete with Jetpack Compose Below :
var expanded by remember { mutableStateOf(false) }
var masterSuggestions = listOf ("karan", "priya", "vihaan")
var suggestions = remember { mutableStateListOf <String>() }
var selectedText by remember { mutableStateOf("") }
var dropDownWidth by remember { mutableStateOf(0) }
Column() {
OutlinedTextField(
value = selectedText,
onValueChange = {
println ("inside value change")
suggestions.removeAll{true}
selectedText = it
for (name in masterSuggestions)
{
if (name.startsWith(selectedText)) {
suggestions.add(name)
}
}
},
modifier = Modifier.fillMaxWidth(),
label = {Text ("label")}
)
DropdownMenu(
expanded = suggestions.isNotEmpty(),
onDismissRequest = { suggestions.removeAll{true} },
properties = PopupProperties(focusable = false),
modifier = Modifier.fillMaxWidth()
) {
suggestions.forEach { label ->
DropdownMenuItem(onClick = {
selectedText = label
}) {
Text(text = label)
}
}
}
}