-
Notifications
You must be signed in to change notification settings - Fork 8
Home
Finds usages of the modifier parameter on non-top-level children of a composable function. This tends to happen during refactorings and often leads to incorrect rendering of a composable.
For example, imagine that Column here used to be a top composable, but then it got wrapped by the Row. But the modifier parameter was moved along with it and is now applied to the wrong composable:
@Composable
fun MyComposable(modifier: Modifier) {
Row(modifier = Modifier.padding(30.dp)) {
Column(modifier = modifier.padding(20.dp)) {
}
}
}
@Composable
fun Content() {
MyComposable(modifier = Modifier.background(color = Color.Green))
}This should be fixed by using modifier parameter on the Row instead:
@Composable
fun MyComposable(modifier: Modifier) {
Row(modifier = modifier.height(30.dp)) {
Column(modifier = Modifier.padding(20.dp)) {
}
}
}Suggests hoisting event argument passing to the upper level which often simplifies individual composable components. This makes individual components less coupled to the structure of their parameters and leaves that to the parent, which in turn often leads to simplification of a composable.
For example here the PrettyButton is unnecessary coupled to the structure of Data — it extracts id field inside the onClick:
data class Data(id: Int, title: String)
fun PrettyButton(data: Data, onAction: (Int) -> Unit) {
Button(onClick = { onAction(data.id) })
}
fun Parent() {
val data = Data(id = 3, title = "foo")
PrettyButton(data = data, onAction = { id -> process(id) })
}This "knowledge" of id can be moved to the parent which would not only simplify the PrettyButton by removing unnecessary lambda wrapper around onAction call, but this also makes it easier to work with PrettyButton later, during refactorings. Here the data.id is hoisted into the parent:
fun PrettyButton(data: Data, onAction: () -> Unit) {
Button(onClick = onAction)
}
fun Parent() {
val data = Data(id = 3, title = "foo")
PrettyButton(data = data, onAction = { process(data.id) })
}Ensures that all event handler parameters of composable functions are named in the same Compose-like style, i.e. they have on prefix and do not use past tense.
This rule suggests naming improvements
fun Button(click: () -> Unit) // ❌ wrong: missing "on"
fun Button(onClick: () -> Unit) // ✅ correct
fun Box(scroll: () -> Unit) // ❌ wrong: missing "on"
fun Box(onScroll: () -> Unit) // ✅ correct
fun Box(onScrolled: () -> Unit) // ❌ wrong: using past tense
fun Box(onScroll: () -> Unit) // ✅ correctChecks that parameters of Composable functions have a correct order:
- Required parameters come first
- Optional parameters come after required
Non-compliant:
Header(
title: String,
enabled: Boolean = false,
description: String,
)Compliant:
Header(
title: String,
description: String,
enabled: Boolean = false,
)Checks that the modifier parameter of a Composable function has the correct default value.
Using a default value other than Modifier can lead to various non-obvious issues and inconveniences.
Non-compliant:
fun Content(modifier: Modifier = Modifier.fillMaxSize()) {
Text("Greetings", modifier) // fillMaxSize will be ignored here
}Compliant:
fun Content(modifier: Modifier = Modifier) {
Text("Greetings", modifier.fillMaxSize())
}Checks that the modifier parameter of a Composable function has a default value.
Non-compliant:
fun Content(modifier: Modifier) {
Text("Greetings")
}Compliant:
fun Content(modifier: Modifier = Modifier) {
Text("Greetings")
}Suggests using Modifier.heightIn() instead of Modifier.height() on a layouts which have Text children, so that if the text turns out to be long and would overflow and wrap, layout will not cut it off
Row(modifier = Modifier.height(24.dp)) {
Text("hello")
}with
Row(modifier = Modifier.heightIn(min = 24.dp)) {
Text("hello")
}Ensure that modifier is declared as a first parameter after required parameters and before optional parameters:
@Composable
fun Button(
modifier: Modifier = Modifier,
text: String,
onClick: () -> Unit,
arrangement: Arrangement = Vertical,
)Should be replaced with:
@Composable
fun Button(
text: String,
onClick: () -> Unit,
modifier: Modifier = Modifier,
arrangement: Arrangement = Vertical,
)Rationale: this would allow one to write a short form
Button("Continue", onClick = { ... })
Title("Hello")otherwise it would be required to always use named parameters
Button(text = "Continue", onClick = { ... })
Title(text = "Hello")Google's androidx.compose.material composables follow this convention.
Finds and reports composable previews which are not marked as private
Checks that composable function is defined as a top-level function.
allowInObjects config property can be used to control if usage of composable functions
in object is permitted.
Non-compliant code would look like this:
interface Screen {
@Composable
fun Content(modifier: Modifier = Modifier)
}
class ScreenImpl : Screen {
@Composable
override fun Content(modifier: Modifier) {
Text("Greetings", modifier.fillMaxSize())
}
}And the compliant code is to have a top-level composable function:
fun ScreenContent(modifier: Modifier = Modifier) {
Text("Greetings", modifier.fillMaxSize())
}The @Composable functions that return Unit should start with upper-case while the ones that return a value should
start with lower case.
Examples of compliant and non-compliant code are given below.
Non-compliant:
@Composable
fun button() {
…
}Correct:
@Composable
fun Button() {
…
}Non-compliant:
@Composable
fun Value(): Int = …Compliant:
@Composable
fun value(): Int = …See also: Compose API guidelines.