I am having an odd issue with jetpack compose dropdown menus.
I am working on an app that requires the user to register an account which requires 3 drop down menus (one for each question). The problem I am running into is that only the last one will update the text despite them all being written the exact same way.
I have tried to make a separate project and the problem still is still present. I have looked for tutorials but all of them have dealt with only one dropdown.
Here is the code any help would be greatly appreciated Thank you for your time and any help
Code:
package com.example.jetpackcomposedropdownissue
import android.content.Context
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.KeyboardArrowDown
import androidx.compose.material.icons.filled.KeyboardArrowUp
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.example.jetpackcomposedropdownissue.ui.theme.JetpackComposeDropDownIssueTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
JetpackComposeDropDownIssueTheme {
val context: Context = LocalContext.current
var securityQuestions = listOf(
"Please select a security Question",
"What is your favorite color ?",
"First pets name ?",
"Favorite musical artist or band ?",
"Favorite animal ?",
"Favorite tv Show ?"
)
var expanded by remember { mutableStateOf(false) }
var selectedSecurityQuestion1Text by remember { mutableStateOf(securityQuestions[0]) }
var selectedSecurityQuestion2Text by remember { mutableStateOf(securityQuestions[0]) }
var selectedSecurityQuestion3Text by remember { mutableStateOf(securityQuestions[0]) }
var icon = if (expanded) {
Icons.Filled.KeyboardArrowUp
} else {
Icons.Filled.KeyboardArrowDown
}
var createUserSecurityQuestion1AnswerState by remember { mutableStateOf("") }
var createUserSecurityQuestion2AnswerState by remember { mutableStateOf("") }
var createUserSecurityQuestion3AnswerState by remember { mutableStateOf("") }
var createUserUserNameTextFieldState by remember { mutableStateOf("") }
var createUserUserKeyTextFieldState by remember { mutableStateOf("") }
var createUserConfirmUserKeyTextFieldState by remember { mutableStateOf("") }
// A surface container using the 'background' color from the theme
Surface(color = MaterialTheme.colors.background) {
TextField(
value = selectedSecurityQuestion1Text,
onValueChange = { selectedSecurityQuestion1Text = it },
label = { Text(text = "Selected Security Question") },
trailingIcon = {
Icon(icon, "", Modifier.clickable { expanded = !expanded })
},
modifier = Modifier
.padding(PaddingValues(start = 70.dp, top = 40.dp))
.size(width = 315.dp, height = 60.dp)
)
DropdownMenu(expanded = expanded,
onDismissRequest = { expanded = false }
) {
securityQuestions.forEach { label ->
DropdownMenuItem(onClick = {
selectedSecurityQuestion1Text = label
expanded = true
}) {
Text(text = label)
}
}
}
TextField(
value = createUserSecurityQuestion1AnswerState,
label = {
Text("Answer 1")
},
onValueChange = {
createUserSecurityQuestion1AnswerState = it
},
singleLine = true,
modifier = Modifier
.padding(
PaddingValues(start = 70.dp, 110.dp)
)
.size(width = 210.dp, 48.dp)
)
TextField(
value = selectedSecurityQuestion2Text,
onValueChange = { selectedSecurityQuestion2Text = it },
label = { Text(text = "Selected Security Question") },
trailingIcon = {
Icon(icon, "", Modifier.clickable { expanded = !expanded })
},
modifier = Modifier
.padding(PaddingValues(start = 70.dp, top = 160.dp))
.size(width = 315.dp, height = 60.dp)
)
DropdownMenu(expanded = expanded,
onDismissRequest = { expanded = false }
) {
securityQuestions.forEach { label ->
DropdownMenuItem(onClick = {
selectedSecurityQuestion2Text = label
expanded = true
}) {
Text(text = label)
}
}
}
TextField(
value = createUserSecurityQuestion2AnswerState,
label = {
Text("Answer 2")
},
onValueChange = {
createUserSecurityQuestion2AnswerState = it
},
singleLine = true,
modifier = Modifier
.padding(
PaddingValues(start = 70.dp, 230.dp)
)
.size(width = 210.dp, 48.dp)
)
TextField(
value = selectedSecurityQuestion3Text,
onValueChange = { selectedSecurityQuestion3Text = it },
label = { Text(text = "Selected Security Question") },
trailingIcon = {
Icon(icon, "", Modifier.clickable { expanded = !expanded })
},
modifier = Modifier
.padding(PaddingValues(start = 70.dp, top = 280.dp))
.size(width = 315.dp, height = 60.dp)
)
DropdownMenu(expanded = expanded,
onDismissRequest = { expanded = false }
) {
securityQuestions.forEach { label ->
DropdownMenuItem(onClick = {
selectedSecurityQuestion3Text = label
expanded = true
}) {
Text(text = label)
}
}
}
TextField(
value = createUserSecurityQuestion3AnswerState,
label = {
Text("Answer 3")
},
onValueChange = {
createUserSecurityQuestion3AnswerState = it
},
singleLine = true,
modifier = Modifier
.padding(
PaddingValues(start = 70.dp, 350.dp)
)
.size(width = 210.dp, 48.dp)
)
TextField(
value = createUserUserNameTextFieldState,
label = {
Text("Username")
},
onValueChange = {
createUserUserNameTextFieldState = it
},
singleLine = true,
modifier = Modifier
.padding(
PaddingValues(start = 70.dp, 410.dp)
)
.size(width = 210.dp, 48.dp)
)
TextField(
value = createUserUserKeyTextFieldState,
label = {
Text("Userkey")
},
onValueChange = {
createUserUserKeyTextFieldState = it
},
singleLine = true,
modifier = Modifier
.padding(
PaddingValues(start = 70.dp, 470.dp)
)
.size(width = 210.dp, 48.dp)
)
TextField(
value = createUserConfirmUserKeyTextFieldState,
label = {
Text("Confirm Userkey")
},
onValueChange = {
createUserConfirmUserKeyTextFieldState = it
},
singleLine = true,
modifier = Modifier
.padding(
PaddingValues(start = 70.dp, 530.dp)
)
.size(width = 210.dp, 48.dp)
)
Button(
onClick = {
},
modifier = Modifier
.padding(PaddingValues(start = 100.dp, top = 600.dp))
.width(200.dp),
shape = RoundedCornerShape(20.dp)
) {
Text(text = "Create User")
}
Button(
onClick = {
},
modifier = Modifier
.padding(PaddingValues(start = 100.dp, top = 660.dp))
.width(200.dp),
shape = RoundedCornerShape(20.dp)
) {
Text(text = "Back To Login")
}
}
}
}
}
}
#Preview(showBackground = true)
#Composable
fun DefaultPreview() {
JetpackComposeDropDownIssueTheme {
}
}
Related
I have a couple of textFields and button where it opens the bottom Drawer. How to remove the empty space occupied by the bottom bar i don't want the empty space so that my textFields are near my button. I tried using modifier but after reducing the height the bottom bar is visible in the screen. is it possible to remove the redundant space
My Screen
Scaffold(scaffoldState = scaffoldState) {
Column(Modifier.padding(16.dp)) {
BottomDrawerSample()
OutlinedTextField(value = text,
onValueChange = { text = it },
label = { Text(text = "Title") },
singleLine = true)
OutlinedTextField(value = text,
onValueChange = { text = it },
label = { Text(text = "Brand") },
singleLine = true)
}
}
Bottom Drawer
#Composable
#OptIn(ExperimentalMaterialApi::class)
fun BottomDrawerSample() {
val (gesturesEnabled, toggleGesturesEnabled) = remember { mutableStateOf(true) }
val scope = rememberCoroutineScope()
Column {
Row(
modifier = Modifier
.fillMaxWidth()
.toggleable(
value = gesturesEnabled,
onValueChange = toggleGesturesEnabled
)
) {
Checkbox(gesturesEnabled, null)
Text(text = if (gesturesEnabled) "Gestures Enabled" else "Gestures Disabled")
}
val drawerState = rememberBottomDrawerState(BottomDrawerValue.Closed)
BottomDrawer(
gesturesEnabled = gesturesEnabled,
drawerState = drawerState,
drawerContent = {
Button(
modifier = Modifier
.align(Alignment.CenterHorizontally)
.padding(top = 16.dp),
onClick = { scope.launch { drawerState.close() } },
content = { Text("Close Drawer") }
)
LazyColumn {
items(25) {
ListItem(
text = { Text("Item $it") },
icon = {
Icon(
Icons.Default.Favorite,
contentDescription = "Localized description"
)
}
)
}
}
},
content = {
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
val openText = if (gesturesEnabled) "▲▲▲ Pull up ▲▲▲" else "Click the button!"
Text(text = if (drawerState.isClosed) openText else "▼▼▼ Drag down ▼▼▼")
Spacer(Modifier.height(20.dp))
Button(onClick = { scope.launch { drawerState.open() } }) {
Text("Click to open")
}
}
}
)
}
}
You should put your scaffold inside the content parameter of BottomDrawer.
Consider the example below:
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.selection.toggleable
import androidx.compose.material.BottomDrawer
import androidx.compose.material.BottomDrawerValue
import androidx.compose.material.Button
import androidx.compose.material.Checkbox
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.Icon
import androidx.compose.material.ListItem
import androidx.compose.material.OutlinedTextField
import androidx.compose.material.Scaffold
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material.rememberBottomDrawerState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.launch
#Composable
fun Tear() {
BottomDrawerSample()
}
#Composable
#OptIn(ExperimentalMaterialApi::class)
fun BottomDrawerSample() {
val (gesturesEnabled, toggleGesturesEnabled) = remember { mutableStateOf(true) }
val scope = rememberCoroutineScope()
Column {
Row(
modifier = Modifier
.fillMaxWidth()
.toggleable(
value = gesturesEnabled,
onValueChange = toggleGesturesEnabled
)
.background(Color.Red)
) {
Checkbox(gesturesEnabled, null)
Text(text = if (gesturesEnabled) "Gestures Enabled" else "Gestures Disabled")
}
val drawerState = rememberBottomDrawerState(BottomDrawerValue.Closed)
BottomDrawer(
gesturesEnabled = gesturesEnabled,
drawerState = drawerState,
drawerContent = {
Button(
modifier = Modifier
.align(Alignment.CenterHorizontally)
.padding(top = 16.dp),
onClick = { scope.launch { drawerState.close() } },
content = { Text("Close Drawer") }
)
LazyColumn {
items(25) {
ListItem(
text = { Text("Item $it") },
icon = {
Icon(
Icons.Default.Favorite,
contentDescription = "Localized description"
)
}
)
}
}
},
content = {
Scaffold { padding ->
var text by remember {
mutableStateOf("")
}
Column(Modifier.padding(16.dp)) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
val openText = if (gesturesEnabled) "▲▲▲ Pull up ▲▲▲" else "Click the button!"
Text(text = if (drawerState.isClosed) openText else "▼▼▼ Drag down ▼▼▼")
Spacer(Modifier.height(20.dp))
Button(onClick = { scope.launch { drawerState.open() } }) {
Text("Click to open")
}
OutlinedTextField(
value = text,
onValueChange = {
text = it
},
label = { Text(text = "Title") },
singleLine = true
)
OutlinedTextField(
value = text,
onValueChange = { text = it },
label = { Text(text = "Brand") },
singleLine = true
)
}
}
}
}
)
}
}
So, I have an AlertDialog with a ListItem inside, which in turn has 2 TextFields inside:
val keyboardController = LocalSoftwareKeyboardController.current
val focusManager = LocalFocusManager.current
val (nameFieldFocus, valueFieldFocus) = remember{FocusRequester.createRefs()}
AlertDialog(
...
text = {
ListItem(
...
leadingContent = {
Column(verticalArrangement = Arrangement.spacedBy(10.dp)) {
TextField(
modifier = Modifier.focusRequester(nameFieldFocus).focusProperties{next = valueFieldFocus}.focusable(),
value = someValue,
onValueChange = {someValue = it},
label = {Text(text = "Some label")},
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
keyboardActions = KeyboardActions(onNext = {focusManager.moveFocus(FocusDirection.Next)}),
singleLine = true
)
TextField(
modifier = Modifier.focusRequester(valueFieldFocus).focusProperties{previous = nameFieldFocus}.focusable(),
value = anotherValue,
onValueChange = {anotherValue = it},
label = {Text(text = "Another label")},
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
keyboardActions = KeyboardActions(onDone = {focusManager.clearFocus()}),
singleLine = true
)
}
}
)
}
)
The problem is, when I press the action key on the keyboard, no focus change occurs, regardless of whether the text fields have text in them or not. I also tried onNext = null and onDone = null, which is supposed to use the default implementation according to the documentation, but that also doesn't behave as expected.
P.S. I'm using androidx.compose.material3.
You should call val focusManager = LocalFocusManager.current inside AlertDialog. Also, remove the Modifier.focusable().
AlertDialog(
text = {
val focusManager = LocalFocusManager.current
val (nameFieldFocus, valueFieldFocus) = remember { FocusRequester.createRefs() }
ListItem(
leadingContent = {
Column(verticalArrangement = Arrangement.spacedBy(10.dp)) {
TextField(
modifier = Modifier
.focusRequester(nameFieldFocus)
.focusProperties { next = valueFieldFocus },
value = someValue,
onValueChange = { someValue = it },
label = { Text(text = "Some label") },
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
keyboardActions = KeyboardActions(onNext = {
focusManager.moveFocus(
FocusDirection.Next
)
}),
singleLine = true
)
TextField(
modifier = Modifier
.focusRequester(valueFieldFocus)
.focusProperties { previous = nameFieldFocus },
value = anotherValue,
onValueChange = { anotherValue = it },
label = { Text(text = "Another label") },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
singleLine = true
)
}
},
headlineText = {
Text(text = "Headline Text")
}
)
},
confirmButton = {
Button(onClick = { /*TODO*/ }) {
Text(text = "Confirm")
}
},
onDismissRequest = {
}
)
I'm trying to do a ConstraintLayout in jetpack compose as I am having problems doing too many nested Columns and Rows.
Here is what I have:
#Composable
fun StateAndZipLayout(
modifier: Modifier,
onFormChanged: (FormType, String) -> Unit,
selectedLocation: Address,
stateError: Boolean,
zipError: Boolean
) {
val configuration = LocalConfiguration.current
val screenWidth = configuration.screenWidthDp.dp
val componentWidth = (screenWidth - 48.dp)/2
ConstraintLayout(modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()) {
val rightGuideline = createGuidelineFromStart(0.5f)
val (stateDropDown, shippingField) = createRefs()
StateSelection(
modifier = modifier
.constrainAs(stateDropDown) {
top.linkTo(parent.top)
bottom.linkTo(parent.bottom)
end.linkTo(rightGuideline, margin = 8.dp)
}
.requiredWidth(componentWidth)
.wrapContentHeight(),
onFormChanged = onFormChanged,
selectedLocation = selectedLocation,
label = "State",
error = stateError,
)
ShippingField(
modifier = modifier
.constrainAs(shippingField) {
start.linkTo(rightGuideline, margin = 8.dp)
top.linkTo(stateDropDown.top)
bottom.linkTo(stateDropDown.bottom)
end.linkTo(parent.end)
}
.requiredWidth(componentWidth),
onFormChanged = onFormChanged,
formType = FormType.SHIPPING_ZIP,
label = "Zip",
valueField = selectedLocation.zipCode,
error = zipError
)
}
}
Here is my state selection view:
#Composable
fun StateSelection(
modifier: Modifier,
onFormChanged: (FormType, String) -> Unit,
selectedLocation: Address,
error: Boolean,
label: String
) {
// State variables
val statesMap = AddressUtils.mapOfAmericanStatesToValue
var stateName: String by remember { mutableStateOf(selectedLocation.shippingState) }
var expanded by remember { mutableStateOf(false) }
val focusManager = LocalFocusManager.current
var errorState by remember { mutableStateOf(error) }
Column {
Row(
Modifier
.clickable {
expanded = !expanded
},
) { // Anchor view
TextField(
modifier = Modifier
.fillMaxWidth(),
value = stateName,
onValueChange = {
onFormChanged(FormType.SHIPPING_COUNTRY, it)
},
label = { Text(text = label) },
textStyle = MaterialTheme.typography.subtitle1,
singleLine = true,
trailingIcon = {
IconButton(onClick = { expanded = true }) {
Icon(
imageVector = Icons.Filled.ArrowDropDown,
contentDescription = "",
tint = if (errorState) MaterialTheme.colors.error
else MaterialTheme.colors.onPrimary
)
}
},
keyboardActions = KeyboardActions(onNext = {
focusManager.moveFocus(
FocusDirection.Down
)
}),
keyboardOptions = KeyboardOptions(
imeAction = ImeAction.Done,
keyboardType = KeyboardType.Text
),
colors = TextFieldDefaults.textFieldColors(
cursorColor = MaterialTheme.colors.secondary,
textColor = MaterialTheme.colors.onPrimary,
focusedLabelColor = if (errorState) MaterialTheme.colors.error
else MaterialTheme.colors.secondary,
focusedIndicatorColor = if (errorState) MaterialTheme.colors.error
else MaterialTheme.colors.secondary,
backgroundColor = MaterialTheme.colors.secondaryVariant
)
) // state name label
DropdownMenu(expanded = expanded, onDismissRequest = {
expanded = false
}) {
statesMap.asIterable().iterator().forEach {
val (key, value) = it
DropdownMenuItem(
onClick = {
expanded = false
stateName = key
onFormChanged(FormType.SHIPPING_STATE, key)
},
modifier = Modifier.fillMaxWidth()
) {
Text(text = key)
}
}
}
}
if (errorState && error) {
ErrorMessages(modifier = modifier, message = "$label is required")
}
}
}
This is what it looks like, the state drop down and the zip code field are overlapping:
I'm trying to do a constraint layout in my Compose view. Unfortunately, I get no output. I was having problems using columns to show error output after the TextField, so in desperation, I am using a constraint layout. Here is the component:
#Composable
fun StateAndZip(
modifier: Modifier,
onFormChanged: (FormType, String) -> Unit,
selectedLocation: Address,
stateError: Boolean,
zipError: Boolean
) {
ConstraintLayout {
val (stateDropDown, shippingField) = createRefs()
StateSelection(
modifier = modifier.constrainAs(stateDropDown) {
top.linkTo(parent.top)
bottom.linkTo(parent.bottom)
start.linkTo(parent.start)
end.linkTo(shippingField.start, margin = 8.dp)
width = Dimension.preferredWrapContent
height = Dimension.fillToConstraints
},
onFormChanged = onFormChanged,
selectedLocation = selectedLocation,
label = "State",
error = stateError,
)
ShippingField(
modifier = modifier.constrainAs(shippingField) {
top.linkTo(parent.top)
bottom.linkTo(parent.bottom)
end.linkTo(parent.end)
width = Dimension.preferredWrapContent
height = Dimension.fillToConstraints
},
onFormChanged = onFormChanged,
formType = FormType.SHIPPING_ZIP,
label = "Zip",
valueField = selectedLocation.zipCode,
error = zipError
)
}
}
Here is where I'm calling it:
#Composable
fun ShippingForm(
modifier: Modifier = Modifier,
onFormChanged: (FormType, String) -> Unit,
selectedLocation: Address,
validateErrors: Boolean
) {
var cityError by remember { mutableStateOf(false)}
var stateError by remember { mutableStateOf(false)}
var countryError by remember {mutableStateOf(false)}
var zipError by remember { mutableStateOf(false)}
if (validateErrors) {
if (selectedLocation.shippingCity.isBlank()) {
cityError = true
}
if (selectedLocation.shippingState.isBlank()) {
stateError = true
}
if (selectedLocation.zipCode.isBlank()) {
zipError = true
}
if (selectedLocation.shippingCountry.isBlank()) {
countryError = true
}
}
//already in a column, so no need to add another one.
Spacer(modifier = Modifier.height(spacerHeight()))
ShippingField(
modifier = modifier,
onFormChanged = onFormChanged,
formType = FormType.SHIPPING_2,
label = "Apartment, suite, etc. (Optional)",
valueField = selectedLocation.shipping2,
error = false
)
Spacer(modifier = Modifier.height(spacerHeight()))
ShippingField(
modifier = modifier,
onFormChanged = onFormChanged,
formType = FormType.SHIPPING_CITY,
label = "City",
valueField = selectedLocation.shippingCity,
error = cityError
)
Spacer(modifier = Modifier.height(spacerHeight()))
StateAndZip(
modifier = modifier,
onFormChanged = onFormChanged,
selectedLocation = selectedLocation,
stateError = stateError,
zipError = zipError
)
Spacer(modifier = Modifier.height(spacerHeight()))
CountrySelection(
onFormChanged = onFormChanged,
selectedLocation = selectedLocation,
label = "Country",
error = countryError
)
}
The individual views are quite complex. Here is the stateDropDown:
#Composable
fun StateSelection(
modifier: Modifier,
onFormChanged: (FormType, String) -> Unit,
selectedLocation: Address,
error: Boolean,
label: String
) {
// State variables
val statesMap = AddressUtils.mapOfAmericanStatesToValue
var stateName: String by remember { mutableStateOf(selectedLocation.shippingState) }
var expanded by remember { mutableStateOf(false)}
val focusManager = LocalFocusManager.current
var errorState by remember { mutableStateOf(error)}
// Create references for the composables to constrain
Box(
contentAlignment = Alignment.CenterStart,
modifier = modifier
) {
Row(
Modifier
.clickable {
expanded = !expanded
},
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically
) { // Anchor view
TextField(
modifier = Modifier
.fillMaxWidth(),
value = stateName,
onValueChange = {
onFormChanged(FormType.SHIPPING_COUNTRY, it)
},
label = { Text(text = label) },
textStyle = MaterialTheme.typography.subtitle1,
singleLine = true,
trailingIcon = {
IconButton(onClick = { expanded = true }) {
Icon(
imageVector = Icons.Filled.ArrowDropDown,
contentDescription = "",
tint = if (errorState) MaterialTheme.colors.error
else MaterialTheme.colors.onPrimary
)
}
},
keyboardActions = KeyboardActions(onNext = {
focusManager.moveFocus(
FocusDirection.Down
)
}),
keyboardOptions = KeyboardOptions(
imeAction = ImeAction.Done,
keyboardType = KeyboardType.Text
),
colors = TextFieldDefaults.textFieldColors(
cursorColor = MaterialTheme.colors.secondary,
textColor = MaterialTheme.colors.onPrimary,
focusedLabelColor = if (errorState) MaterialTheme.colors.error
else MaterialTheme.colors.secondary,
focusedIndicatorColor = if (errorState) MaterialTheme.colors.error
else MaterialTheme.colors.secondary,
backgroundColor = MaterialTheme.colors.secondaryVariant
)
) // state name label
DropdownMenu(expanded = expanded, onDismissRequest = {
expanded = false
}) {
statesMap.asIterable().iterator().forEach {
val (key, value) = it
DropdownMenuItem(
onClick = {
expanded = false
stateName = key
onFormChanged(FormType.SHIPPING_STATE, key)
},
modifier = Modifier.fillMaxWidth()
) {
Text(text = key)
}
}
}
}
}
if (errorState && error) {
ErrorMessages(modifier = modifier, message = "$label is required")
}
}
I have started to learn jetpack compose. I want to show bottomsheet in click of IconButton.But i got error #Composable invocations can only happen from the context of a #Composable function how I can implement this logic.
Here is ui screen
Here is code
#RequiresApi(Build.VERSION_CODES.O)
#OptIn(ExperimentalMaterialApi::class)
#Composable
fun AddTaskScreen(navController: NavController) {
var taskTitle by remember { mutableStateOf("") }
val currentDate = SimpleDateFormat("dd-MM-yyyy", Locale.getDefault()).format(Date())
var taskDescription by remember { mutableStateOf("") }
val taskDuration by remember { mutableStateOf(currentDate.toString()) }
val taskTypes = listOf("Urgent", "Medium", "High")
var expanded by remember { mutableStateOf(false) }
var selectedOptionText by remember { mutableStateOf(taskTypes[0]) }
val clickHandler: () -> Unit = {
DateBottomSheet()
}
Column() {
AppToolBar(title = "AddTaskScreen") {
navController.navigateUp()
}
OutlinedTextField(
value = taskTitle,
label = { Text(text = "Please input task title") },
onValueChange = { text -> taskTitle = text },
modifier = textFieldModifier
)
OutlinedTextField(
value = taskDescription,
label = { Text(text = "Please input task description") },
onValueChange = { text -> taskDescription = text },
modifier = textFieldModifier.height(200.dp)
)
ExposedDropdownMenuBox(
expanded = expanded,
onExpandedChange = {
expanded = !expanded
},
modifier = Modifier
.fillMaxWidth()
.padding(20.dp)
) {
OutlinedTextField(
readOnly = true,
value = selectedOptionText,
onValueChange = { },
label = { Text("Task priority") },
trailingIcon = {
ExposedDropdownMenuDefaults.TrailingIcon(
expanded = expanded
)
},
modifier = Modifier.fillMaxWidth()
)
ExposedDropdownMenu(
expanded = expanded,
onDismissRequest = {
expanded = false
}
) {
taskTypes.forEach { selectionOption ->
DropdownMenuItem(
onClick = {
selectedOptionText = selectionOption
expanded = false
}
) {
Text(text = selectionOption)
}
}
}
}
//task time textField
OutlinedTextField(
value = taskDuration,
readOnly = true,
label = { Text(text = "Please select task duration") },
onValueChange = { text -> taskDescription = text },
modifier = textFieldModifier,
trailingIcon = {
IconButton(
onClick = {
clickHandler.invoke()
}) {
Icon(Icons.Filled.DateRange, contentDescription = "")
}
}
)
}
}
#OptIn(ExperimentalMaterialApi::class)
#Composable
fun DateBottomSheet() {
val bottomSheetScaffoldState = rememberBottomSheetScaffoldState(
bottomSheetState = rememberBottomSheetState(
initialValue = BottomSheetValue.Collapsed
)
)
BottomSheetScaffold(
sheetContent = {
Column() {
Text(text = "ThisIsBottomSheet")
Text(text = "ThisIsBottomSheet")
Text(text = "ThisIsBottomSheet")
Text(text = "ThisIsBottomSheet")
Text(text = "ThisIsBottomSheet")
}
},
scaffoldState = bottomSheetScaffoldState
) {
}
But i got error #Composable invocations can only happen from the context of a #Composable function how I can implement this logic
You can simply use mutabelState for handling your button click event to show Bottom Sheet.
You can do following changes ->
#RequiresApi(Build.VERSION_CODES.O)
#OptIn(ExperimentalMaterialApi::class)
#Composable
fun AddTaskScreen(navController: NavController) {
var taskTitle by remember { mutableStateOf("") }
val currentDate = SimpleDateFormat("dd-MM-yyyy", Locale.getDefault()).format(Date())
var taskDescription by remember { mutableStateOf("") }
val taskDuration by remember { mutableStateOf(currentDate.toString()) }
val taskTypes = listOf("Urgent", "Medium", "High")
var expanded by remember { mutableStateOf(false) }
var selectedOptionText by remember { mutableStateOf(taskTypes[0]) }
var openBottomSheet by rememberSaveable { mutableStateOf(false) }
Column() {
AppToolBar(title = "AddTaskScreen") {
navController.navigateUp()
}
OutlinedTextField(
value = taskTitle,
label = { Text(text = "Please input task title") },
onValueChange = { text -> taskTitle = text },
modifier = textFieldModifier
)
OutlinedTextField(
value = taskDescription,
label = { Text(text = "Please input task description") },
onValueChange = { text -> taskDescription = text },
modifier = textFieldModifier.height(200.dp)
)
ExposedDropdownMenuBox(
expanded = expanded,
onExpandedChange = {
expanded = !expanded
},
modifier = Modifier
.fillMaxWidth()
.padding(20.dp)
) {
OutlinedTextField(
readOnly = true,
value = selectedOptionText,
onValueChange = { },
label = { Text("Task priority") },
trailingIcon = {
ExposedDropdownMenuDefaults.TrailingIcon(
expanded = expanded
)
},
modifier = Modifier.fillMaxWidth()
)
ExposedDropdownMenu(
expanded = expanded,
onDismissRequest = {
expanded = false
}
) {
taskTypes.forEach { selectionOption ->
DropdownMenuItem(
onClick = {
selectedOptionText = selectionOption
expanded = false
}
) {
Text(text = selectionOption)
}
}
}
}
//task time textField
OutlinedTextField(
value = taskDuration,
readOnly = true,
label = { Text(text = "Please select task duration") },
onValueChange = { text -> taskDescription = text },
modifier = textFieldModifier,
trailingIcon = {
IconButton(
onClick = {
openBottomSheet = true
}) {
Icon(Icons.Filled.DateRange, contentDescription = "")
}
}
)
if (openBottomSheet) {
DateBottomSheet()
}
}
}
#OptIn(ExperimentalMaterialApi::class)
#Composable
fun DateBottomSheet() {
val bottomSheetScaffoldState = rememberBottomSheetScaffoldState(
bottomSheetState = rememberBottomSheetState(
initialValue = BottomSheetValue.Collapsed
)
)
BottomSheetScaffold(
sheetContent = {
Column() {
Text(text = "ThisIsBottomSheet")
Text(text = "ThisIsBottomSheet")
Text(text = "ThisIsBottomSheet")
Text(text = "ThisIsBottomSheet")
Text(text = "ThisIsBottomSheet")
}
},
scaffoldState = bottomSheetScaffoldState
) {
}