Compose ModalBottomSheetLayout keyboard problem - android

I have a strange UI glitch for the keyboard when the ModalBottomSheetLayout is in expanded state and the keyboard appears.
#Composable
#OptIn(ExperimentalMaterialApi::class)
fun ModalBottomSheetSample() {
val state = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden, skipHalfExpanded = true)
val scope = rememberCoroutineScope()
ModalBottomSheetLayout(
sheetState = state,
sheetContent = {
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
repeat(20) {
TextField(value = "", onValueChange = {})
}
}
}
) {
Button(onClick = { scope.launch { state.show() } }) {
Text("Click to show sheet")
}
}
}
and here is a video: https://drive.google.com/file/d/1IMU4kGY3hnixK6bVME1iqH0aPDe4-hjr/view?usp=sharing
Anyone any idea how to fix this?

Related

How to Close Keyboard if the ModalBottomSheetLayout closes in Compose

Im working with Jetpack Compose and having a ModalBottomSheetLayout with a TextField. While Closing the bottom sheet, the keyboard it not going off.
Keyboard should hide, when ModalBottomSheetLayout dismisses.
#OptIn(ExperimentalMaterialApi::class)
#Composable
fun ModalBottomSheetSample() {
val state = rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden,)
val scope = rememberCoroutineScope()
BackHandler(state.isVisible) {
scope.launch {state.hide()}
}
ModalBottomSheetLayout(sheetState = state, sheetContent = {
TextField(value = "",
onValueChange = {},
placeholder = { Text(text = "Search") },
modifier = Modifier
.padding(10.dp)
.fillMaxWidth()
)
LazyColumn {items(50) {...}}
}) {
Button(onClick = { scope.launch { state.show() } }) {
Text("Click to show sheet")
}
}
}
have referred this question, but this is not applicable for this scenario. As this is based on Composable.

Android Compose ModalBottomSheetLayout calling hide on rememberModalBottomSheetState object causing overlay hanging with JobCancellationException

I'm using Android Compose ModalBottomSheetLayout, when calling hide() on rememberModalBottomSheetState object it causes an overlay hang periodically.
The exception being thrown is
CancellationException nested kotlinx.coroutines.JobCancellationException: ScopeCoroutine was cancelled; job=ScopeCoroutine{Cancelled}#5ef8661
What is the reason for this being called and/or is there an efficient way to retry hiding the Bottom Sheet Dialog when the exception is thrown?
#Composable
fun ExampleUi(
exampleBotttomSheetState: ExampleBottomSheetState,
coroutineScope: CoroutineScope
) {
val bottomSheetScaffoldState = rememberModalBottomSheetState(
initialValue = ModalBottomSheetValue.Hidden
)
when (exampleBotttomSheetState.value) {
is ExampleBottomSheetState.Hide -> {
coroutineScope.launch {
bottomSheetScaffoldState.hide()
}
}
is ExampleBottomSheetState.Show -> {
coroutineScope.launch {
bottomSheetScaffoldState.show()
}
}
}
ModalBottomSheetLayout(
sheetState = bottomSheetScaffoldState,
sheetContent = {
// Bottom Sheet Content UI
}
) {
// UI content
}
}
It’s best not to be launching coroutines in a composable like this. Recomposition can happen for many reasons and frequently, so that would trigger the when block many times.
I recommend passing in the modalBottomSheetState directly and modifying it at the source instead of having the logic in a Composable.
Example here: https://foso.github.io/Jetpack-Compose-Playground/material/modalbottomsheetlayout/
#Composable
#OptIn(ExperimentalMaterialApi::class)
fun ModalBottomSheetSample() {
val state = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden)
val scope = rememberCoroutineScope()
ModalBottomSheetLayout(
sheetState = state,
sheetContent = {
LazyColumn {
items(50) {
ListItem(
text = { Text("Item $it") },
icon = {
Icon(
Icons.Default.Favorite,
contentDescription = "Localized description"
)
}
)
}
}
}
) {
Column(
modifier = Modifier.fillMaxSize().padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text("Rest of the UI")
Spacer(Modifier.height(20.dp))
Button(onClick = { scope.launch { state.show() } }) {
Text("Click to show sheet")
}
}
}
}

Expand navigation material ModalBottomSheetLayout to max or custom height

I'm trying to implement https://google.github.io/accompanist/navigation-material/ and i want to expand modelsheet to custom height or more than half screen but i don't have any idea how to achieve it
Currently ModelBottomSheet
Wish to expand like this
Instead of using the show method of the ModalBottomSheetState, You can use the animateTo method. The show method will default to a half screen size modal. The animateTo(ModalBottomSheetValue.Expanded) will expand to the full size of the content. In the example i've used a BoxWithConstrains to get the screen size and set the size of the modal content to 80%.
I hope this helps!
#Composable
#Preview
fun BottomSheetDemo() {
val modalBottomSheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden)
BoxWithConstraints {
val sheetHeight = this.constraints.maxHeight * 0.8f
val coroutineScope = rememberCoroutineScope()
Column {
Button(onClick = {
coroutineScope.launch { modalBottomSheetState.animateTo(ModalBottomSheetValue.Expanded) }
}) {
Text(text = "Expand")
}
Button(onClick = {
coroutineScope.launch { modalBottomSheetState.animateTo(ModalBottomSheetValue.Hidden) }
}) {
Text(text = "Collapse")
}
}
ModalBottomSheetLayout(
sheetBackgroundColor = Color.Red,
sheetState = modalBottomSheetState,
sheetContent = {
Box(modifier = Modifier.height(with(LocalDensity.current) { sheetHeight.toDp() })) {
Text(text = "This is some content")
}
}
) {}
}
}
EDIT:
If you want to use the material navigation, you will need a custom extension function. The difference in this function with the original is the skipHalfExpanded parameter. This on will make it possible to create bottom sheets larger then half screen.
#Composable
fun rememberBottomSheetNavigator(
animationSpec: AnimationSpec<Float> = SwipeableDefaults.AnimationSpec
): BottomSheetNavigator {
val sheetState = rememberModalBottomSheetState(
initialValue = ModalBottomSheetValue.Hidden,
animationSpec = animationSpec,
skipHalfExpanded = true
)
return remember(sheetState) {
BottomSheetNavigator(sheetState = sheetState)
}
}
The implementation itself will be something like this:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val bottomSheetNavigator = rememberBottomSheetNavigator()
val navController = rememberNavController(bottomSheetNavigator)
ModalBottomSheetLayout(bottomSheetNavigator) {
NavHost(navController, "home") {
composable(route = "home") {
Home(navController)
}
bottomSheet(route = "sheet") {
ModalDemo()
}
}
}
}
}
}
#Composable
fun Home(navController: NavController) {
val coroutineScope = rememberCoroutineScope()
Column {
Button(onClick = {
coroutineScope.launch { navController.navigate("sheet") }
}) {
Text(text = "Expand")
}
}
}
#Composable
fun ModalDemo() {
Column(Modifier.fillMaxWidth().height(700.dp).background(Color.Red), horizontalAlignment = Alignment.CenterHorizontally) {
Text(text = "This is some content")
}
}

Unable to show ModalBottomSheetLayout expanded

I'm having some issues with ModalBottomSheetLayout trying to make it fully expanded. I tried some answers from other posts (like Make ModalBottomSheetLayout always Expanded) with no result.
This is the Composable function I've created:
#OptIn(ExperimentalMaterialApi::class)
#Composable
fun ExpandedSheetDialog(
sheetContent: #Composable (() -> Unit),
screenContent: #Composable (() -> Unit),
modalState: ModalBottomSheetState
) {
ModalBottomSheetLayout(
sheetState = modalState,
sheetShape = RoundedCornerShape(topEnd = 10.dp, topStart = 10.dp),
sheetContent = { sheetContent.invoke() },
content = { screenContent.invoke() }
)
}
And this is the Preview:
#OptIn(ExperimentalMaterialApi::class)
#Preview(showBackground = true)
#Composable
fun ExpandedSheetDialogPreview() {
val scope: CoroutineScope = rememberCoroutineScope()
val modalSheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Expanded)
ExpandedSheetDialog(
sheetContent = {
Button(
onClick = {
scope.launch {
modalSheetState.animateTo(ModalBottomSheetValue.Hidden)
}
},
text = "Hide"
)
},
screenContent = {
Column(
modifier = Modifier.padding(16.dp).fillMaxHeight(),
horizontalAlignment = Alignment.CenterHorizontally
) {
Button(
onClick = {
scope.launch {
modalSheetState.animateTo(ModalBottomSheetValue.Expanded)
}
},
text = "Show Extended"
)
}
},
modalState = modalSheetState
)
}
I tried in several emulators, all of them with the same result, the "BottomSheetDialog" appears but is not fully expanded. What am I doing wrong?

Compose AnimatedVisibility escapes container

I am using Jetpack Compose (version 1.1.1) and I am trying to show an snackbar-style alert coming from the top of the screen using AnimatedVisibility. However, when I do so the alert escapes its container and overlaps the content above it (in this case, the action bar). Also, the content below it (the button) waits until the animation is finished before adjusting its position. This results in a jumpy effect. I would like it to slide down in sync with the alert.
#Composable
fun HomeScreen() {
val showAlert = remember { mutableStateOf(false) }
val scope = rememberCoroutineScope()
Column(Modifier.fillMaxHeight()) {
ActionBar("Home Screen")
Column {
Alert(visible = showAlert.value)
Button(modifier = Modifier.padding(16.dp), onClick = {
scope.launch {
showAlert.value = true
delay(1000)
showAlert.value = false
}
}) {
Text("Show Alert")
}
}
}
}
#Composable
private fun ActionBar(title: String) {
TopAppBar(backgroundColor = Color.Blue, contentColor = Color.White) {
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(start = 8.dp, end = 8.dp)) {
Text(title)
}
}
}
#Composable
fun Alert(visible: Boolean) {
AnimatedVisibility(visible = visible, enter = slideInVertically(), exit = slideOutVertically()) {
Column(Modifier.fillMaxWidth().background(Color.Red)) {
Text("An error has occurred", Modifier.padding(16.dp), style = TextStyle(Color.White))
}
}
}
#Preview
#Composable
fun HomeScreenPreview() {
Box(Modifier.fillMaxSize().background(Color.White)) {
HomeScreen()
}
}
For the overlap issue, you can use the modifier: Modifier.clipToBounds()
For the 'jumpy effect' you can put the alert text and the page content in a single column and animate the offset.
enum class AlertState {
Collapsed,
Expanded
}
#Preview
#Composable
fun HomeScreen() {
val scope = rememberCoroutineScope()
val alertHeight = 40.dp
var currentState by remember { mutableStateOf(AlertState.Collapsed) }
val transition = updateTransition(currentState, label = "")
val alertOffset by transition.animateDp(label = "") {
when (it) {
AlertState.Collapsed -> -alertHeight
AlertState.Expanded -> 0.dp
}
}
Column(Modifier.fillMaxHeight()) {
TopAppBar(backgroundColor = Color.Blue, contentColor = Color.White) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(start = 8.dp, end = 8.dp)
) {
Text("Home Screen")
}
}
Column(
modifier = Modifier.clipToBounds().offset(y = alertOffset)
) {
Row(
Modifier.height(alertHeight).fillMaxWidth().background(Color.Red).padding(start = 16.dp)
) {
Text(
"An error has occurred", style = TextStyle(Color.White),
modifier = Modifier.align(Alignment.CenterVertically)
)
}
Button(modifier = Modifier.padding(16.dp), onClick = {
scope.launch {
currentState = AlertState.Expanded
delay(1000)
currentState = AlertState.Collapsed
}
}) {
Text("Show Alert")
}
}
}
}
If your alert height is not fix, you can get it using the modifier Modifier.onGloballyPositioned

Categories

Resources