How to properly use Jetpack Compose inside BottomSheetDialogFragment? - android-jetpack-compose

For example, I have MyBottomSheetDialogFragment with Compose LazyColumn code in the application:
class MyBottomSheetDialogFragment : BottomSheetDialogFragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return ComposeView(requireContext()).apply {
setContent {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text("Header", color = Color.Black)
LazyColumn(
Modifier
.weight(1f)
.fillMaxWidth()) {
items(100) {
Text("Item $it", Modifier.fillMaxWidth(), Color.Black)
}
}
}
}
}
}
}
And show it using this code:
MyBottomSheetDialogFragment().show(activity.supportFragmentManager, null)
That's what we have:
MyBottomSheetDialogFragment screen image.jpg
Now if to scroll LazyColumn list DOWN then everything works as it should, but if to scroll LazyColumn list UP then Bottom Sheet Dialog scrolls instead of LazyColumn list.
How to properly implement LazyColumn inside BottomSheetDialogFragment?
When we used the XML RecyclerView list, to fix this issue we had to wrap the RecyclerView list with NestedScrollView like described here, but how to fix it with Jetpack Compose?

You should use a BottomSheetScaffold and set the sheetContent with your bottom sheet content. Don't use the BottomSheetDialogFragment.
Here's an example of basic structure of a BottomSheetScaffold:
val scaffoldState = rememberBottomSheetScaffoldState(
bottomSheetState = rememberBottomSheetState(BottomSheetValue.Expanded)
)
BottomSheetScaffold(
modifier = Modifier.navigationBarsPadding(),
scaffoldState = scaffoldState,
topBar = {
// Your topBar
},
sheetShape = BottomSheetShape,
sheetPeekHeight = MaterialTheme.spacing.large,
sheetContent = {
// Your sheet content
}
) { innerPadding ->
// You content
}

You can use the new rememberNestedScrollInteropConnection() on compose 1.2.0 which will allow compose to interrupt View's scrolling and enable nested scrolling.
In your case it will be
setContent {
Column(modifier = Modifier.nestedScroll(rememberNestedScrollInteropConnection()), horizontalAlignment = Alignment.CenterHorizontally) {
Text("Header", color = Color.Black)
LazyColumn(
Modifier
.weight(1f)
.fillMaxWidth()) {
items(100) {
Text("Item $it", Modifier.fillMaxWidth(), Color.Black)
}
}
}
}

Related

Spoiler component in Compose

Is there a something like a spoiler component in Compose (that doesn't look too much Android-ish)? Or can it be done fast using LazyVerticalGrid or something else? I'm looking for a box that opens and clothes by clicking on its header.
What you look for is AnimatedVisibility Composable.
var visible by remember {
mutableStateOf(true)
}
Column(modifier = Modifier.fillMaxSize()) {
Text("Spoiler", modifier = Modifier
.fillMaxWidth()
.clickable {
visible = !visible
}
)
AnimatedVisibility(visible = visible) {
Column {
Text("Lorem ipsum...Rest of the text")
}
}
}
https://developer.android.com/jetpack/compose/animation#animatedvisibility

Material 3 Top Bar container color not change on scroll

The top app bar container color not change on scroll, the color keep the same. Working with XML views it works fine, but compose doesn't.
If is a bug or the featured was not ported for compose yet, but if ins't a bug, i'm miss something in the code?
I'm using version 1.0.1 of the material 3 library.
Sample code:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
WindowCompat.setDecorFitsSystemWindows(window, false)
setContent {
val uiController = rememberSystemUiController()
uiController.setStatusBarColor(Color.Transparent)
MyApplication5Theme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
Scaffold(
topBar = {
TopAppBar(
title = { Text("k") },
modifier = Modifier.statusBarsPadding(),
)
}
) {
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(it)
) {
items((1..100).toList()) {
Text(it.toString(), modifier = Modifier.padding(8.dp))
}
}
}
}
}
}
}
}
I tried create a simple layout with a top app bar and a lazy column, i expected top app bar container color changes on scroll of the lazy column, but the color keep the same.
The TopAppBar composable has a scrollBehavior argument, which is null be default. If you leave it null, it will not react to scroll events, you have to connect it with your scrollable component. You can do that like this:
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
Scaffold(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = { TopAppBar(scrollBehavior = scrollBehavior, ...) },
) { LazyColumn() {} }

swipe to dismiss in lazy list - accidental horizontal scrolling during vertical scrolling in jetpack compose

I’m trying to add swipe to dismiss to a LazyList.
The default implementation of SwipeToDismiss will lead to accidental horizontal scrolling while trying to scroll vertically leading to accidental swipe to dismiss events.
In vanilla android view world i'd set a touch listener and check against x > y touch event distance before starting swipe to dismiss. However in Jetpack Compose I don't know how.
I tried pointerInput modifier, however it disabled vertical scrolling completely.
I’m aware of dismissThresholds but that does only handle invoking the dismissState change.
#Composable
fun MyList(modifier: Modifier = Modifier, list: MutableState<List<String>>) {
val scrollState: LazyListState = rememberLazyListState()
LazyColumn(
modifier = modifier
.fillMaxWidth()
.background(MaterialTheme.colors.surface),
state = scrollState,
) {
items(
items = list.value,
key = { it },
contentType = { 0 }
) { item ->
MySwipeToDismiss(modifier = modifier, onSwipeLeft = {}, onSwipeRight = {}) {
Text(text = item)
}
}
}
}
}
#OptIn(ExperimentalMaterialApi::class)
#Composable
fun MySwipeToDismiss(modifier: Modifier = Modifier, onSwipeLeft: () -> Unit, onSwipeRight: () -> Unit, content: #Composable () -> Unit) {
val dismissState = rememberDismissState {
when (it) {
DismissValue.Default -> return#rememberDismissState false
DismissValue.DismissedToEnd -> onSwipeRight()
DismissValue.DismissedToStart -> onSwipeLeft()
}
true
}
SwipeToDismiss(
modifier = modifier,
state = dismissState,
dismissThresholds = { FractionalThreshold(0.7f) },
background = {
Box(
modifier = Modifier
.fillMaxSize()
.background(Color.Cyan)
)
}
) {
content()
}
}
Any suggestion is highly appreciated.

Show keyboard over Scaffold's bottomBar in Jetpack Compose and apply proper inset paddings

I'm using Scaffold for my main screen with a fixed bottomBar that is visible in every screen of the app, and I'm applying the innerPadding of the Scaffold to its content.
I want the keyboard to appear over the bottomBar, and for that I'm applying the imePadding() only to the Scaffold's content.
However, when the keyboard is opened, both the Scaffold's innerPading and imePadding() are applied to the contents padding.
I've tried to go through the Accompanist Insets migration, but no lucky.
Is there anyway that I can prevent it and apply only one or the other?
Here is a piece of my code:
Scaffold(
topBar = { },
bottomBar = { },
modifier = Modifier
.systemBarsPadding()
) { innerPadding ->
Content(
modifier = Modifier
.padding(innerPadding)
.imePadding()
)
}
And this is the result:
With the now, deprecated, Accompanist Insets, I was using the following solution:
val isImeVisible = LocalWindowInsets.current.ime.isVisible
val contentPadding = remember(isImeVisible) {
if (isImeVisible) PaddingValues(top = innerPadding.calculateTopPadding()) else innerPadding
}
According to Accompanist Insets migration, LocalWindowInsets.current.ime should be replaced with WindowInsets.ime.
It doesn't have isVisible for now, until this bug is fixed. Here's how I've re-created it for now:
val WindowInsets.Companion.isImeVisible: Boolean
#Composable
get() {
val density = LocalDensity.current
val ime = this.ime
return remember {
derivedStateOf {
ime.getBottom(density) > 0
}
}.value
}
Usage:
val isImeVisible = WindowInsets.isImeVisible
This should work with your old remember(isImeVisible) code.
Another solution would be to set BringIntoViewRequester to your content inside Scaffold. Then when textField is focused, you could call bringIntoViewRequester.bringIntoView(). This way you wouldn't need to set any paddings.
val bringIntoViewRequester = remember { BringIntoViewRequester() }
Column(
modifier = Modifier.bringIntoViewRequester(bringIntoViewRequester)
) {
TextField(
value = "",
onValueChange = {},
modifier = Modifier
.onFocusEvent {
if (it.isFocused) {
coroutineScope.launch {
delay(350)
bringIntoViewRequester.bringIntoView()
}
}
}
)
}
Try using something like this (WARNING: consumedWindowInsets is Experimental, but it's working):
Scaffold(
topBar = { },
bottomBar = { },
modifier = Modifier
.systemBarsPadding()
) { innerPadding ->
Content(
modifier = Modifier
.consumedWindowInsets(innerPadding)
.padding(innerPadding)
.imePadding()
)
}

android compose lazy column separate by weight

Is it possible to do weights in Jetpack Compose with lazy column?
I'd like to set it menu item is weighted as 1/n (n = number of menus) of a layout, and the other takes up the remaining 1/n, also.
I want to list it at the same height as the number of menus.
MenuList
#Composable
fun MenuList(
loading: Boolean,
menus: List<Menu>,
onNavigateToMenuDetailScreen: (String) -> Unit
) {
Box(modifier = Modifier
.background(color = MaterialTheme.colors.surface)
.fillMaxSize()) {
Column(modifier = Modifier.fillMaxSize()) {
if (loading && menus.isEmpty()) {
LoadingShimmer(imageHeight = 800.dp)
}
else if (menus.isEmpty()) {
NothingHere()
}
else {
LazyColumn(
modifier = Modifier
.fillMaxSize()
.weight(1F)
) {
itemsIndexed(
items = menus
) { index, menu ->
MenuCard(
menu = menu,
onClick = {
}
)
}
}
}
}
}
}
MenuCard
#Composable
fun MenuCard(
menu: Menu,
modifier: Modifier = Modifier,
onClick: () -> Unit,
) {
Card(
shape = MaterialTheme.shapes.small,
modifier = Modifier
.padding(
bottom = 6.dp,
top = 6.dp
)
.fillMaxWidth()
.clickable(onClick = onClick),
elevation = 8.dp
) {
Column {
Text(
text = menu.name,
fontSize = 30.sp,
modifier = Modifier
.fillMaxWidth()
.wrapContentWidth(Alignment.CenterHorizontally)
.wrapContentHeight(Alignment.CenterVertically),
style = MaterialTheme.typography.h3
)
}
}
}
In summary, MenuCards are created as many as the number of menu on the MenuList screen, and I hope the height of each MenuCard will be 1/n.
(n = number of menu)
Like, when number of menu is 8,
Just same height to each menu.
Replace your LazyColumn code with BoxWithConstraints and regular Column.
BoxWithConstraints for getting the minHeight or in this case you can say screen height.
Change to something like the below.
BoxWithConstraints(modifier = Modifier.fillMaxSize()) {
val height = minHeight/menus.size
Column(
modifier = Modifier
.fillMaxSize()
) {
menus.forEachIndexed { index, menu ->
MenuCard(
modifier = Modifier.height(height),
menu = menu,
onClick = {
}
)
}
}
}
MenuCard
#Composable
fun MenuCard(
menu: Menu,
modifier: Modifier = Modifier,
onClick: () -> Unit,
) {
Card(
shape = MaterialTheme.shapes.small,
modifier = modifier
.padding(
bottom = 6.dp,
top = 6.dp
)
.fillMaxWidth()
.clickable(onClick = onClick),
elevation = 8.dp
) {
Column(
modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = menu.name,
fontSize = 30.sp,
style = MaterialTheme.typography.h3
)
}
}
}
You will get this
3 items and 6 items
If all you want is a way to divide the screen height in equal proportions to all the n items, you can simply go for a column.
val items = ... //I assume you have access to this
val screenHeight = LocalConfiguration.current.screenHeightDp
Column{
items.forEach{
MenuCard(modifier = Modifier.height(1f / items.size * screenHeight))
}
}
Column and LazyColumn are fundamentally different. LazyColumn's sole purpose is to deal with large datasets by loading and caching only a small window off that set. Now, if there is nothing to be scrolled, LazyColumn is useless, in fact, it is worse.
EDIT:
You could use BoxWithConstraints to get the screen dimensions as well, but it, in this context, would be more of a workaround than a solution. It adds an extra Composable and decreases the readability of the code too, so for getting the screen dimensions, always use the API that was specifically built for the job.

Resources