On my Samsung Galaxy S22+ with One UI 5.0 and Android 13, compose AlertDialog always takes up full width, on other devices it works just as expected.
Compose version is 1.3.1
You can reproduce this by simply just downloading material catalog app from Google Play store.
I suspect this is most likely a bug on Compose side, if there's a quick fix, I'd appreciate it.
#Composable
fun AlertDialogSample() {
val openDialog = remember { mutableStateOf(true) }
if (openDialog.value) {
AlertDialog(
onDismissRequest = {
// Dismiss the dialog when the user clicks outside the dialog or on the back
// button. If you want to disable that functionality, simply use an empty
// onCloseRequest.
openDialog.value = false
},
title = {
Text(text = "Title")
},
text = {
Text(
"This area typically contains the supportive text " +
"which presents the details regarding the Dialog's purpose."
)
},
confirmButton = {
TextButton(
onClick = {
openDialog.value = false
}
) {
Text("Confirm")
}
},
dismissButton = {
TextButton(
onClick = {
openDialog.value = false
}
) {
Text("Dismiss")
}
}
)
}
}
After Android 13 update on my device, Dialog with XML layouts are taking expected width. But Compose AlertDialog & Dialog are taking up full width. We are facing this issue with Compose Dialogs only,
I am using Samsung Galaxy M32 with One UI 5.0 & Android 13, App uses Compose version 1.1.0-beta01 & targetSdkVersion 33,
using usePlatformDefaultWidth = true did not help,
This issue is most likely a bug on Compose side,
You can find quick fixes for both Dialog and AlertDialog in compose,
For Compose AlertDialog()
I have used modifier and set DialogProperty usePlatformDefaultWidth to false & set fillMaxWidth with fraction 0.92f.
modifier = Modifier.fillMaxWidth(0.92f),
properties =DialogProperties(usePlatformDefaultWidth =false),
Compose AlertDialog() code snippet:
AlertDialog(
modifier = Modifier.fillMaxWidth(0.92f),
properties = DialogProperties(
usePlatformDefaultWidth = false
),
onDismissRequest = { ... },
buttons = {
Column(
modifier = Modifier
.fillMaxWidth()
) {
...
}
},
title = {
},
text = {
Column(
modifier = Modifier
.fillMaxWidth()
) {
........
}
}
)
For Compose Dialog()
I have used Surface to wrap the dialog content with modifier = Modifier.fillMaxWidth(0.92f),
RoundedCornerShape with radius, set Color.Transparent to background color and also set DialogProperty usePlatformDefaultWidth to false
Surface(
modifier = Modifier.fillMaxWidth(0.92f),
shape = RoundedCornerShape(8.dp),
color = Color.Transparent,
content = {})
Compose Dialog() code snippet:
Dialog(
onDismissRequest = { },
properties = DialogProperties(
dismissOnClickOutside = true,
dismissOnBackPress = true,
usePlatformDefaultWidth = false
),
content = {
Surface(
modifier = Modifier.fillMaxWidth(0.92f),
shape = RoundedCornerShape(8.dp),
color = Color.Transparent,
content = {
Column(
modifier = Modifier
.background(color = colorResource(id = android.R.color.white))
.fillMaxWidth(1f)
.wrapContentHeight(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
........
}
})
})
The Alert-Dialog-Composable accepts DialogProperties
#Composable
fun AlertDialog(
properties: DialogProperties = DialogProperties()
...
)
/**
* Properties used to customize the behavior of a [Dialog].
...
* #property usePlatformDefaultWidth Whether the width of the dialog's content should
* be limited to the platform default, which is smaller than the screen width.
*/
class DialogProperties #ExperimentalComposeUiApi constructor(
val usePlatformDefaultWidth: Boolean = true
...
)
By default, usePlatformDefaultWidth = true, so the Dialog should not fill the screen width.
-> What you see is most probably a bug & should be reported
Related
I'm facing this weird issue with fillMaxWidth(fraction = ...) with an AlertDialog and Button, where the Button shows up initially at one size, and on click it shrinks to wrapping its content. Here is the most basic example I can create. I've tried with multiple versions of Compose but they all do the same thing. Any ideas?
AlertDialog(
modifier = modifier,
onDismissRequest = {},
text = { },
buttons = {
Button(
onClick = { },
modifier = Modifier
.fillMaxWidth(0.75f)
.padding(start = 12.dp, end = 12.dp, bottom = 8.dp)
) {
Text(text = "Done")
}
}
)
Before click:
After click:
I think you have to define a specific width for the AlertDialog, any child it has may not be able to calculate a 75% of unknown width .
either you fill the max width of the dialog
AlertDialog(
modifier = Modifier.fillMaxWidth(),
...
or specify an actual dp width
AlertDialog(
modifier = Modifier.width(150.dp),
...
I don't wanna throw jargons I can't explain, but I suspect when AlertDialog doesn't have any specific width, it cannot provide any incoming measurements for its children, which is in this case the Button cannot compute a 75% of unknown width on initial display, it looks like all size computation is happening only after an action has been made to the button, maybe a recomposition is happening under-the-hood having correct measurements.
I've used the sample composable from here and added your modifier to it for each button so it looks like
#Composable
fun AlertDialogSample() {
MaterialTheme {
Column {
val openDialog = remember { mutableStateOf(false) }
Button(onClick = {
openDialog.value = true
}) {
Text("Click me")
}
if (openDialog.value) {
AlertDialog(
onDismissRequest = {
// Dismiss the dialog when the user clicks outside the dialog or on the back
// button. If you want to disable that functionality, simply use an empty
// onCloseRequest.
openDialog.value = false
},
title = {
Text(text = "Dialog Title")
},
text = {
Text("Here is a text ")
},
confirmButton = {
Button(
modifier = Modifier
.fillMaxWidth(0.75f)
.padding(start = 12.dp, end = 12.dp, bottom = 8.dp),
onClick = {
openDialog.value = false
}) {
Text("This is the Confirm Button")
}
},
dismissButton = {
Button(
modifier = Modifier
.fillMaxWidth(0.75f)
.padding(start = 12.dp, end = 12.dp, bottom = 8.dp),
onClick = {
openDialog.value = false
}) {
Text("This is the dismiss Button")
}
}
)
}
}
}
}
and I see the buttons at 75% width.
Doesn't explain why you see the described issue, but does give you a solution.
I implemented a simple dialog with Jetpack Compose on Android.
I am trying to show a caution message when isRehearsal is true.
The variable isRehearsal is toggled when the user clicks buttons, and changing button works fine when the user clicks the buttons and toggles isRehearal.
The problem is, that the caution text does not appear when the initial value of isRehearsal is false and later the variable becomes true. When I change the initial value of isRehearsal to true, then the text disappears/appears fine when isRehearsal becomes false or true.
var isRehearsal by remember { mutableStateOf(false) }
Dialog(
onDismissRequest = { dismiss() },
DialogProperties(dismissOnBackPress = true, dismissOnClickOutside = true)
) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.background(White, shape = RoundedCornerShape(8.dp))
.fillMaxWidth()
.padding(12.dp)
) { // the box does not resize when the caution text appears dynamically.
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
Text(text = "Set speed")
Row(
horizontalArrangement = Arrangement.SpaceEvenly,
modifier = Modifier.fillMaxWidth()
) {
if (isRehearsal) {
Button(
onClick = { isRehearsal = false },
colors = ButtonDefaults.buttonColors(
backgroundColor = Colors.Red400
)
) {
Text(text = "Rehearsal ON")
}
} else {
Button(
onClick = { isRehearsal = true },
colors = ButtonDefaults.buttonColors(
backgroundColor = Colors.Green400
)
) {
Text(text = "Rehearsal OFF")
}
}
Button(onClick = { onClickStart(pickerValue) }) {
Text(text = "Start")
}
}
if (isRehearsal) { // BUG!! when the intial value of isRehearsal is false, then the text never appears even when the value becomes true.
Text(text = "Rehearsal does not count as high score") // <- The caution text
}
}
}
}
How should I change the Box block to make the box's height extend properly to be able to wrap caution text when a view appears dynamically?
Edit
If I change the catuion message part to like below, the text appears fine even when the initial value of isRehearsal is false. Therefore, I think that the issue is with the height of the Box composable.
if (isRehearsal) {
Text(text = "Rehearsal does not count as high score")
} else {
Spacer(modifier = Modifier.height(100.dp))
}
You can use DialogProperties() with
usePlatformDefaultWidth = false
as a workaround. Example:
Dialog(
onDismissRequest = {},
properties = DialogProperties(usePlatformDefaultWidth = false)
) {
Card(modifier = Modifier.padding(8.dp).wrapContentSize().animateContentSize()) {
// content
}
}
It looks like the topmost Dialog view size is not getting updated after the first recomposition. It's a known bug.
As a workaround until it's fixed, you can make a transparent topmost view take all the size available, and handle clicks same as dismissOnClickOutside option does:
Dialog(
onDismissRequest = { },
) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.fillMaxSize()
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = null,
) {
// same action as in onDismissRequest
}
) {
// content
}
}
In a simple AlertDialog like the following
AlertDialog(
modifier = Modifier,
title = {
Text(text = "Title")
},
text = {
Column(
modifier = Modifier.fillMaxWidth()
) {
TextButton() {
Text("Text 1")
}
TextButton() {
Text("Text 2")
}
}
},
confirmButton = {},
dismissButton = {}
)
how can I set a spacing between title and the first TextButton?
I tried to set a .padding(top = X.dp) to the Column, or the first text button, but this seems to only create a space at the bottom of the AlertDialog.
Also setting a custom .height(X.dp) did not work.
I'm using Compose 1.0.3
As #Abhimanyu perfectly explains why it's not working right now, here's the workaround I'm using to achieve the desired padding: putting the title in the content. AlertDialog's title param is optional, so it can be omitted/set to null, and instead the actual title can be put in the text parameter (which holds the dialog content).
#Composable
fun MyComposable() {
AlertDialog(
title = null,
text = {
Column {
Text(
modifier = Modifier.padding(vertical = 16.dp),
text = "Actual title"
)
// Rest of the dialog content
}
}
)
}
This is NOT an answer. It only provides info on why this is not possible.
The requirement seems not achievable at this point (6th Oct 2021) with the current compose version (1.0.3).
Will update this once that is possible.
The AlertDialog code does not respect the padding values provided.
AlertDialog.kt
// Baseline distance from the first line of the text to the last line of the title
private val TextBaselineDistanceFromTitle = 36.sp
The text offset used for the positioning is calculated like this.
val textOffset = if (titlePlaceable == null) {
TextBaselineDistanceFromTop.roundToPx()
} else {
TextBaselineDistanceFromTitle.roundToPx()
}
The distance between the first text in the text composable and the last text in the title composable is always 36.sp.
The Alert Dialog code in compose seems too hackish currently and I could see a few TODO's in the code.
Hopefully, the code will be changed to handle more scenarios soon.
I'm using this composable as first child inside Column
#Composable
fun HackySpacer(space: Dp) {
Box(
modifier = Modifier
.height(space)
.fillMaxWidth()
) {
Text(text = "")
}
}
It's not perfect, but it works for my usecase.
Is now possible using the new AlertDialog from Compose Material 3.
The default spacing between title and text is much more reasonable and it is also possible to add Modifier.padding() or Spacer() to both.
implementation("androidx.compose.material3:material3:1.0.0-alpha01")
androidx.compose.material3.AlertDialog(
onDismissRequest = {
openDialog.value = false
},
title = {
Text(text = "Title", modifier = Modifier.padding(50.dp))
},
text = {
Spacer(Modifier.height(50.dp))
Text(text = "Turned on by default")
},
confirmButton = {
TextButton(
onClick = {
openDialog.value = false
}
) {
Text("Confirm")
}
},
dismissButton = {
TextButton(
onClick = {
openDialog.value = false
}
) {
Text("Dismiss")
}
}
)
It seems impossible to change the width of the dialog with Compose.
The closest I've come to with changing the dialog width is through DialogProperties.usePlatformDefaultWidth. Setting it to false causes the dialog to fill the screen but is there a way to use custom width?
Use can define a custom AlertDialog using the constructor with text,title and buttons parameters and applying a size (for example with the Modifier.size) and overriding the default behaviour with usePlatformDefaultWidth = false :
AlertDialog(
onDismissRequest = { /*TODO*/ },
title = {
Text(text = "Title")
},
text = {
Text(
"This area typically contains the supportive text " +
"which presents the details regarding the Dialog's purpose."
)
},
buttons = {},
properties = DialogProperties(
usePlatformDefaultWidth = false
),
modifier = Modifier.size(200.dp,250.dp)
)
If you want to use a constant width in all your project you can create a dialog with customized width as follows
#Composable
fun MyCustomDialog(
onDismissRequest: () -> Unit,
properties: DialogProperties = DialogProperties(),
content: #Composable () -> Unit
) {
Dialog(
onDismissRequest = onDismissRequest,
// We are copying the passed properties
// then setting usePlatformDefaultWidth to false
properties = properties.let {
DialogProperties(
dismissOnBackPress = it.dismissOnBackPress,
dismissOnClickOutside = it.dismissOnClickOutside,
securePolicy = it.securePolicy,
usePlatformDefaultWidth = false
)
},
content = {
Surface(
color = Color.Transparent,
modifier = Modifier.width(250.dp), // Customize your width here
content = content
)
}
)
}
Hi i'm trying to implement a lazycolumn of a list of posts, I tested it on the emulator api 21 and 29 and it looks kinda smooth on the api 29 it's a little bit laggy, when I tested it on a physical device it was lagging, It looks like it's skipping frames or something..
I tried to remove some views that uses imageVector to see if that was the problem and still the same problem.
This is my composable view:
#Composable
fun HomePostView(
category: String,
imagesUrl: List<String> = listOf(imageHolder),
doctorProfileImage: String = imageUrl,
title: String,
subTitle: String
) {
Card(
shape = PostCardShape.large, modifier = Modifier
.padding(horizontal = 3.dp)
.fillMaxWidth()
) {
Column {
PostTopView(
category = category,
onOptionsClicked = { /*TODO option click*/ },
onBookmarkClicked = {/*TODO bookmark click*/ })
CoilImage(
data = imagesUrl[0],
fadeIn = true,
contentDescription = "post_image",
modifier = Modifier
.fillMaxWidth()
.requiredHeight(190.dp)
.padding(horizontal = contentPadding),
contentScale = ContentScale.Crop
)
Spacer(modifier = Modifier.height(10.dp))
PostDoctorContent(
doctorProfileImage = doctorProfileImage,
title = title,
subTitle = subTitle
)
Spacer(modifier = Modifier.height(contentPadding))
PostBottomView(likesCount = 293, commentsCount = 22)
Spacer(modifier = Modifier.height(contentPadding))
}
}
Spacer(modifier = Modifier.height(10.dp))
}
#Composable
private fun PostDoctorContent(doctorProfileImage: String, title: String, subTitle: String) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = contentPadding)
) {
CoilImage(data = doctorProfileImage,
contentScale = ContentScale.Crop,
contentDescription = null,
fadeIn = true,
modifier = Modifier
.size(30.dp)
.clip(CircleShape)
.clickable {
/*Todo on doctor profile clicked*/
})
Column {
Text(
text = title, fontSize = 14.sp, maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.padding(horizontal = contentPadding)
)
Text(
text = subTitle,
fontSize = 11.sp,
color = LightTextColor,
maxLines = 2,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.padding(horizontal = contentPadding)
)
}
}
}
#Composable
private fun PostBottomView(likesCount: Long, commentsCount: Long) {
Row(
modifier = Modifier.padding(horizontal = contentPadding),
verticalAlignment = Alignment.CenterVertically
) {
Row(
Modifier
.clip(RoundedCornerShape(50))
.clickable { /*Todo on like clicked*/ }
.padding(5.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = ImageVector.vectorResource(id = R.drawable.ic_heart),
contentDescription = "Like"
)
Spacer(modifier = Modifier.width(5.dp))
Text(text = likesCount.toString(), fontSize = 9.sp)
}
Spacer(Modifier.width(20.dp))
Row(
Modifier
.clip(RoundedCornerShape(50))
.clickable { /*Todo on comment clicked*/ }
.padding(5.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = ImageVector.vectorResource(id = R.drawable.ic_comment),
contentDescription = "Comment"
)
Spacer(modifier = Modifier.width(5.dp))
Text(text = commentsCount.toString(), fontSize = 9.sp)
}
}
}
#Composable
private fun PostTopView(
category: String,
onOptionsClicked: () -> Unit,
onBookmarkClicked: () -> Unit
) {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Row(verticalAlignment = Alignment.CenterVertically) {
IconButton(onClick = onOptionsClicked) {
Icon(
imageVector = ImageVector.vectorResource(id = R.drawable.ic_threedots),
contentDescription = "Options",
tint = Color.Unspecified
)
}
Text(text = category, fontSize = 16.sp, color = LightTextColor)
}
IconButton(onClick = onBookmarkClicked) {
Icon(
imageVector = ImageVector.vectorResource(id = R.drawable.ic_bookmark),
contentDescription = "Bookmark"
)
}
}
}
and the lazyColumn:
LazyColumn(contentPadding = paddingValues , state = state ) {
item {
Spacer(modifier = Modifier.height(10.dp))
DoctorsList(
viewModel.doctorListData.value,
onCardClicked = {})
}
items(30) { post ->
HomePostView(
category = "Public Health ",
title = "Food Importance",
subTitle = "you should eat every day it's healthy and important for you, and drink water every 2 hours and what you should do is you should run every day for an hour"
)
}
}
Note: I'm still not using a viewmodel i'm just testing the view with fake data
This may not work for anyone else but on an earlier version (1.0.0-beta01) I saw a very large improvement in performance when I switched
lazy(items) { item ->
...
}
to
items.forEach { item ->
lazy {
...
}
}
I have no idea why and I'm not sure if this is still the case in later versions but its worth checking. So for the example given in the question, that would mean changing
items(30) {
...
}
to
repeat(30) {
item {
...
}
}
TLDR: Make sure your new LazyColumn compose element is not within a RelativeLayout or LinearLayout.
After some investigation the solution to this issue for us was the view in which the LazyColumn was constrained.
Our project uses a combination of Jetpack Compose and the older XML layout files. Our new compose elements were embedded within a RelativeLayout of an existing XML file. This was where the problem was. The compose element would be given the entire screen and then the onMeasure function of the compose element was called to re-configure the view and add our bottom nav bar...this onMeasure was called over and over again, which also in the case of a LazyColumn the re-measuring was throwing out the cache as the height had changed.
The solution for us was to change the RelativeLayout that contained both the new compose element and the bottom nav bar and replace it with a ConstraintLayout. This prevented the onMeasure from being called more than twice and gave a massive performance increase.
Try to build release build with turn off debug logs. should be works fine.
Ok, So far I know that there is an issue with the API when it comes to performance ...But what I found is this
Actually, In my case, I was just loading the image with 2980*3750 pixels image. I just crunched my resources to shorter pixels by some other tools
Now the lag is not present...
In my case, after I set the height of ComposeView to a specific value, it make LazyColumn scroll smooth.
Therefore, I create a XML file like
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.compose.ui.platform.ComposeView
android:id="#+id/compose"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
Fragment
override fun onViewCreated(view: View, savedInstanceState: Bundle ? ) {
super.onViewCreated(view, savedInstanceState)
view.doOnLayout {
compose.layoutParams.height = view.height
compose.requestLayout()
}
}
I know ComposeView height already match_parent so set the height for it again on Fragment seem useless. However, without setting the height, LazyColumn will lagging when scroll.
I am not sure if it will work in all device but work well on my Pixel and Xiomi.
If you are using JPGs or PNGs in your project then check for their size, images having larger size causes a lots of lagging issues on low end devices.
I was having the same issue with my simple LazyColumn list and turned out I was using a JPG having size larger than 2MBs.