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)
)
}
Related
I'm currently applying what I've learnt on compose with a little app, and I wanted to implement a button that has a loader replacing the text when the action is loading.
To do that, I want to implement a button that has either a Text composable or a CircularProgressIndicator, whether the data is loading or not, so the 2 composables are never in the button at the same time.
My problem is that with my implementation, only one of them exists at a time, considering I use a state to define if the button is loading or not.
Screenshot of the idle state:
Screenshot of the loading state (same scale):
Has anybody already encountered this kind of problem? I could put the CircularProgressIndicator at the end of the text but if I can, I would prefer to display one or the other.
Composable:
#Composable
fun ButtonWithLoader(
modifier: Modifier,
isLoading: Boolean,
title: String,
onClickAction: (() -> Unit)? = null
) {
Button(
modifier = modifier,
shape = RoundedCornerShape(50),
onClick = { onClickAction?.invoke() }
) {
if (isLoading) {
CircularProgressIndicator(
modifier = Modifier
.padding(MajorDimens.normal),
color = MajorColor.White
)
} else {
Text(
modifier = Modifier
.padding(MajorDimens.normal),
text = title,
style = MajorFonts.buttonText
)
}
}
}
This is because by default minimum touch target size for CircularProgressIndicator is 48.dp. When it's in composition when isLoading true content height of Button is calculated as 48.dp
You can set a default height for your content so it won't change unless Text height is bigger than 48.dp.
#Composable
fun ButtonWithLoader(
modifier: Modifier,
isLoading: Boolean,
title: String,
onClickAction: (() -> Unit)? = null
) {
Button(
modifier = modifier,
shape = RoundedCornerShape(50),
onClick = { onClickAction?.invoke() }
) {
Box(
modifier = Modifier.height(48.dp),
contentAlignment = Alignment.Center
) {
if (isLoading) {
CircularProgressIndicator(
modifier = Modifier,
color = Color.White
)
} else {
Text(
modifier = Modifier,
text = title,
)
}
}
}
}
If Text can be bigger than 48.dp you can set minimum height so it will be set to height of bigger one and won't change
#Composable
fun ButtonWithLoader(
modifier: Modifier,
isLoading: Boolean,
title: String,
onClickAction: (() -> Unit)? = null
) {
Button(
modifier = modifier,
shape = RoundedCornerShape(50),
onClick = { onClickAction?.invoke() }
) {
Box(
modifier = Modifier.heightIn(min = 48.dp),
contentAlignment = Alignment.Center
) {
if (isLoading) {
CircularProgressIndicator(
modifier = Modifier,
color = Color.White
)
} else {
Text(
modifier = Modifier,
text = title,
fontSize = 40.sp
)
}
}
}
}
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)
)
I use Jetpack Compose in a Android Studio project, the code A can work well, but I get the following warning information, why ?
"Modifier parameter should be named modifier"
Code A
#Composable
fun ScreenAbout(
rootModifier: Modifier = Modifier,
onBack: () -> Unit,
scaffoldState: ScaffoldState = rememberScaffoldState()
) {
Scaffold(
modifier = rootModifier.fillMaxSize(),
scaffoldState = scaffoldState,
topBar = { AboutAppBar(onBack = onBack) }
) { paddingValues ->
Column(
...
}
There is Link Check.
It checks Composable functions with Modifiers parameters for consistency with guidelines.
For functions with one / more modifier parameters, the first modifier parameter must:
Be named modifier
Have a type of Modifier
Either have no default value, or have a default value of Modifier
If optional, be the first optional parameter in the parameter list
In you case just use:
#Composable
fun ScreenAbout(
modifier: Modifier = Modifier,
Or if you want to suppress the warning add
#SuppressLint("ModifierParameter")
#Composable
fun ScreenAbout(
rootModifier: Modifier = Modifier,
#Composable
fun LoginMethod(icon: Icon , text : String) {
Row(
modifier = Modifier
.padding(8.dp)
.shadow(
elevation = 6.dp,
shape = RoundedCornerShape(6.dp)
)
) {
Image(imageVector = icon , contentDescription ="")
Spacer(modifier = Modifier.height(4.dp))
Text(text = text)
}
}
You can use something like:
#Composable
fun LoginMethod(icon: ImageVector, text : String) {
/* your code */
}
and then call it with:
LoginMethod( Icons.Filled.Add, "title" )
If you wish to access the icon anywhere, store it in a viewmodel. You cannot re-reference the Composable from anywhere like views, but as far as the usage INSIDE the scope of the Composable is concerned, you can use the passed-in icon anywhere inside it. You must store it for use outside
I have a composable that passes a Modifier instance to its child composable as follows:
#Composable
fun MyComposable(
modifier: Modifier = Modifier,
content: #Composable BoxScope.() -> Unit,
) {
Box(
modifier = modifier.fillMaxWidth(),
content = content,
)
}
This adds the fillMaxWidth modifier to the modifier argument. However, this is not the desired behaviour because I would like fillMaxWidth to be the default width, but still allow the caller to override it.
How do I combine/merge the two modifiers while making my local modifiers the default?
You can simply use Modifier.then(otherModifier).
Note: Order is important and you might want to consider what you are adding yourself and what you are adding from outside.
composed is used for stateful modifiers like when you want to implement custom touch controls where you will be called every-time anything changes.
See Composed Docs
Use the Modifier.composed function.
#Composable
fun MyComposable(
modifier: Modifier = Modifier,
content: #Composable BoxScope.() -> Unit,
) {
OtherComposable(
modifier = Modifier.fillMaxWidth().composed { modifier },
content = content,
)
}
Use the Modifier.then function.
#Composable
fun MyComposable(
modifier: Modifier = Modifier,
content: #Composable BoxScope.() -> Unit,
) {
OtherComposable(
modifier = Modifier.fillMaxWidth().then(modifier),
content = content,
)
}