If my keyboard is opened, then on clicking of button if i try to hide my keyboard and change sheetState to show() then my keyboard hides but my sheet is not visible.
Mimicing opening of sheetState on result from api call.
Note - Sheet is shown for first time only
setContent {
val sheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden)
val key = LocalSoftwareKeyboardController.current
ModalBottomSheetLayout(
sheetState = sheetState,
sheetContent = {
repeat(20) {
Text(text = "$it")
}
}
) {
Column {
var otpResponse by remember { mutableStateOf<Boolean?>(null) }
if (otpResponse == false) {
LaunchedEffect(key1 = Unit, block = {
delay(180)
otpResponse = true
})
}
if (otpResponse == true) {
LaunchedEffect(key1 = Unit, block = {
sheetState.show()
})
}
Column {
var string by remember { mutableStateOf("") }
TextField(value = string, onValueChange = { string = it })
Button(onClick = {
key?.hide()
otpResponse = false
}) {
Text(text = "TEST")
}
}
}
}
}
I have modified your code have look which is working without delay
#OptIn(ExperimentalMaterialApi::class, ExperimentalComposeUiApi::class)
#Preview(showBackground = true)
#Composable
fun DefaultPreview(){
val sheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden)
val key = LocalSoftwareKeyboardController.current
val scope = rememberCoroutineScope()
ModalBottomSheetLayout(
sheetState = sheetState,
sheetContent = {
repeat(20) {
Text(text = "$it")
}
}
) {
Column {
Column {
var string by remember { mutableStateOf("") }
TextField(value = string, onValueChange = { string = it })
Button(onClick = {
key?.hide()
scope.launch{
sheetState.show()
}
}) {
Text(text = "TEST")
}
}
}
}
}
thanks for my colleague to fix this
Related
I am having an issue, where after a Snackbar appears the FAB won't perform navigation for the period of the Snackbar message. If I click it multiple times when the Snackbar is displayed it will stack and open multiple instances of one screen (only after the Snackbar is gone). How do I cancel showing the Snackbar on click of the FAB and preserve the functionality of the FAB at all times?
TasksList.kt:
#OptIn(ExperimentalMaterial3Api::class)
#Composable
fun TasksListScreen(
modifier: Modifier = Modifier,
onNavigate: (UiEvent.Navigate) -> Unit,
viewModel: TaskListViewModel = hiltViewModel()
) {
val tasks = viewModel.tasks.collectAsState(initial = emptyList())
val snackbarHostState = remember { SnackbarHostState() }
LaunchedEffect(key1 = true) {
viewModel.uiEvent.collect { event ->
when (event) {
is UiEvent.ShowSnackbar -> {
val result = snackbarHostState.showSnackbar(
message = event.message,
actionLabel = event.action,
duration = SnackbarDuration.Long,
)
if (result == SnackbarResult.ActionPerformed) {
viewModel.onEvent(TaskListEvent.OnUndoDeleteTask)
}
}
is UiEvent.Navigate -> onNavigate(event)
else -> Unit
}
}
}
Scaffold(
snackbarHost = {
SnackbarHost(snackbarHostState) { data ->
Snackbar(
shape = RoundedShapes.medium,
actionColor = MaterialTheme.colorScheme.primary,
contentColor = MaterialTheme.colorScheme.background,
snackbarData = data
)
}
},
floatingActionButton = {
FloatingActionButton(
shape = RoundedShapes.medium,
onClick = { viewModel.onEvent(TaskListEvent.OnAddTask) },
containerColor = MaterialTheme.colorScheme.primary,
contentColor = MaterialTheme.colorScheme.background
) {
Icon(
imageVector = Icons.Default.Add,
contentDescription = stringResource(R.string.fab_cd)
)
}
},
topBar = {
TopAppBar(
title = { Text(stringResource(R.string.app_name)) },
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.primary,
titleContentColor = MaterialTheme.colorScheme.background
)
)
},
) { padding ->
LazyColumn(
state = rememberLazyListState(),
verticalArrangement = spacedBy(12.dp),
contentPadding = PaddingValues(vertical = 16.dp),
modifier = modifier
.fillMaxSize()
.padding(padding)
.padding(horizontal = 16.dp),
) {
items(items = tasks.value, key = { task -> task.hashCode() }) { task ->
val currentTask by rememberUpdatedState(newValue = task)
val dismissState = rememberDismissState(confirmValueChange = {
if (it == DismissValue.DismissedToStart) {
viewModel.onEvent(TaskListEvent.OnDeleteTask(currentTask))
}
true
})
SwipeToDismiss(state = dismissState,
directions = setOf(DismissDirection.EndToStart),
background = { },
dismissContent = {
TaskItem(
task = task, modifier = modifier
)
})
}
}
}
}
TaskListViewModel:
#HiltViewModel
class TaskListViewModel #Inject constructor(private val repository: TaskRepositoryImplementation) :
ViewModel() {
val tasks = repository.getTasks()
private val _uiEvent = Channel<UiEvent>()
val uiEvent = _uiEvent.receiveAsFlow()
private var deletedTask: Task? = null
fun onEvent(event: TaskListEvent) {
when (event) {
is TaskListEvent.OnAddTask -> {
sendUiEvent(UiEvent.Navigate(Routes.ADD_EDIT_TASK))
}
is TaskListEvent.OnEditClick -> {
sendUiEvent(UiEvent.Navigate(Routes.ADD_EDIT_TASK + "?taskId=${event.task.id}"))
}
is TaskListEvent.OnDeleteTask -> {
val context = TaskApp.instance?.context
viewModelScope.launch(Dispatchers.IO) {
deletedTask = event.task
repository.deleteTask(event.task)
if (context != null) {
sendUiEvent(
UiEvent.ShowSnackbar(
message = context.getString(R.string.snackbar_deleted),
action = context.getString(R.string.snackbar_action)
)
)
}
}
}
is TaskListEvent.OnUndoDeleteTask -> {
deletedTask?.let { task ->
viewModelScope.launch {
repository.addTask(task)
}
}
}
}
}
private fun sendUiEvent(event: UiEvent) {
viewModelScope.launch(Dispatchers.IO) {
_uiEvent.send(event)
}
}
}
I am facing an issue while showing the bottom sheet dialog in jetpack compose on each row item click. When I click on an item the app crashes.
Here is my LazyColumn code:
//Setup Recyclerview
#OptIn(ExperimentalMaterialApi::class)
#SuppressLint("CoroutineCreationDuringComposition")
#Composable
fun SetRecyclerview(viewModel: UserViewModel, paddingValues: PaddingValues) {
val isLoading = viewModel.isLoading.value
var showSheet by remember { mutableStateOf(false) }
var sheetUser: User? by remember { mutableStateOf(null) }
//Creating a variable for the StateFlow variable
val userList = viewModel.userData.collectAsState().value
if (showSheet) {
ShowBottomSheetDialog(user = sheetUser!!)
sheetScope.launch {
sheetState.show()
}
}
if (isLoading) {
ProgressBarComponent()
} else {
LazyColumn(
modifier = Modifier.padding(0.dp, 5.dp, 0.dp, 5.dp)
) {
userList.forEach { user ->
items(user.results.size) {
EachRow(user = user.results[it], onClick = {
showSheet = true
sheetUser = user.results[it]
})
}
}
}
}
}
Here is my ShowBottomSheetDialog function:
//ShowBottomSheetDialog
#SuppressLint("CoroutineCreationDuringComposition")
#OptIn(ExperimentalMaterialApi::class)
#Composable
fun ShowBottomSheetDialog(user: User) {
sheetState = rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden)
sheetScope = rememberCoroutineScope()
ModalBottomSheetLayout(
sheetState = sheetState,
sheetContent = { BottomSheetItem(user = user) },
sheetBackgroundColor = Color.White,
sheetShape = RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp),
) {}
}
Here is BottomSheetItem function:
//Bottom Sheet Item
#Composable
fun BottomSheetItem(user: User) {
Log.e("TAG", "BottomSheetItem: " + user.email)
}
I am trying to save my edit text value after changing from one activity to another
ex: just like in a register pager (first you have to fill all data in pages and in the end submit.
I am having trouble here is if i want to edit my first page data so i moved from 2nd page to 1st page but my 1st page doesn't show any data
I tried using save state and restore state of navigation but it wont help
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent { ScreenNavigation() }
}
}
#Composable
fun Page1(navController: NavHostController) {
var test = remember { mutableStateOf("") }
Column() {
TextField(value = test.value, onValueChange = { test.value = it })
Button(
onClick = {
navController.navigate(ScreenRoute.Page2.name) {
popUpTo(ScreenRoute.Page2.name) { saveState = true }
launchSingleTop = true
restoreState = true
}
}
) {}
Text(text = "Page1")
TextField(
value = test.value,
onValueChange = { test.value = it },
)
}
}
#Composable
fun Page2(navController: NavHostController) {
var test = remember { mutableStateOf("") }
Column() {
TextField(value = test.value, onValueChange = { test.value = it })
Button(
onClick = {
navController.navigate(ScreenRoute.Page1.name) {
popUpTo(ScreenRoute.Page1.name) { saveState = true }
launchSingleTop = true
restoreState = true
}
}
) {}
Text(text = "Page2")
}
}
// code for navigation
#Composable
fun ScreenNavigation() {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = ScreenRoute.Page1.name) {
composable(ScreenRoute.Page1.name) { Page1(navController) }
composable(ScreenRoute.Page2.name) { Page2(navController) }
}
}
// code for screen route
enum class ScreenRoute {
Page1,
Page2,
}
Have both variables outside the functions and above class like this:
var test1 = mutableStateOf("")
var test2 = mutableStateOf("")
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent { ScreenNavigation() }
}
}
#Composable
fun Page1(navController: NavHostController) {
Column() {
TextField(value = test1.value, onValueChange = { test1.value = it })
Button(
onClick = {
navController.navigate(ScreenRoute.Page2.name) {
popUpTo(ScreenRoute.Page2.name) { saveState = true }
launchSingleTop = true
restoreState = true
}
}
) {}
Text(text = "Page1")
TextField(
value = test1.value,
onValueChange = { test1.value = it },
)
}
}
#Composable
fun Page2(navController: NavHostController) {
Column() {
TextField(value = test2.value, onValueChange = { test2.value = it })
Button(
onClick = {
navController.navigate(ScreenRoute.Page1.name) {
popUpTo(ScreenRoute.Page1.name) { saveState = true }
launchSingleTop = true
restoreState = true
}
}
) {}
Text(text = "Page2")
}
}
// code for navigation
#Composable
fun ScreenNavigation() {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = ScreenRoute.Page1.name) {
composable(ScreenRoute.Page1.name) { Page1(navController) }
composable(ScreenRoute.Page2.name) { Page2(navController) }
}
}
// code for screen route
enum class ScreenRoute {
Page1,
Page2,
}
Have both variables outside the functions
Use rememberSaveable
remembersaveable save the state of value
val numberOfFamilyMember = rememberSaveable {
mutableStateOf("")
}
Try using rememberSaveable like this
#Composable
fun Page1(navController: NavHostController) {
var test by rememberSaveable { mutableStateOf("") }
Column() {
TextField(value = test.value, onValueChange = { test.value = it })
Button(
onClick = {
navController.navigate(ScreenRoute.Page2.name) {
popUpTo(ScreenRoute.Page2.name) { saveState = true }
launchSingleTop = true
restoreState = true
}
}
) {}
Text(text = "Page1")
TextField(
value = test.value,
onValueChange = { test.value = it },
)
}
}
Using variables outside Composable seems to be a side effect.
rememberSaveable should survive the activity change according to docs
I tried to show a timestamp (or any strings that can be updated in general) inside a sheetContent of the ModalBottomSheetLayout that is shown by clicking on a floating action button. However, that timestamp is only updated once: the first time that the sheet is shown. If I close the sheet (through onSave in the code) and open it again, the timestamp stays the same instead of showing the newest timestamp. I think I maybe missing "remember" or "mutableState" somehow but I am not able to get it to work.
class MainActivity : ComponentActivity() {
#ExperimentalMaterialApi
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
AskQuestionTheme {
val modalBottomSheetState =
rememberModalBottomSheetState(ModalBottomSheetValue.Hidden)
val scope = rememberCoroutineScope()
Scaffold(
floatingActionButton = {
FloatingActionButton(
onClick = { scope.launch { modalBottomSheetState.show() } }) {
Icon(
Icons.Rounded.Add,
contentDescription = "add"
)
}
}) {
ModalBottomSheetLayout(
sheetState = modalBottomSheetState,
sheetContent = {
var t = Date().toString()
MySheet(
str = t,
onSave = {
scope.launch {
modalBottomSheetState.hide()
}
},
onCancel = { scope.launch { modalBottomSheetState.hide() } })
}
) {
Text("hello")
}
}
}
}
}
}
#Composable
fun MySheet(str: String, onSave: () -> Unit, onCancel: () -> Unit) {
Column {
Box(modifier = Modifier.fillMaxWidth()) {
Icon(
Icons.Rounded.Close,
contentDescription = "edit",
modifier = Modifier.align(Alignment.CenterStart).clickable { onCancel() })
Button(onClick = onSave, modifier = Modifier.align(Alignment.CenterEnd)) {
Text("save")
}
}
Text(str)
Spacer(Modifier.height(100.dp))
}
}
There are two ways for me. Up to you which select)
First variant. Added a new property var t by remember { mutableStateOf(Date().toString()) } and change its before showing ModalBottomSheetLayout. It is an update code
t = Date().toString().
class MainActivity : ComponentActivity() {
#ExperimentalMaterialApi
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
AskQuestionTheme {
val modalBottomSheetState =
rememberModalBottomSheetState(ModalBottomSheetValue.Hidden)
val scope = rememberCoroutineScope()
var t by remember { mutableStateOf(Date().toString()) }
Scaffold(
floatingActionButton = {
FloatingActionButton(
onClick = {
scope.launch {
t = Date().toString()
modalBottomSheetState.show()
}
}) {
Icon(
Icons.Rounded.Add,
contentDescription = "add"
)
}
}) {
ModalBottomSheetLayout(
sheetState = modalBottomSheetState,
sheetContent = {
MySheet(
str = t,
onSave = {
scope.launch {
modalBottomSheetState.hide()
}
},
onCancel = { scope.launch { modalBottomSheetState.hide() } })
}
) {
Text("hello")
}
}
}
}
}
}
Second variant. Now you can see LaunchedEffect. Code inside this block is triggered when this value modalBottomSheetState.direction changes. Inside this block we update a date.
class MainActivity : ComponentActivity() {
#ExperimentalMaterialApi
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
AskQuestionTheme {
val modalBottomSheetState =
rememberModalBottomSheetState(ModalBottomSheetValue.Hidden)
val scope = rememberCoroutineScope()
Scaffold(
floatingActionButton = {
FloatingActionButton(
onClick = { scope.launch { modalBottomSheetState.show() } }) {
Icon(
Icons.Rounded.Add,
contentDescription = "add"
)
}
}) {
ModalBottomSheetLayout(
sheetState = modalBottomSheetState,
sheetContent = {
var t by remember { mutableStateOf(Date().toString()) }
LaunchedEffect(modalBottomSheetState.direction) {
if (modalBottomSheetState.direction == -1f) {
t = Date().toString()
}
}
MySheet(
str = t,
onSave = {
scope.launch {
modalBottomSheetState.hide()
}
},
onCancel = { scope.launch { modalBottomSheetState.hide() } })
}
) {
Text("hello")
}
}
}
}
}
}
Simple code below for showing the Compose Snackbar
This code correctly shows the Snackbar, when onClick event occurs.
val scaffoldState = rememberScaffoldState() // this contains the `SnackbarHostState`
val coroutineScope = rememberCoroutineScope()
Scaffold(
modifier = Modifier,
scaffoldState = scaffoldState // attaching `scaffoldState` to the `Scaffold`
) {
Button(
onClick = {
coroutineScope.launch { // using the `coroutineScope` to `launch` showing the snackbar
// taking the `snackbarHostState` from the attached `scaffoldState`
val snackbarResult = scaffoldState.snackbarHostState.showSnackbar(
message = "This is your message",
actionLabel = "Do something."
)
when (snackbarResult) {
SnackbarResult.Dismissed -> Log.d("SnackbarDemo", "Dismissed")
SnackbarResult.ActionPerformed -> Log.d("SnackbarDemo", "Snackbar's button clicked")
}
}
}
) {
Text(text = "A button that shows a Snackbar")
}
}
How to dismiss snackbar on right/left-swipe?
The SnackbarHost has no such functionality. But you can extend it with the nackbarHost argument.
Also if you want the snackbar to disappear only with a swipe, you probably need to set the duration to Indefinite:
Scaffold(
modifier = Modifier,
scaffoldState = scaffoldState,
snackbarHost = { SwipeableSnackbarHost(it) } // modification 1
) {
Button(
onClick = {
coroutineScope.launch {
val snackbarResult = scaffoldState.snackbarHostState.showSnackbar(
message = "This is your message",
actionLabel = "Do something.",
duration = SnackbarDuration.Indefinite, // modification 2
)
when (snackbarResult) {
SnackbarResult.Dismissed -> Log.d("SnackbarDemo", "Dismissed")
SnackbarResult.ActionPerformed -> Log.d(
"SnackbarDemo",
"Snackbar's button clicked"
)
}
}
}
) {
Text(text = "A button that shows a Snackbar")
}
}
SwipeableSnackbarHost inspired by this answer
enum class SwipeDirection {
Left,
Initial,
Right,
}
#Composable
fun SwipeableSnackbarHost(hostState: SnackbarHostState) {
if (hostState.currentSnackbarData == null) { return }
var size by remember { mutableStateOf(Size.Zero) }
val swipeableState = rememberSwipeableState(SwipeDirection.Initial)
val width = remember(size) {
if (size.width == 0f) {
1f
} else {
size.width
}
}
if (swipeableState.isAnimationRunning) {
DisposableEffect(Unit) {
onDispose {
when (swipeableState.currentValue) {
SwipeDirection.Right,
SwipeDirection.Left -> {
hostState.currentSnackbarData?.dismiss()
}
else -> {
return#onDispose
}
}
}
}
}
val offset = with(LocalDensity.current) {
swipeableState.offset.value.toDp()
}
SnackbarHost(
hostState,
snackbar = { snackbarData ->
Snackbar(
snackbarData,
modifier = Modifier.offset(x = offset)
)
},
modifier = Modifier
.onSizeChanged { size = Size(it.width.toFloat(), it.height.toFloat()) }
.swipeable(
state = swipeableState,
anchors = mapOf(
-width to SwipeDirection.Left,
0f to SwipeDirection.Initial,
width to SwipeDirection.Right,
),
thresholds = { _, _ -> FractionalThreshold(0.3f) },
orientation = Orientation.Horizontal
)
)
}