Slot API Design in Jetpack Compose | by Anton Popov | December 2022

Photo by Towfiqu barbhuiya

Limit the content material of the composable lambda utilizing layoutId modifier

Jetpack Compose launched us to a brand new idea: Slots API. Permits builders to create versatile simple to make use of and reusable UI elements. Nevertheless, typically there’s an excessive amount of flexibility: we want a strategy to permit solely a sure variety of UI elements to be positioned in a slot.

However methods to do it? Immediately we are going to discover out. Buckle up!

Think about that we’re designing our personal TopAppBar:

@Composable
enjoyable TopAppBar(
title: String,
icon: @Composable () -> Unit,
)

And we have already got a behavior Icon:

@Composable
enjoyable Icon(painter: Painter, tint: Colour = DefaultTintColor)

However we would like customers of TopAppBar to have the ability to place one and just one Icon composable in a icon slot.

The best manner is to only do that:

@Composable
enjoyable TopAppBar(
title: String,
icon: painter: Painter,
iconTint: Colour = DefaultTintColor,
) {
// ...
Icon(painter, iconTint)
// ...
}

Nevertheless, if a Icon element has many parameters (5–9 or much more), and/or TopAppBar has many icons, this resolution turns into impractical.

We will create a TopAppBarIcon knowledge class particularly for TopAppBar:

knowledge class TopAppBarIcon(
val painter: Painter,
val tint: Colour = DefaultTintColor,
)

@Composable
enjoyable TopAppBar(
title: String,
icon: TopAppBarIcon,
) {
// ...
Icon(icon.painter, icon.tint)
// ...
}

Nevertheless, this resolution has many disadvantages:

  1. code duplication. A listing of IconParameters and their default values ​​are duplicated in TopAppBarIconwill probably be a ache to keep up.
  2. combinatorial explosion. If an icon is for use in lots of different elements, there will probably be many wrapper lessons for it. Icon element.
  3. not idiomatic. Jetpack Compose makes use of slot APIs loads and the builders are used to it. This strategy strays from conference and confuses builders.
  4. Recomposition scope. Sure icon.tint modifications, it would set off a recomposition of the set TopAppBarwhich isn’t very environment friendly, particularly when utilizing animations (animating tint, for instance).

The Compose Structure subsystem has a factor known as layoutId — a parameter that every LayoutNode can have (applied utilizing main data modifier).

First, it’s configured utilizing a Modifier.layoutIdthen – learn in a design section (measuring).

Making use of this data to our downside, we first use Modifier.layoutId inside a Icon like this:

@Composable
enjoyable Icon(painter: Painter, tint: Colour = DefaultTintColor) {
Field(Modifier.layoutId(IconLayoutId)) {
Icon(
painter = painter,
tint = tint,
contentDescription = null
)
}
}

non-public object IconLayoutId

Then create a composable operate. RequireLayoutId:

@Composable
enjoyable RequireLayoutId(
layoutId: Any?,
errorMessage: String = "Failed requirement.",
content material: @Composable () -> Unit,
) = Structure(content material) { measurables, constraints ->
val youngster = measurables.singleOrNull()
?: error("Solely a single youngster is allowed, was: ${measurables.measurement}")

// learn layoutId of a single youngster
require(youngster.layoutId == layoutId) { errorMessage }

// don't truly measure or structure a toddler
structure(0, 0) {}
}

This operate is a customized structure that does not truly measurement or structure any youngsters, it solely checks if a single allowed youngster has a required structure ID.

Lastly, we use the operate like this:

@Composable 
enjoyable TopAppBar(
title: String,
icon: @Composable () -> Unit,
) {
RequireLayoutId(
layoutId = IconLayoutId,
errorMessage = "Solely Icon is allowed",
content material = icon
)

// later in code
icon()
}

Listed below are some check circumstances:

@Preview
@Composable
enjoyable TestCases() = Column {
// ✅
TopAppBar(title = "Lorem") {
Icon(painter = rememberVectorPainter(Icons.Default.Add))
}

// ❌
TopAppBar(title = "Lorem") {
Button(onClick = {})
}

// ❌
TopAppBar(title = "Lorem") {

}

// ❌
TopAppBar(title = "Lorem") {
Field {
Icon(painter = rememberVectorPainter(Icons.Default.Add))
}
}

@Composable
enjoyable IconWrapper() {
// you need to use any composable features that don't emit UI
bear in mind { "One thing" }
LaunchedEffect(Unit) { delay(200) }
Icon(painter = rememberVectorPainter(Icons.Default.Add))
}

// ✅
TopAppBar(title = "Lorem") {
IconWrapper()
}
}

If you’d like much more granular management over what Icons may be handed to TopAppBaryou’ll be able to create a composable wrapper that may solely permit a sure subset of all the things attainable Icon settings:

interface TopAppBarScope {
@Composable
enjoyable TopAppBarIcon(painter: Painter) {
Field(Modifier.layoutId(TopAppBarIconLayoutId)) {
Icon(painter = painter, tint = TopAppBarTint)
}
}

companion object {
non-public val occasion = object : TopAppBarScope {}
inner operator enjoyable invoke() = occasion
}
}

non-public object TopAppBarIconLayoutId

@Composable
enjoyable TopAppBar(
title: String,
icon: @Composable TopAppBarScope.() -> Unit,
) {
// ...
RequireLayoutId(
layoutId = TopAppBarIconLayoutId,
errorMessage = "Solely TopAppBarIcon is allowed",
) {
TopAppBarScope().icon()
}
TopAppBarScope().icon()
// ...
}

Use:

@Preview
@Composable
enjoyable TestCases() = Column {
// ✅
TopAppBar(title = "Lorem") {
TopAppBarIcon(painter = rememberVectorPainter(Icons.Default.Add))
}
}

As a result of TopAppBarScope we even get a pleasant autocompletion:

Autocomplete dropdown in TopAppBarScope

After all, this strategy can simply be prolonged to just accept a particular variety of totally different UI elements.

That is all for right now, I hope it really works for you! Be happy to depart a remark if one thing is unclear or when you have questions. Thanks for studying!

By admin

x
THE FUTURE - BENEFIT NEWS - DANA TECH - RALPH TECH - Tech News - BRING THE TECH - Tech Updates - News Update Viral - THE TRUTH - WORLD TODAY - WORLD UPDATES - NEWS UPDATES - NEWS FLASH - TRUTH NEWS - RANK NEWS - PREMIUM NEWS - FORUM NEWS - PROJECT NEWS - POST NEWS - WORLD NEWS - SPORT NEWS - INDICATOR NEWS - NEWS ROOM - HEADLINE NEWS - NEWS PLAZA