Jetpack Compose UI - Button width changes on click inside AlertDialog - android

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.

Related

How can I show full screen dialog or navigate to other screen when I click a Layout function?

I have AppBarNavGraph and RootNavGraph.
I'd like to know two methods, Full Screen Dialog and also Navigating to a new Screen.
What I am trying to is..
Column(
modifier = Modifier
.width(97.dp)
.clickable {
Dialog(onDismissResult = { /*TODO*/ }) {
}
}
,
horizontalAlignment = Alignment.CenterHorizontally
But in clickable, composable function is not callable.
So, What I tried is using this,
val isClicked = remember { mutableStateOf(false) }
Column(
modifier = Modifier
.width(97.dp)
.clickable {
isClicked.value = !isClicked.value
},
horizontalAlignment = Alignment.CenterHorizontally
)
if(isClicked.value){
// show full screen dialog or navigate to some screen.
}
Here are two problems.
I tried with Dialog function.
Dialog(
onDismissResult: () -> Unit,
properties: DialogProperties = DialogProperties(),
content: #Composable () -> Unit
)
but, this doesn't show full screen but just next item. (it's in GridLayout and when I click an item, the dialog needs to shows up to add the item.)
For navigation, I tried, varies ways, but I haven't found any clear solutions...
Could you please help me out? or any advices?
This should do the trick for a simple full screen popup
var showPopup by remember { mutableStateOf(false) }
Box(Modifier
.background(Color.Blue)
.fillMaxSize()) {
Button(onClick = { showPopup = true }) {
Text("Show popup")
}
}
if (showPopup) {
Popup(
onDismissRequest = { showPopup = false }
) {
Box(Modifier
.background(Color.Red)
.fillMaxSize()) {
Button(onClick = { showPopup = false },
modifier = Modifier.align(Alignment.Center)) {
Text("Hide popup")
}
}
}
}
Though if you want proper navigation you should probably use this

How to make a LazyColumn item occupy the remaining height on Jetpack Compose

Can I make a item on LazyColumn occupy only the remaining height available? I'm I tried to use fillParentMaxSize but it make the item as the same size of the LazyColumn, so i can't put another item at the top of the list, like a header that I want to scroll with the content.
Sample code
#Composable
fun LazyColumnTest() {
LazyColumn(
modifier = Modifier.fillMaxSize()
) {
item {
Column {
Text(text = "This is a title", style = MaterialTheme.typography.h4)
Text(text = "With a subtitle", style = MaterialTheme.typography.subtitle1)
}
}
item {
OtherLayout(modifier = Modifier.fillParentMaxHeight())
}
}
}
#Composable
fun OtherLayout(modifier: Modifier = Modifier) {
Box(modifier = modifier.fillMaxSize()) {
Icon(
Icons.Default.Close,
contentDescription = null,
modifier = Modifier
.size(150.dp)
.align(Alignment.TopCenter)
)
Button(
onClick = { /*TODO*/ },
modifier = Modifier
.padding(bottom = 16.dp)
.align(Alignment.BottomCenter)
) {
Text(text = "Button at bottom")
}
}
}
Here is the current result. The button is outside the screen, so I need to scroll to see it.
Update
In the example above, the idea is to use this OtherLayout like a state. I can show the items or this OtherLayout that has a button at bottom, like a retry button.
I can do about the same layout on view system if I add fillViewport="true" on a ScrollView. It's possible to add a gravity="bottom" on another view and it will stay at the bottom of screen.
I will add a new example here with the header/footer layout to see if I can explain better.
#Composable
fun LazyColumnTest() {
LazyColumn(
modifier = Modifier.fillMaxSize()
) {
item {
// need to scroll with the content
Header()
}
items(2) { position ->
Text(text = "Item $position")
}
item {
// need to stay at bottom if the items not fill the screen
// and scroll with the content if has a lot of items
Footer(modifier = Modifier)
}
}
}
#Composable
private fun Header() {
Column {
Text(text = "This is a title", style = MaterialTheme.typography.h4)
Text(text = "With a subtitle", style = MaterialTheme.typography.subtitle1)
}
}
#Composable
fun Footer(modifier: Modifier = Modifier) {
Column(modifier = modifier.fillMaxWidth()) {
Text(text = "This is a default footer that cannot be changed")
Button(
onClick = { /*TODO*/ },
modifier = Modifier
.padding(bottom = 16.dp)
) {
Text(text = "With a button")
}
}
}
in this example I need that header and footer scrolls with the content, but if the items not fill the entire screen, the footer remains at bottom of the screen.
Using the item function you are adding another item to the scrollable content of the LazyColumn.
If you want that the LazyColumn occupies only the remaining height available, and the footer that doesn't scroll with the list, you can move the footer out of the LazyColumn and apply the weight modifier to the LazyColumn.
Something like:
Column(){
Header()
LazyColumn(Modifier.weight(1f)) {
//....
}
Footer() //your Box
}
In your case:
Column {
//Header
Box(Modifier.fillMaxWidth().height(30.dp).background(Red))
LazyColumn(Modifier.weight(1f)) {
items(itemsList){
Text("Item $it")
}
}
//Footer
OtherLayout()
}
With:
#Composable
fun OtherLayout(modifier: Modifier = Modifier) {
Box(modifier = modifier.fillMaxWidth()) {
//..
}
}

Why can't I use Modifier.padding(top = 20.dp) to increase space in AlertDialog when I use JetPack Compose?

The Code A displays a dialog box based AlertDialog, and I get Image A when I run Code A.
I find the space between title = { Text(text = dialogTitle) } and text = {...} is too closer in Image A.
So I set Modifier.padding(top = 100.dp) to wish to increase the space between the two controls, but I only get Image B, it seems that Modifier.padding(top = 100.dp) doesn't work as expected, how can I fix it?
Code A
#Composable
fun EditTextDialog(
isShow: Boolean,
onDismiss: () -> Unit,
onConfirm: (String) -> Unit,
saveTitle: String = stringResource(R.string.dialog_save_title),
cancelTitle:String = stringResource(R.string.dialog_cancel_title),
dialogTitle:String ="Edit",
editFieldContent:String ="",
) {
var mText by remember(editFieldContent){ mutableStateOf(editFieldContent) }
val cleanAndDismiss = {
mText = editFieldContent
onDismiss()
}
if (isShow) {
AlertDialog(
title = { Text(text = dialogTitle) },
text = {
Column(
Modifier.padding(top = 20.dp)
//Modifier.padding(top = 100.dp)
//Modifier.height(100.dp), //The same result as Image A
//verticalArrangement = Arrangement.Center
) {
TextField(
value = mText,
onValueChange = { mText = it }
)
}
},
confirmButton = {
TextButton(onClick = { onConfirm(mText) }) {
Text(text = saveTitle)
}
},
dismissButton = {
TextButton(onClick = cleanAndDismiss) {
Text(text = cancelTitle)
}
},
onDismissRequest = cleanAndDismiss
)
}
}
Image A
Image B
With M3 AlertDialog (androidx.compose.material3.AlertDialog) it works.
With M2 AlertDialog, one solution is to remove the title attribute and use the text attribute for the whole layout.
AlertDialog(
onDismissRequest = {},
text = {
Column(){
Text(text = "Title")
Spacer(Modifier.height(30.dp))
TextField(
value = "mText",
onValueChange = { },
)
}
},
//buttons..
)
I don't understand what you're trying to do. If you want more space between the TextField and the dialog buttons, then you don't want a top padding. You want padding below the TextField, so it would be bottom padding on the column.
Also, there's a chance that it won't work properly inside a Column, and you might have to switch it out for Box. And if that doesn't work for some reason, just add a spacer below the TextField:
Spacer(Modifier.height(20.dp).fillMaxWidth())
I assume you are using Material AlertDialog? If yes try using the Material3 variant. It should work then.
Just implement following library:
implementation "androidx.compose.material3:material3:1.0.0-beta02"
And make sure to use the Material3 AlertDialog Composable which is imported with the library.

How to make Dialog re-measure when a child size changes dynamically?

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
}
}

Increase space between title and text of AlertDialog in Compose

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")
}
}
)

Categories

Resources