I'm using room database with jetpack compose,upon deleting all items the ui is not recomposing unless i move to another screen and come back again , any help would be appreciated , Thank you
This is my code
fun CheckOutScreen(shopViewModel: ShopViewModel,
navHostController: NavHostController,
list : SnapshotStateList<PurchaseModel> ) {
val firebaseAuth = FirebaseAuth.getInstance()
val databaseReference = FirebaseDatabase.getInstance().reference
val context = LocalContext.current
val totalAmount = remember { mutableStateOf(0.0) }
val count = remember { mutableStateOf(0) }
val isDialogShowing = remember { mutableStateOf(false) }
val isProgressShowing = remember { mutableStateOf(false) }
val isDataSaving = remember { mutableStateOf(false) }
val isMovingToAnotherScreen = remember { mutableStateOf(false)}
list.forEach {
if(count.value < list.size){
totalAmount.value += it.totalPrice
count.value++
}
}
Scaffold{
Column {
Row(modifier = Modifier.fillMaxWidth()) {
Text(text = "Cart", modifier = Modifier
.weight(1f)
.padding(10.dp), color = Color.DarkGray,
style = MaterialTheme.typography.h1, fontSize = 17.sp)
IconButton(onClick = {
isDialogShowing.value = true
}) {
Icon(Icons.Filled.Delete,"")
}
}
if(list.size == 0){
Box(contentAlignment = Alignment.Center, modifier = Modifier.fillMaxSize()) {
Text("No Items In The Cart", style = MaterialTheme.typography.h1,
color = Color.DarkGray , fontSize = 17.sp)
}
}
else {
Box(contentAlignment = Alignment.Center){
Column() {
Text(text = "Purchase To : " + list[0].fullName, modifier = Modifier.padding(start = 10.dp,
end = 10.dp, top = 5.dp), fontSize = 17.sp,style = MaterialTheme.typography.body1)
LazyColumn(modifier = Modifier
.weight(1f)
.padding(top = 10.dp)){
items(list.size, key = { item -> item.hashCode()}){ pos ->
val dismissState = rememberDismissState(confirmStateChange = {
if(it == DismissValue.DismissedToStart || it == DismissValue.DismissedToEnd){
shopViewModel.deletePurchase(list[pos].purchaseId!!)
Toast.makeText(context,"Item Successfully Dismissed",Toast.LENGTH_SHORT).show()
}
true
})
if(!isMovingToAnotherScreen.value){
SwipeToDismiss(
state = dismissState,
background = {},
dismissContent = {
Card(elevation = 10.dp, modifier = Modifier
.clip(shape = RoundedCornerShape(6.dp))
.padding(5.dp)) {
Row(modifier = Modifier.fillMaxWidth()) {
Box(contentAlignment = Alignment.Center) {
AsyncImage(model = ImageRequest.Builder(context).data(list[pos].productImage).build(),
contentScale = ContentScale.Crop,
modifier = Modifier
.width(80.dp)
.height(80.dp)
.padding(10.dp),
contentDescription = "")
}
Column(modifier = Modifier.weight(1f)) {
Text(list[pos].title, modifier = Modifier.padding(5.dp), fontWeight = FontWeight.Bold)
Text("Quantity : " + list[pos].totalQuantity)
Text("Due To : " + list[pos].totalPrice)
}
}
}
},
directions = setOf(DismissDirection.StartToEnd,DismissDirection.EndToStart))
}
}
}
Card(modifier = Modifier
.fillMaxWidth()
.clip(shape = RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp))) {
Button(modifier = Modifier.padding(start = 20.dp, end = 20.dp),onClick = {
isProgressShowing.value = true
}) {
Text("Pay ${totalAmount.value}")
}
}
}
}
AnimatedVisibility(visible = isProgressShowing.value) {
CircularProgressIndicator(color = Color.DarkGray)
}
}
}
if(isProgressShowing.value){
val map = hashMapOf<String,Any>()
map["list"] = list
map["uid"] = firebaseAuth.currentUser!!.uid
map["totalPrice"] = totalAmount.value.toString()
val db = databaseReference.child("Purchases")
db.child(firebaseAuth.currentUser!!.uid)
.child(Calendar.getInstance().timeInMillis.toString())
.setValue(map)
.addOnSuccessListener {
isDataSaving.value = true
}
.addOnFailureListener {
}
}
if(isDialogShowing.value){
AlertDialog(
onDismissRequest = { isDialogShowing.value = false },
confirmButton = {
TextButton(onClick = {
shopViewModel.deletePurchases()
isDialogShowing.value = false
Toast.makeText(context,"All items are successfully removed",Toast.LENGTH_SHORT).show()
}) {
Text("Proceed")
}
},
dismissButton = {
TextButton(onClick = { isDialogShowing.value = false }) {
Text("Cancel")
}
},
title = { Text("Removing Items ") },
text = { Text("Do you want to remove items from cart ! ") }
)
}
if(isDataSaving.value){
LaunchedEffect(Unit){
delay(3000)
isMovingToAnotherScreen.value = true
shopViewModel.deletePurchases()
isProgressShowing.value = false
isDataSaving.value = false
navHostController.navigate(AppRouting.Payment.route)
}
}
}
BackHandler {
isMovingToAnotherScreen.value = true
navHostController.popBackStack()
}
}
It's most likely because of not updating value of your State. In you example it's always the same list. You either need to set value with new instance of list or use SnapshotStateList. You can check this answer also.
Related
I am currently observing some strange behavior when using an OutlinedTextField inside of an ExposedDropdownMenuBox. If I have already clicked into the OutlinedTextField once, the focus always jumps back to it when I click into another field or sometimes when I'm simply scrolling through my view - preferably after the keyboard is closed manually, if the keyboard keeps visible I can click into the other input fields without any problem.
Has anyone experienced a similar behaviour and knows how to fix this?
I'm using version 1.3.1 of androidx.compose.material:material
My Composable looks like this:
#OptIn(ExperimentalMaterialApi::class)
#Composable
fun ContributorComposable(contributor: Contributor, logbookEntryState: LogbookEntryState, enabled: Boolean) {
Spacer(modifier = Modifier.height(8.dp))
val addContributor: #Composable () -> Unit = {
IconButton(onClick = { logbookEntryState.contributors.add(Contributor()) }) {
Icon(imageVector = Icons.Filled.Add, contentDescription = "")
}
}
var expanded by remember { mutableStateOf(false) }
val options = logbookEntryState.contributorsDropDownList
Column(modifier = Modifier.fillMaxWidth()) {
Row(verticalAlignment = CenterVertically, horizontalArrangement = Arrangement.End, modifier = Modifier.fillMaxWidth()) {
Spacer(modifier = Modifier.width(40.dp))
ExposedDropdownMenuBox(
expanded = expanded,
onExpandedChange = { expanded = !expanded && enabled },
modifier = Modifier.focusable(false)
) {
Row(verticalAlignment = CenterVertically, horizontalArrangement = Arrangement.End, modifier = Modifier.fillMaxWidth()) {
OutlinedTextField(
colors = if (
contributor.company.value.isNotBlank() ||
contributor.registrationNumber.value.isNotBlank() ||
contributor.userCertificationNumber.value.isNotBlank() ||
contributor.companyCertificationNumber.value.isNotBlank() ||
contributor.userFullname.value.isNotBlank()
) TextFieldDefaults.outlinedTextFieldColors(unfocusedBorderColor = db_primary_red) else TextFieldDefaults.outlinedTextFieldColors(),
modifier = Modifier
.weight(1f),
value = contributor.userFullname.value,
onValueChange = { newText ->
contributor.userFullname.value = newText
viewModel.findContributorByName(logbookEntryState, newText)
expanded = true
},
label = { Text(stringResource(id = R.string.logbook_entry_create_contributors)) },
trailingIcon = if (enabled) addContributor else null,
enabled = enabled,
)
if (enabled && logbookEntryState.contributors.size > 1) {
IconButton(onClick = { logbookEntryState.contributors.remove(contributor) }, modifier = Modifier.width(40.dp)) {
Icon(imageVector = Icons.Filled.Delete, contentDescription = "")
}
} else {
Spacer(modifier = Modifier.width(40.dp))
}
}
if (options.isNotEmpty())
ExposedDropdownMenu(
expanded = expanded,
modifier = Modifier.background(db_secondary_white),
onDismissRequest = { expanded = false },
) {
options.forEach { selectionOption ->
DropdownMenuItem(
modifier = Modifier
.background(db_secondary_white)
.exposedDropdownSize(true),
onClick = {
contributor.internalId = selectionOption.internalId
contributor.userFullname.value = selectionOption.userFullname.value
contributor.userFullnameErrorMessage.value = ""
contributor.company.value = selectionOption.company.value
contributor.companyErrorMessage.value = ""
contributor.registrationNumber.value = selectionOption.registrationNumber.value
contributor.registrationNumberErrorMessage.value = ""
contributor.userCertificationNumber.value = selectionOption.userCertificationNumber.value
contributor.userCertificationNumberErrorMessage.value = ""
contributor.companyCertificationNumber.value = selectionOption.companyCertificationNumber.value
contributor.companyCertificationNumberErrorMessage.value = ""
contributor.id = selectionOption.id
contributor.entryId = selectionOption.entryId
contributor.createdBy = selectionOption.createdBy
contributor.creationDate = selectionOption.creationDate
expanded = false
}
) {
Column(modifier = Modifier.fillMaxWidth()) {
Row(verticalAlignment = CenterVertically) {
Text(text = selectionOption.userFullname.value ?: "")
}
// if there are users with the same name, we show additional information
if (options.groupBy { it.userFullname }.size > 1) {
Row(verticalAlignment = CenterVertically) {
Text(text = selectionOption.company.value ?: "")
}
Row(verticalAlignment = CenterVertically) {
Text(text = selectionOption.registrationNumber.value ?: "")
}
if (viewModel.shouldShowCertificatenummber()) {
Row(verticalAlignment = CenterVertically) {
Text(text = selectionOption.userCertificationNumber.value ?: "")
}
}
if (viewModel.shouldShowCertificatenummber()) {
Row(verticalAlignment = CenterVertically) {
Text(text = selectionOption.companyCertificationNumber.value ?: "")
}
}
DefaultDivider()
Spacer(modifier = Modifier.height(8.dp))
}
}
}
}
}
}
}
ErrorComposable(contributor.userFullnameErrorMessage.value)
}
CustomTextField(
label = R.string.logbook_entry_create_company_contributors,
input = contributor.company,
errorMessage = contributor.companyErrorMessage,
enabled = enabled,
shouldShow = true,
mandatoryField = contributor.userFullname.value.isNotBlank(),
)
CustomTextField(
label = R.string.logbook_entry_create_registrationnr_contributors,
input = contributor.registrationNumber,
errorMessage = contributor.registrationNumberErrorMessage,
enabled = enabled,
shouldShow = viewModel.logbook?.classEntity?.hasRegistrationNumber == true,
mandatoryField = contributor.userFullname.value.isNotBlank(),
)
CustomTextField(
label = R.string.logbook_entry_create_certification_numbers_contributors,
input = contributor.userCertificationNumber,
errorMessage = contributor.userCertificationNumberErrorMessage,
enabled = enabled,
shouldShow = viewModel.shouldShowCertificatenummber(),
mandatoryField = contributor.userFullname.value.isNotBlank(),
)
CustomTextField(
label = R.string.logbook_entry_create_company_certification_numbers_contributors,
input = contributor.companyCertificationNumber,
errorMessage = contributor.companyCertificationNumberErrorMessage,
enabled = enabled,
shouldShow = viewModel.shouldShowCertificatenummber(),
mandatoryField = contributor.userFullname.value.isNotBlank(),
)
}
Scaffold(bottomBar = {
if (currentRoute(navController) != Screen.SplashScreen.route)
// AnimatedVisibility(visible= bottomBarState.value ) {
BottomAppBar(modifier = modifier
.height(80.dp)
.padding(5.dp)
.clip(shape = RoundedCornerShape(60.dp)), containerColor = Color.Gray, tonalElevation = 5.dp) {
items.forEach { items ->
val selected = items.route == backStackEntry.value?.destination?.route
NavigationBarItem(
selected = items.route == navController.currentDestination?.route,
onClick = { onItemClick(items) },
icon = {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
if (items.badgeCount > 0) {
BadgedBox(badge = {
Text(text = items.badgeCount.toString())
}) {
Icon(imageVector = items.icon, contentDescription = items.name)
}
} else {
Icon(imageVector = items.icon, contentDescription = items.name)
}
if (selected) {
Text(text = items.name, textAlign = TextAlign.Center, fontSize = 10.sp)
}
}
},
colors = NavigationBarItemDefaults.colors(
selectedIconColor = Color.Green,
unselectedIconColor = Color.Red
,
selectedTextColor = Color.Green,
indicatorColor = Color.DarkGray
)
)
}
}
//}
}){
}
You can use something like this:
Your extension function:
#Stable
#Composable
private fun NavController.shouldShowBottomNavigationAsState(shouldHideRoutes: List<String>): State<Boolean> {
val shouldShowBottomNavigation = remember { mutableStateOf(false) }
DisposableEffect(this) {
val listener = NavController.OnDestinationChangedListener { _, destination, _ ->
if (shouldHideRoutes.any { it == destination.route }) {
shouldShowBottomNavigation.value = true
} else {
shouldShowBottomNavigation.value = false
}
}
addOnDestinationChangedListener(listener)
onDispose {
removeOnDestinationChangedListener(listener)
}
}
return shouldShowBottomNavigation
}
Usage:
val useBottomNavigation by navController.shouldShowBottomNavigationAsState(
listOf(
"route1","route2" // put your routes that you want to hide bottom navigation view
),
)
if (useBottomNavigation) {
// your bottom navigation composable
}
I have basic Login screen in my app, which looks like this:
LoginScreen.kt:
#Composable
fun LoginScreen(navController: NavController, viewModel: LoginViewModel) {
val userEmail = viewModel.userEmail.collectAsState()
val userPassword = viewModel.userPassword.collectAsState()
val isLoginPending = viewModel.isLoginPending.collectAsState()
val isLoginButtonEnabled = viewModel.isLoginButtonEnabled.collectAsState()
val scaffoldState = rememberScaffoldState()
if (viewModel.getOnboardingStatus() == OnboardingStatus.NOT_COMPLETED) {
navController.navigate(Screen.Onboarding.route)
}
LaunchedEffect(key1 = true) {
viewModel.loginError.collectLatest {
it?.let {
scaffoldState.snackbarHostState.showSnackbar(message = it)
}
}
}
Scaffold(
modifier = Modifier
.fillMaxSize()
.recomposeHighlighter(),
scaffoldState = scaffoldState
) {
Box(modifier = Modifier
.fillMaxSize()
.recomposeHighlighter()) {
if (isLoginPending.value) {
ProgressBar(
modifier = Modifier
.align(Alignment.Center)
.recomposeHighlighter(),
)
}
Column(
modifier = Modifier
.fillMaxWidth()
.align(Alignment.Center)
.recomposeHighlighter(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
text = "Login",
fontWeight = FontWeight.Bold,
fontSize = 36.sp
)
Spacer(modifier = Modifier
.height(64.dp)
.recomposeHighlighter())
UserInputTextField(
modifier = Modifier
.fillMaxWidth()
.padding(start = 16.dp, end = 16.dp)
.recomposeHighlighter(),
label = "Email",
inputState = userEmail.value,
onValueChange = { viewModel.onEvent(LoginEvent.EnteredEmail(it)) },
isLoginPending = isLoginPending.value
)
Spacer(modifier = Modifier
.height(8.dp)
.recomposeHighlighter())
UserInputTextField(
modifier = Modifier
.fillMaxWidth()
.padding(start = 16.dp, end = 16.dp)
.recomposeHighlighter(),
label = "Password",
inputState = userPassword.value,
onValueChange = { viewModel.onEvent(LoginEvent.EnteredPassword(it)) },
isLoginPending = isLoginPending.value,
keyboard = KeyboardOptions(keyboardType = KeyboardType.Password),
visualTransformation = PasswordVisualTransformation()
)
Spacer(modifier = Modifier
.height(32.dp)
.recomposeHighlighter())
Button(
modifier = Modifier
.fillMaxWidth()
.padding(
start = 16.dp,
end = 16.dp
)
.recomposeHighlighter(),
enabled = isLoginButtonEnabled.value,
onClick = {
viewModel.onEvent(LoginEvent.Login)
}
) {
Text(text = "Login")
}
}
ClickableButton(
modifier = Modifier
.align(Alignment.BottomCenter)
.padding(bottom = 32.dp)
.recomposeHighlighter(),
text = "No account? Click to register",
onTextClicked = { navController.navigate(Screen.Register.route) }
)
}
}
}
#Composable
private fun UserInputTextField(
modifier: Modifier,
label: String,
inputState: String,
onValueChange: (String) -> Unit,
isLoginPending: Boolean,
keyboard: KeyboardOptions? = null,
visualTransformation: VisualTransformation? = null
) {
OutlinedTextField(
value = inputState,
onValueChange = { onValueChange(it) },
label = { Text(text = label) },
placeholder = { Text(text = label) },
singleLine = true,
enabled = !isLoginPending,
modifier = modifier,
keyboardOptions = keyboard?.let { keyboard } ?: KeyboardOptions.Default,
visualTransformation = visualTransformation?.let { visualTransformation } ?: VisualTransformation.None
)
}
And I am using it with view model to decide if login button should be enabled, to login user etc.
LoginViewModel:
#HiltViewModel
class LoginViewModel #Inject constructor(private val repository: LoginRepository): ViewModel() {
private val _userEmail = MutableStateFlow<String>(value = String())
val userEmail: StateFlow<String> = _userEmail.asStateFlow()
private val _userPassword = MutableStateFlow<String>(value = String())
val userPassword: StateFlow<String> = _userPassword.asStateFlow()
private val _isLoginPending = MutableStateFlow<Boolean>(value = false)
val isLoginPending: StateFlow<Boolean> = _isLoginPending.asStateFlow()
private val _loginError = MutableSharedFlow<String?>()
val loginError: SharedFlow<String?> = _loginError.asSharedFlow()
private val _isLoginButtonEnabled = MutableStateFlow<Boolean>(value = false)
val isLoginButtonEnabled: StateFlow<Boolean> = _isLoginButtonEnabled.asStateFlow()
fun getOnboardingStatus(): OnboardingStatus {
return repository.isOnboardingCompleted()
}
fun onEvent(event: LoginEvent) {
when (event) {
is LoginEvent.EnteredEmail -> {
_userEmail.value = event.email
_isLoginButtonEnabled.value = _userEmail.value.isNotEmpty() && _userPassword.value.isNotEmpty()
}
is LoginEvent.EnteredPassword -> {
_userPassword.value = event.password
_isLoginButtonEnabled.value = _userEmail.value.isNotEmpty() && _userPassword.value.isNotEmpty()
}
is LoginEvent.Login -> {
viewModelScope.launch {
_isLoginPending.value = true
_isLoginButtonEnabled.value = false
val result = repository.loginUser(_userEmail.value, _userPassword.value)
when (result) {
is AuthRequestState.Success -> { /* nothing to do */ }
is AuthRequestState.Fail -> {
_loginError.emit(result.msg)
}
}
_isLoginPending.value = false
_isLoginButtonEnabled.value = true
}
}
}
}
}
However the problem is that when I am typing something in TextFields in layout inspector I see that other elements of UI (like Button and ClickableButton) are recomposed:
How to avoid such behavior?
How can I add more elements to the static list in the jetpack compose
#OptIn(ExperimentalFoundationApi::class)
#Composable
fun AddNotesToList(notesList: List<String>) {
val listState = rememberScrollState()
Log.d("TAG", notesList.toString())
LazyColumn() {
items(notesList.size) {
Box(contentAlignment = Alignment.Center,
modifier = Modifier
.padding(start = 15.dp, top = 15.dp, bottom = 1.dp, end = 15.dp)
.fillMaxSize() .horizontalScroll(listState)
.background(Color.White)
.clip(RoundedCornerShape(10.dp)) .padding(15.dp)
.animateItemPlacement(animationSpec = tween(1000))) {
Text(text = notesList[it],
color = Color.Black,
modifier = Modifier.align( Alignment.BottomCenter)
.animateItemPlacement(animationSpec = tween(10000)))
}
}
}
}
this is my addition to the Ui function, this is now I add elements
AddNotesToList(notesList = listOf(
"Drink water",
"Read Books",
"Eat fruits",
"Go for a Walk",
"Drink water",
"Read Books",
"Eat fruits",
"Go for## Heading ## a Walk",
"Go for a Walk",
"Drink water",
"Read Books",
"Eat fruits",
"Go for a Walk"))
now I want to add one more element and I am trying this
function
#Composable
fun AddNewNote(noteDescription: String) {
Log.d("noteDescription", noteDescription)
AddNotesToList(notesList = listOf(noteDescription))
}
Solution:
val _noteList = remember { MutableStateFlow(listOf<String>()) }
val noteList by remember { _noteList }.collectAsState()
// Add note
fun addItem(item: String) {
val newList = ArrayList(noteList)
newList.add(yourItem)
_noteList.value = newList
}
And then you can pass noteList to your LazyColumn
This is how i call my function
AddNewNote { item -> //updating state with added item noteListState = noteListState + listOf(item) }
This is my function
#OptIn(ExperimentalAnimationApi::class)
#Composable
fun AddNewNote(onNewNoteAdded: (String) -> Unit) {
val openDialog = remember { mutableStateOf(true) }
val (visible) = remember { mutableStateOf(true) }
var text by remember { mutableStateOf("") }
AnimatedVisibility(
visible = visible,
enter = slideInVertically(initialOffsetY = { 9000 * it }),
exit = fadeOut()
) {
if (openDialog.value) {
AlertDialog(
onDismissRequest = {
openDialog.value = false
},
title = {
Text(
modifier = Modifier.animateEnterExit(
enter = slideInVertically(
initialOffsetY = { 9000 * it },
),
exit = slideOutVertically()
),
text = "Add Note Description"
)
},
text = {
Column() {
TextField(
value = text,
onValueChange = { text = it }
)
Text("Note description")
}
},
buttons = {
Row(
modifier = Modifier.padding(all = 8.dp),
horizontalArrangement = Arrangement.Center
) {
val addNoteButtonState by remember { mutableStateOf(false) }
if (addNoteButtonState) {
onNewNoteAdded(text)
} else {
Box(contentAlignment = Alignment.Center) {
Button(
modifier = Modifier.fillMaxWidth(),
onClick = {
if (text != "") {
onNewNoteAdded(text)
}
// addNoteButtonState = true
openDialog.value = false
}
) {
Text(
"Add Note To The List",
)
}
}
}
}
},
)
}
}
}
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
) {
}