docs/api_guidelines/compose_api_guidelines/basic_patterns.md
This section covers the basic API patterns used throughout the compose libraries and lightly explains when each is applicable.
@Composable component {#component-pattern}A @Composable component is defined as a @Composable function that returns
Unit, emits a Layout, and accepts a Modifier.
Components:
@Composable
fun ExampleComposable(
modifier: Modifier = Modifier,
content: @Composable () -> Unit,
) {
// Box emits a layout
// content may draw something or accept input
Box(
modifier = modifier.exampleDecorations(),
content = content
)
}
A modifier is a wrapper around a single layout, and can be chained with other modifiers.
Modifiers passed to a component MUST apply to exactly one layout, and can perform any basic function of compose: Measure, Layout, Draw, Semantics, etc.
Developers typically pass modifiers using a fluent-style builder to customize a component.
Component(
modifier = Modifier
.padding(...)
.background(...)
)
Components are the nouns of Compose, and named UI elements that describe a
user-visible widget or layout MUST be components. For example Button,
Text, Column and Box are all components.
Any feature that needs to emit different components over time MUST be a component.
Features that do not need to emit a new layout node, and only modifies exactly one layout and can be applied to any layout MAY be a modifier.
Features that are applied to arbitrary single layouts (e.g. padding, drawBehind) SHOULD be a Modifier.
DON’T
@Composable
fun Padding(allSides: Dp, content: @Composable () -> Unit) {
// impl
}
// usage
Padding(12.dp) {
// What does padding mean with 2x children?
UserCard()
UserPicture()
}
Do:
fun Modifier.padding(allSides: Dp): Modifier = // implementation
// usage
UserCard(modifier = Modifier.padding(12.dp))
Do
@Composable
fun AnimatedVisibility(
visible: Boolean,
modifier: Modifier = Modifier,
content: @Composable () -> Unit
) {
// ... lots of details
if (isVisibleOrAnimating) {
AnimatedVisibilityImpl(
content,
visible,
modifier.layout { /* details */ }
)
}
}
// usage: AnimatedVisibility has to have power to remove/add UserCard
// to hierarchy depending on the visibility flag
AnimatedVisibility(visible = false) {
UserCard()
}
Used when typical API interactions involve a developer producing a state-object in composition.
If the following criteria are met a composable factory MAY be used for APIs where a state object is required as a parameter to a composable or modifier and it is likely to be created at the call-site.
At least one of the following should be true for all remember factories:
rememberSavableObjects that can be constructed easily SHOULD expose a regular constructor only.
Remember factories with parameters fall into three categories:
You may prefer simple keys if:
You may prefer to use the param state update model if:
You may prefer parameter factories if:
// showing all three parameter options
@Composable
fun rememberItemReturned(param: Para): ItemReturned {
// example of simple keys pattern
return rememberSavable(param, saver = ItemReturnedSaver) {
ItemReturned(param)
}
}
@Composable
fun rememberItemReturned(param: Param): ItemReturned {
// example of param state changes
return rememberSavable(saver = ItemReturnedSaver) {
ItemReturned()
}.also {
it.param = param
}
}
@Composable
fun rememberItemReturned(paramProducer: () -> Param): ItemReturned {
// example of parameter factory
return rememberSavable(saver = ItemReturnedSaver) {
ItemReturned(paramProducer)
}
}
// always expose a non-composable constructor or factory, no matter what style
// of remember* you expose
class ItemReturned(param: Param) {
}
Slots are @Composable lambda passed to a component. This allows developers
calling to fully control the behavior of parts of the component, allowing
component authors to focus on solving one problem.
@Composable Lambdas SHOULD follow these naming rules
content and is a trailing lambda.content, other lambdas are optional (nullable).content. Multiple lambdas are required (non-null)Slots SHOULD be nullable when providing a value to the slot changes the behavior of the composable (e.g. padding changes).
@Composable
fun Tab(
....
modifier: Modifier = Modifier
text: @Composable (() -> Unit)? = null,
icon: @Composable (() -> Unit)? = null,
//... more non-slot params ...
)
@Composable
public fun Button(
onClick: () -> Unit,
modifier: Modifier = Modifier,
// ...
content: @Composable BoxScope.() -> Unit,
) =
Default objects expose default arguments and constants used by components as public API, this allows developers to easily wrap the component in a new component with the exact same default behavior.
A default object is just an object with getters, factory methods, composable getters, and composable factories attached. All public methods should be useful for developers to implement workalike components properly.