Jetpack-Compose ModalBottomSheetLayout throws java.lang.IllegalArgumentException with initial state "Hidden" - android

By playing with a ModalBottomSheet in Compose, I get the following issue:
java.lang.IllegalArgumentException: The initial value must have an associated anchor.
My composable function has:
a ModalBottomSheetState and a CoroutinScope,
a ModalBottomSheetLayout with,
a Scaffold as bottom sheet content.
// 1.
val sheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden)
val coroutineScope = rememberCoroutineScope()
// 2.
ModalBottomSheetLayout(
sheetContent = {
/* sheetContent */
},
sheetState = sheetState,
// modifier = Modifier.fillMaxSize() --> it doesn't change the result
) {
// 3.
Scaffold {
/* scaffold content */
}
}
By setting the initial state of the bottom sheet to ModalBottomSheetValue.Expanded, the issue disappears. Note the exception is also thrown for ModalBottomSheetValue.HalfExpanded and without any initial value (the default is Hidden, so it seems logic).
Is there a known workaround or a version of the library where it is working (version of compose I use: 1.0.0 and I tried with 1.0.0-rc2) ?
UPDATE
After some investigation, it seems that the issue is due to a dynamic content in the sheet content. I have there a Column/LazyColumn that recomposes when data are available. By having a fixed content, the issue disappear for any ModalBottomSheetValue.
FIX
With "null" content (understand a content with a height of 0 dp), the composable function has probably not enough information to compose the modal bottom sheet. It is the case with dynamic column content (starting with no content, so height = 0 dp).
To fix this, set a minimal height of 1 dp somewhere in the sheet content hierarchy:
val sheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden)
val coroutineScope = rememberCoroutineScope()
ModalBottomSheetLayout(
sheetContent = {
Box(modifier.defaultMinSize(minHeight = 1.dp)) {
/* sheet content */
}
},
sheetState = sheetState,
// modifier = Modifier.fillMaxSize() --> it doesn't change the result
) {
Scaffold {
/* scaffold content */
}
}

With "null" content (understand a content with a height of 0 dp), the composable function has probably not enough information to compose the modal bottom sheet. It is the case with dynamic column content (starting with no content, so height = 0 dp).
To fix this, set a minimal height of 1 dp somewhere in the sheet content hierarchy:
val sheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden)
val coroutineScope = rememberCoroutineScope()
ModalBottomSheetLayout(
sheetContent = {
Box(modifier.defaultMinSize(minHeight = 1.dp)) {
/* sheet content */
}
},
sheetState = sheetState,
// modifier = Modifier.fillMaxSize() --> it doesn't change the result
) {
Scaffold {
/* scaffold content */
}
}

you can add Spacer in sheetContent to avoid above expecption
sheetContent = {
Spacer(modifier = Modifier.height(1.dp))
Box() {
/* sheet content */
}
}

For me the approved solution didn't work.
In my case it was failing when I had a bottomSheet content with an unspecified height (lazyColumn) and I wanted the bottomSheet to wrap the height of the content by using this line when opening it:
sheetState.animateTo(ModalBottomSheetValue.HalfExpanded)
For me the solution was to wrap the sheetContent with:
Column(Modifier.fillMaxSize()).
I hope this solution can help someone else out, here is the pseudo code:
val sheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden)
val coroutineScope = rememberCoroutineScope()
ModalBottomSheetLayout(
sheetContent = {
Column(modifier.fillMaxSize()) { ------> The solution
/* sheet content */
}
},
sheetState = sheetState,
) {
Scaffold {
/* scaffold content */
}
}

Related

Jetpack Compose, ModalBottomSheetLayout and content height

In my app, I'd like to use a ModalBottomSheetLayout. In my sheet content it, I'd like to use multiple screens :
ModalBottomSheetLayout(
modifier = Modifier,
content = { /* Some content */ },
sheetContent = {
Crossfade(
modifier = Modifier,
targetState = someState
) { state ->
when (state) {
A -> FirstScreen()
B -> SecondScreen()
}
}
}
)
The issue is that if my next screen is smaller than my previous, the animation makes that the bottom sheet is falling down.
I've tried to use Modifier.animateContentSize or Modifier.wrapContent on the Crossfade, or use a AnimatedContent, but the result is always the same
Anyone knows how to resolve that issue ? Thank for your help

Kotlin Compose global footer view

I am trying to show an Ad banner at the bottom which is globally displayed. That means that I want to stick there when I navigate throughout the app.
So far I've tried adding the NavHost and the AdView inside a Column, hoping that the navigation with the linked pages will just take the remaining space and the bottom view will stick there. However the NavHost takes the whole screen and the AdView has zero height. When I navigate the second view, the AdView is still inside the view hierarchy but still with zero height.
I've tried changing all kinds of setup, adding .weight(1f) to the Ad view, changing Column Arrangement, .fillMaxSize(), .fillMaxHeight(), .minHeight(), fixed height, etc.
I might need to look into different layout for achieving this result, but not sure in which direction to search. The Ad works well, I tried displaying without the column and is shown correctly in the center of the screen.
Another variant I've tried is to display it without column, add bottom spacing to the NavHost then position the AdView to the bottom somehow. With this variant I haven't been able to position it to bottom.
Any help would be appreciated!
Column() {
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = NavigationRoutes.Home
) {
composable(NavigationRoutes.Home) { HomeView(navController = navController) }
composable(
NavigationRoutes.Books,
arguments = listOf(navArgument("mode") { type = NavType.IntType })
) { backStackEntry ->
val rawMode = backStackEntry.arguments?.getInt("mode") ?: 0
var mode: ContentMode = if (rawMode == 0) {
ContentMode.READING
} else {
ContentMode.QUIZ
}
BooksView(navController = navController, mode = mode)
}
}
AndroidView(
modifier = Modifier
.fillMaxWidth()
.height(IntrinsicSize.Max)
.weight(1f),
factory = { context ->
AdView(context).apply {
setAdSize(AdSize.BANNER)
adUnitId = AdIdentifiers.GlobalBanner.adID
loadAd(AdRequest.Builder().build())
}
}
)
}
It might be the issue of any child Composable of NavHost having Modifier.fillmaxSize(). You can set a Modifier for NavHost which let's you define how much space it will cover
Column(modifier = Modifier.fillMaxSize()){
NavHost(modifier = Modifier.fillmaxWidth).weight(1f){}
AdView(modifier = Modifier
.fillMaxWidth()
.height(IntrinsicSize.Max)
)
}
You can put AndroidView in Box and give androidView height as bottom padding values in screens.
NavHost(
//
){
//
}
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.BottomCenter
){
AndroidView()
}

How to prevent a jetpack compose scaffold from taking the whole height of the screen when it is shown in a dialog

I am having an android activity which has Base.Theme.AppCompat.Light.Dialog as a theme on tablets and therefore is shown in a dialog.
When I used XML layouts I could set a min height with wrap content for the layout so it would
Be at least x dp high when there was no or very little content.
High as the content when the content was higher than the min height
High as the screen when the content wouldnt fit inside the height of the screen, in that case I could scroll
Using a Jetpack Compose's Scaffold I am struggling recreating that behaviour. The dialog is alsways as high as the screen allows, even when there is no content.
I tried the following modifiers as a parameter to the scaffold:
Modifier.wrapContentHeight() - the dialog is still as high as the screen
Modifier.defaultMinSize(minHeight = 250.dp) - the dialog is still as high as the screen
Modifier.height(height = 250.dp) - now it is smaller but it is fixed to that size, not growing when there is more content
Modifier.requiredHeightIn(min = 250.dp, max = getMyScreenHeightinDp()) - the dialog is still as high as the screen
Since you can reduce the height of the scaffold with the .height(...) modifier I think it should also be possible to recreate a wrap content behaviour. But how?
Using requiredHeightIn does work, as tested in the code below. In this list, the height varies from 150.dp to 400.dp. If you only provide 10 items, it will be 150.dp. As you add more items, it increases in height but only to a maximumo of 400.dp.
However, there is a catch. The navigation drawer slides out from the left side of the screen and not the left side of the dialog. There are ways around that. You could replace the navigation drawer with your own custom drawer. I did this once and it can be done.
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
startActivity(intent)
setContent {
Dialog(
onDismissRequest = { },
properties = DialogProperties(dismissOnClickOutside = false)
) {
val scaffoldState = rememberScaffoldState()
val scope = rememberCoroutineScope()
Scaffold(
modifier = Modifier.requiredHeightIn(min = 150.dp, max = 400.dp),
scaffoldState = scaffoldState,
drawerContent = { Text("Drawer content") },
topBar = {
TopAppBar(
title = { Text("Simple Scaffold Screen") },
navigationIcon = {
IconButton(
onClick = {
scope.launch { scaffoldState.drawerState.open() }
}
) {
Icon(Icons.Filled.Menu, contentDescription = "Localized description")
}
}
)
},
floatingActionButtonPosition = FabPosition.End,
floatingActionButton = {
ExtendedFloatingActionButton(
text = { Text("Inc") },
onClick = { /* fab click handler */ }
)
},
content = { innerPadding ->
LazyColumn(contentPadding = innerPadding) {
items(count = 100) {
Text(it.toString())
}
}
}
)
}
}
}
}

Jetpack Compose Bottomsheet with empty sheet content is always expnaded

I'm trying to achieve modalBottomSheet by BottomSheetScaffold for some custom implementation.
here is my BottomSheetScaffold
BottomSheetScaffold(
sheetPeekHeight = 0.dp,
scaffoldState = bottomSheetState,
sheetBackgroundColor = Color.Transparent,
backgroundColor = Color.Transparent,
sheetElevation = 0.dp,
sheetShape = RoundedCornerShape(topStart = 36.dp, topEnd = 36.dp),
snackbarHost = snackBarHost,
sheetContent = { bottomSheet() }) {
Box(Modifier.fillMaxSize()) {
val coroutineScope = rememberCoroutineScope()
sheetContent()
Scrim(
color = Primary,
alpha = bottomSheetState.currentFraction * 0.5f,
onDismiss = {
coroutineScope.launch { bottomSheetState.bottomSheetState.collapse() }
},
visible = bottomSheetState.bottomSheetState.targetValue != BottomSheetValue.Collapsed && bottomSheetState.bottomSheetState.currentValue != BottomSheetValue.Collapsed
)
}
}
When this scaffold is called by some screen, the sheetContent() will be replaced as screen content. My problem here is when bottomSheet() is empty on that screen and thus there is no height, bottom sheet state think it is expanded while I just not put composable inside bottomSheet() and it just fill based on some condition with no default composable. Because of that the Scrim() function will be visible and when I click on it this exception will throw
java.lang.IllegalArgumentException: The target value must have an associated anchor.
It seems while sheetContent is necessary for BottomSheetScaffold there is no way to deal with empty value because BottomSheetState class that handle's swiping need anchor to get height and empty value cause's unexpected result
This is a bug introduced in latest release of compose 1.2.x. To stop showing bottom sheet at start, I only add sheetContent if data is not null(this was not possible in previous release1.1.x). Now I have another issue. The click event to expanding bottomSheet won't trigger at first try. I always need to click it twice.

Jetpack Compose Bottom Sheet initialization error

In Jetpack compose 1.0.0-beta01, I am calling the BottomSheetScaffold like this:
BottomSheetScaffold(
scaffoldState = bottomSheetScaffoldState,
sheetContent = { Text("") },
sheetShape = Shapes.large,
backgroundColor = AppTheme.colors.uiBackground,
modifier = modifier
) { (content) }
... and getting the following error:
java.lang.IllegalArgumentException: The initial value must have an associated anchor.
Any tips on fixing this?
Don't forget to add the following atribute:
sheetPeekHeight = 0.dp
So your code should be like this:
BottomSheetScaffold(
scaffoldState = bottomSheetScaffoldState,
sheetContent = { Text("") },
sheetShape = Shapes.large,
sheetPeekHeight = 0.dp, // <--- new line
backgroundColor = AppTheme.colors.uiBackground,
modifier = modifier
) { (content) }
When bottomSheetState is expand, sheetContent must have real content to show.
You need check this.
I got the same issue when using ModalBottomSheetLayout, and my compose material version is 1.2.0-rc02
androidx.compose.material:material:1.2.0-rc02
I want to show the bottom modal when one item is selected, and if no item is selected, the modal should be empty.
ModalBottomSheetLayout(
sheetState = modalBottomSheetState,
sheetContent = {
EditProgressContent(book = book)
}
) { ... }
#Composable
fun EditProgressContent(book: Book?) {
if (book == null) return
Text(book.title)
}
When book is null, I got the same crash. There is no peekHeight parameter for ModalBottomSheetLayout, so I have to add a pixel when book is null
#Composable
fun EditProgressContent(book: Book?) {
if (book == null) {
Box(modifier = Modifier.size(1.dp)
return
}
Text(book.title)
}
The code looks silly, hope it can be fixed from Google.
In case you end up on this page because you use the BackdropScaffold, the suggested solution does the trick as well. Simply set the peekHeight.
For example like this:
BackdropScaffold(
appBar = {},
backLayerContent = {},
frontLayerContent = {},
peekHeight = 0.dp
)
Then Preview works like a charm again.
Fun fact though: Don't set it to 56.dp which is the default init value it normally should be initialised with (value of BackdropScaffoldDefaults.PeekHeight). 56.dp results in the anchor rendering problem in my current setup.
(Using compose version '1.1.1')
On new version, this error happens when the initial State is Expanded, where try to open the Modal before launched. Try launcher via ModalBottomSheetState from LaunchedEffect (maybe can need a delay)

Categories

Resources