What is the purpose or when is it useful to use Modifier.then in jetpack compose?
If you decided some certain properties for a composable but still want to make it customizable you can use then method to take a modifier that you passed as a parameter in your Composable's constructor.
An example :
#ExperimentalComposeUiApi
#Composable
fun CalculatorButton(
symbol: String,
modifier: Modifier = Modifier,
color: Color = Color.White,
textStyle: TextStyle = TextStyle(),
onClick: () -> Unit
) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.clip(RoundedCornerShape(100.dp))
.background(color)
.clickable {
onClick()
}
.then(modifier) // <--------- This Line we pass modifier parameter
// after certain properteis
) { //content
}
You can use it for conditionals, for instance
Modifier. fillMaxWitdh()
.then(
if (condition) Modifier.background(color)
else Modifier.alpha(alpha)
)
Related
I want to change the horizontalAlignment of particular child and remaining will use the same horizontalAlignment. Is this possible in Column?
For example, Column parent modifier use horizontalAlignment = Alignment.CenterHorizontally, and I want particular child modifier will be different horizontalAlignment.
Column(
modifier = Modifier
.padding(16.dp)
.fillMaxSize()
.verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.CenterHorizontally,
) {
ScreenImage()
Description()
if (viewModel.isBluetoothEnabled) {
ScanDeviceList(scanDeviceList)
} else {
Warning()
Spacer(modifier = Modifier.weight(1f))
TryAgainButtonView { tryAgainAction() }
ButtonView { openSettingAction() }
}
}
I want to change ScanDeviceList() to be different horizontalAlignment
#Composable
fun ColumnScope.ScanDeviceList(scanDeviceList: List<ScanResult>) {
Spacer(modifier = Modifier.height(20.dp))
AnimatedVisibility(scanDeviceList.isNotEmpty()) {
Text(
text = stringResource(R.string.vailable_device),
)
}
}
Many Thanks
You can use Modifier.align(Alignment.Start) to align a particular child.
So for example to make your ScanDeviceList() at the Start of the column the code will be like this:
#Composable
fun ColumnScope.ScanDeviceList(scanDeviceList: List<ScanResult>) {
Spacer(modifier = Modifier.height(20.dp))
AnimatedVisibility(
scanDeviceList.isNotEmpty(),
modifier = Modifier.align(Alignment.Start)
) {
Text(
text = stringResource(R.string.vailable_device),
)
}
}
You can also pass the modifier as an argument to ScanDeviceList composable function to make it more generic to the code will be like this:
#Composable
fun ColumnScope.ScanDeviceList(
scanDeviceList: List<ScanResult>,
modifier: Modifier = Modifier
) {
Spacer(modifier = Modifier.height(20.dp))
AnimatedVisibility(
scanDeviceList.isNotEmpty(),
modifier = modifier
) {
Text(
text = stringResource(R.string.vailable_device),
)
}
}
And when you call it you can specify the alignment that you want:
ScanDeviceList(scanDeviceList, modifier = Modifier.align(Alignment.Start))
Note: adding the modifier an argument to your composable functions is considered as a best practice because it will make the function reusable like the example above you can call ScanDeviceList with Alignment.Start or Alignment.End without changing the function itself, just by passing a different modifier as a parameter.
The title isn't very clear but if you try this code, you'll see that there is no text (for isLoading = false).
#Composable
fun RefreshButton(
isLoading: Boolean,
onClick: () -> Unit) {
Chip(
label = {
if (isLoading) {
CircularProgressIndicator(
modifier = Modifier
.fillMaxSize()
.wrapContentSize(Alignment.Center)
.padding(32.dp)
)
} else {
Text(
text = "Refresh", modifier = Modifier
.fillMaxSize()
.wrapContentSize(Alignment.Center)
.padding(32.dp)
)
}
},
onClick = { onClick() },
modifier = Modifier.width(intrinsicSize = IntrinsicSize.Max)
)
}
It works without the padding but I would like to add some padding.
I use wear compose and wear compose-foundation 1.0.0-alpha20.
My answer below was wrong. Compose components tend to have hardcoded values like size and padding. This is true for Chip.kt. Here's an answer explaining a related question.
bylazy is correct. You should apply padding to the parent, not the
content
isLoading: Boolean,
onClick: () -> Unit) {
Chip(
label = {
if (isLoading) {
CircularProgressIndicator(
modifier = Modifier
.fillMaxSize()
.wrapContentSize(Alignment.Center)
)
} else {
Text(
text = "Refresh", modifier = Modifier
.fillMaxSize()
.wrapContentSize(Alignment.Center)
)
}
},
onClick = { onClick() },
modifier = Modifier
.width(intrinsicSize = IntrinsicSize.Max)
.padding(32.dp)
) }
If we have a compose component which gets two or more modifiers, how should we handle it ?
I mean the naming of modifiers while lint complains changing the name of modifier parameter
Sample code to figure out easily :
#Composable
private fun CompletionSection(iconModifier: Modifier, textModifier: Modifier, isActivated: Boolean, newText: String?) {
if (isActivated) {
Icon(
painter = painterResource(R.drawable.ds_ic_check_circle),
modifier = iconModifier
.wrapContentSize()
.padding(top = 18.dp),
tint = MaterialTheme.colors.positive,
contentDescription = null
)
} else if (!newText.isNullOrBlank()) {
Surface(
modifier = textModifier.padding(top = 18.dp),
shape = RoundedCornerShape(32.dp),
border = BorderStroke(width = 2.dp, color = MaterialTheme.colors.primary.copy(alpha = 0.6f)),
) {
Text(
overflow = TextOverflow.Ellipsis,
maxLines = 1,
fontSize = 11.sp,
color = MaterialTheme.colors.primary.copy(alpha = 0.6f),
text = newText,
modifier = Modifier
.defaultMinSize(minHeight = 20.dp)
.wrapContentSize()
.padding(horizontal = 6.dp, vertical = 2.dp),
style = MaterialTheme.typography.android.caption2
)
}
}
}
Here, where the function is used →
ConstraintLayout(
modifier = Modifier.fillMaxSize(),
constraintSet = decoupledConstraints(
marginSpacing02 = marginSpacing02,
marginSpacing01 = marginSpacing01,
entity = entity
)
) {
CompletionSection(
iconModifier = Modifier.layoutId("completedIcon"),
textModifier = Modifier.layoutId("newTextField"),
isActivated = isActivated,
newText = newText
)
}
I assume the reason for this kind of warning is because you usually have one modifier that has to be applied to the whole view. Having an other modifier in arguments is kind of OK, but, for example if you need to apply Modifier.align, you had to duplicate it.
In your case, when you look from where you're using this function, it's hard to tell which modifier will be applied and which is not - it depends on other parameters and you have to know the logic.
I think at least it could have one generic modifier named modifier, which would apply for both views, and two named ones - in my opinion this would make the API a bit more predictable. You can chain modifiers like this: modifier.then(iconModifier).yourModifier()
Anyway, you can suppress it:
#SuppressLint("ModifierParameter")
#Composable
// ...
https://developer.android.com/reference/kotlin/androidx/compose/ui/Modifier
Composables that accept modifiers to be applied to a specific subcomponent foo should name the parameter fooModifier and follow the same guidelines above for default values and behavior. Subcomponent modifiers should be grouped together and follow the parent composable's modifier. For example:
#Composable
fun ButtonBar(
onOk: () -> Unit,
onCancel: () -> Unit,
modifier: Modifier = Modifier,
buttonModifier: Modifier = Modifier
) {
Row(modifier) {
Button(onCancel, buttonModifier) {
Text("Cancel")
}
Button(onOk, buttonModifier) {
Text("Ok")
}
}
}
Composables are designed to accept only one Modifier, so the lint won't be satifsfied no matter how you rename them.
The Composable is something like a unit of interface, and it having multiple modifiers is leaking its inner workings to the outer composables that use it.
I have a Composable as follows:
#Composable
private fun MoviePosterWithRating(movie: MovieModel) {
Box {
Image(<...>)
Box( //Rating circle
contentAlignment = Alignment.Center,
modifier = Modifier
.padding(end = 8.dp, top = 220.dp)
.size(48.dp)
.background(Color.Black, shape = CircleShape)
.align(Alignment.TopEnd)
) {
CircularProgressIndicator(
progress = movie.score / 10,
color = percentageCircleColor(movie.score),
strokeWidth = 2.dp
)
Text(
text = "${movie.score.asPercentage()}%",
color = Color.White,
textAlign = TextAlign.Center,
fontSize = 13.sp,
modifier = Modifier.padding(4.dp)
)
}
}
I would like to extract the rating circle into it's own method so I can reuse it. However, I can't because of the align on modifier. I could pass the whole modifier in as a parameter, but I would just be passing the same padding, size and background colour every time. Is there a way that I could just pass in the .align part of the modifier?
The way you should do this is to have your composable accept a Modifier as parameter, that way you can pass it at the calling point, making your composable more flexible:
#Composable
fun RatingCircle(
modifier: Modifier = Modifier,
// other attributes
) {
Box(
modifier = modifier,
) {
// other composables
}
}
Then you call it like so
Box {
Image(<...>)
RatingCircle(
modifier = Modifier.align(/* alignment */)
)
}
I'm making a compose scaffold-like composable which contains a text composable. I want the scaffold to have a modifier parameter for the text. I would like to be able to pass a modifier from ColumnScope, for instance to be able to align the text.
Here is a simplified version of the scaffold:
#Composable
fun MyScaffold(
text: String,
modifier: Modifier,
) {
Text(
modifier = modifier,
text = text
)
}
When calling the Scaffold, I want to pass a modifier so that the text will be horizontally aligned:
#Preview
#Composable
fun PreviewScaffold() {
MyScaffold(
text = "Hello",
modifier = Modifier.align(Alignment.CenterHorizontally)
)
}
However this doesn't compile, because the align() method is an extension function in the ColumnScope so it only works when inside the ColumnsScope.
How can I pass a ColumnScope Modifier as a parameter of MyScaffold?
Please note that an alternative would be to pass a whole Text composable in the scaffold instead of just a string, but I'd rather have just a string so that I can make most of the styling inside the scaffold but just give the outside caller the possibility to override it.
Thanks!
You can scope your composable with the required scope:
#Composable
fun ColumnScope.MyScaffoldX(
text: String,
modifier: Modifier,
) {
Text(
modifier = modifier.align(Alignment.CenterHorizontally),
text = text
)
}
and then use it with:
Column() {
MyScaffoldX(text = "Hello")
}
Otherwise you can have the Modifier parameter in your composable as in your example:
#Composable
fun MyScaffold(
text: String,
modifier: Modifier = Modifier,
) {
Text(
modifier = modifier,
text = text
)
}
using it with:
Column() {
MyScaffold(
text = "Hello",
modifier = Modifier.align(Alignment.CenterHorizontally)
)
}