Enhance efficiency of Jetpack Compose with Compose Compiler Metrics
In Exyte we try to contribute to open supply as a lot as attainable, from releasing libraries and elements to writing articles and tutorials. One sort of educational we do is copy — take a posh UI element, implement it utilizing new frameworks, and write the tutorial aspect by aspect. We begin with SwiftUI years in the past, however that is our first foray into Android, utilizing Google’s declarative UI framework: Jetpack Compose.
That is the fifth and ultimate installment of the Compose Dribble Copy sequence. Whereas the earlier sections (Half 1, Half 2, Half 3, Half 4) focuses on implementing complicated consumer interfaces and animations, which can cowl Compose compiler metrics. You may see how metrics are gathered and the way that info may help repair efficiency points.
In Jetpack Compose 1.2.0, a brand new characteristic has been added to the Compose compiler that may show varied efficiency metrics in the course of the construct course of. This can be a useful gizmo to seek out potential efficiency points.
Step one is to configure the metric — to do that, add the next to construct.gradle
doc:
subprojects {
duties.withType(org.jetbrains.kotlin.gradle.duties.KotlinCompile).configureEach {
kotlinOptions {
if (venture.findProperty("musicApp.enableComposeCompilerReports") == "true") {
freeCompilerArgs += [
"-P",
"plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=" +
project.buildDir.absolutePath + "/compose_metrics"
]
freeCompilerArgs += [
"-P",
"plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=" +
project.buildDir.absolutePath + "/compose_metrics"
]
}
}
}
}
Now run the construct with the next choices enabled (please be aware that the construct should be compiled in launch mode).
./gradlew assembleRelease -PmusicApp.enableComposeCompilerReports=true
After constructing, compose_metrics
folder will include the recordsdata we have to see.
-
app_release-classes.txt
– class stability info. -
app_release-composables.txt
– details about whether or not the tactic is restartable or skippable. -
app_release-composables.csv
– similar details about strategies, simply in tabular type. -
app_release-module.json
– basic information details about the venture.
Let’s assessment it first app_release-module.json
and assessment aggregated information.
{
"skippableComposables": 147,
"restartableComposables": 156,
"readonlyComposables": 0,
"totalComposables": 168,
}
skippableComposables
– an combination perform is “ignored” if the framework ignores its name when the perform’s parameters are unchanged.
restartableComposables
– an combination perform will be restarted (callback) impartial of perform parameter modifications.
So statistically we’ve features which are restarted however not dropped. This may occasionally imply that they’re restarted for no motive — if the parameters don’t change, the perform could also be skipped and never recalculated. Because the official documentation places it:
If you happen to see a perform that’s restartable however can’t be ignored, that is not all the time a nasty signal, however typically it is a chance to do one among two issues:
* Make the perform ignoreable by ensuring all its parameters are secure
* Make the perform non-restartable by marking it as
NonRestartableComposable
Now let’s take a look at app_release-composables.csv
desk. Kind desk by skipabble
permits us to outline strategies which are restartable however non-skippable. These are those we have to examine extra intently.
Let’s discover these compilations in app_release-composables.txt
begins with AlbumScreen
perform.
restartable scheme("[androidx.compose.ui.UiComposable]") enjoyable AlbumScreen(
unstable screenState: PlayerScreenState
secure playbackData: PlaybackData
secure sharedProgress: Float
secure onBackClick: Function0
secure onInfoClick:
Function4<@[ParameterName(name = 'info')] ModelAlbumInfo,
@[ParameterName(name = 'offsetX')] Float,
@[ParameterName(name = 'offsetY')] Float,
@[ParameterName(name = 'size')] Int, Unit>
)
As you may see, the issue lies in unstable screenState: PlayerScreenState
. We are able to make this parameter secure to make the perform skippable. Allow us to discover it in app_release-classes.txt
unstable class PlayerScreenState {
secure val density: Density
secure var maxContentWidth: Int
secure var maxContentHeight: Int
secure val easing: Easing
secure var currentScreen$delegate: MutableState
secure var currentDragOffset$delegate: MutableState
secure val songInfoContainerHeight: Int
secure val playerContainerHeight: Int
secure val songInfoContentHeight: Int
secure val albumContainerHeight: Int
secure val albumImageWidth: Dp
secure val commentsContainerHeight: Int
secure val backHandlerEnabled$delegate: State
secure val fromPlayerControlsToAlbumsListProgress$delegate: State
secure val playerControlOffset$delegate: State
secure val commentsListOffset$delegate: State
secure val songInfoOffset$delegate: State
secure val albumsInfoSize: Dp
secure val photoScale$delegate: State
= Unstable
}
All members of the category are secure, the compiler solely reveals
. What are these doc speak about this:
Runtime means stability depends upon different dependencies to be resolved at runtime (a kind parameter or a kind in an exterior module)… The road on the backside signifies “expression” method” is used to deal with this stability at runtime.
So we have to attempt to make the layer self-stable — mark the category with @Steady
be aware:
@Steady
class PlayerScreenState(
constraints: Constraints,
non-public val density: Density,
isInPreviewMode: Boolean = false,
) {
//...
}
Subsequent, we clear the cache and rebuild to see how the outcomes have modified. We’re open once more app_release-composables.csv
desk and search for strategies which are restartable however non-skippable. After our modifications, the variety of such features has decreased, so @Steady
Notes helped.
Subsequent, we’ll take a look at different composites. One main concern stands out: assortment.
restartable scheme("[androidx.compose.ui.UiComposable]") enjoyable AlbumsListContainer(
secure modifier: Modifier? = @static Companion
secure listScrollState: ScrollState? = @dynamic rememberScrollState(0, $composer, 0, 0b0001)
unstable albumData: Assortment
secure albumImageWidth: Dp = @static 150.dp
secure transitionAnimationProgress: Float = @static 0.0f
secure appearingAnimationProgress: Float = @static 1.0f
secure onBackClick: Function0? = @static {}
secure onShareClick: Function0? = @static {}
Let’s cope with assortment we use (checklist, set, et cetera.). The compiler can’t decide when the checklist is immutable — Checklist (as Set and different customary assortment courses) are outlined as interfaces in Kotlin which suggests the underlying implementation remains to be mutable. For instance, you may write:
val set: Checklist
On this case the variable is fixed, its declared sort can’t be modified however its implementation can nonetheless change. The Compose compiler can’t be certain of the immutability of this class as a result of it solely has entry to the kind declared that the category is unstable. Let’s have a look at how we are able to change this.
- Create a customized wrapper for the gathering, annotate it with
@Immutable
. - Use kotlin.collections.immutable library.
Each of those strategies are relevant, however we selected the latter as a result of it is a pre-made answer, though it is nonetheless in alpha on the time of writing. So let’s make the dependency:
implementation "org.jetbrains.kotlinx:kotlinx-collections-immutable:x.x.x"
After we add the secure checklist as wanted:
@Steady
@Immutable
information class ModelAlbumInfo(
@DrawableRes val cowl: Int,
val title: String,
val creator: String,
val yr: Int,
- val songs: Checklist,
+ val songs: ImmutableList,
)
- non-public val songs = listOf(
+ non-public val songs = persistentListOf(
ModelSongInfo(0L, "Aurora", "All Is Gentle Inside", "3:54"),
ModelSongInfo(1L, "Aurora", "Queendom", "5:47"),
ModelSongInfo(2L, "Aurora", "Light Earthquakes", "4:32"),
ModelSongInfo(3L, "Aurora", "Awakening", "6:51"),
ModelSongInfo(4L, "Aurora", "All Is Gentle Inside", "3:54"),
ModelSongInfo(5L, "Aurora", "Queendom", "5:47"),
)
Moreover Checklist
there’s a drawback with SectionSelector
perform to show tabs:
restartable scheme("[androidx.compose.ui.UiComposable]") enjoyable SectionSelector(
secure modifier: Modifier? = @static Companion
unstable sections: Assortment
unstable choice: Part
secure onClick: Function1? = @static { it: Part -> }
)
Let’s take a better take a look at its implementation:
@Composable
enjoyable SectionSelector(modifier: Modifier = Modifier, onSelect: (Part) -> Unit = {}) {
val sectionTitles = bear in mind {
listOf(
Part("Feedback"),
Part("Fashionable"),
)
}
var currentSelection by bear in mind { mutableStateOf(sectionTitles.final()) }
SectionSelector(
modifier = modifier,
sections = sectionTitles,
choice = currentSelection,
onClick = { choice ->
if (choice != currentSelection) {
currentSelection = choice
onSelect(choice)
}
}
)
}
We see that the perform is only a wrapper containing the checklist and the present tab choice logic. We are able to take away this performance by transferring these elements to the subsequent perform within the hierarchy and thus take away the non-skippable performance.
Let’s accumulate compiler statistics once more. Now, there are not any restartable however skippable synthesizers, which is nice!
{
"skippableComposables": 155,
"restartableComposables": 155,
"readonlyComposables": 0,
"totalComposables": 167,
}
dynamic expression
One other factor we have to take note of is the default parameter expressions @dynamic
.
The doc converse:
The default expression needs to be
@static
in all instances besides the next two instances:* You might be explicitly studying an observable volatility. Native elements and state variables are an vital instance of this. In these instances, you want to depend on the truth that the default expression will probably be re-executed when the worth modifications.
* You might be explicitly calling a composable perform similar to
bear in mind
. The most typical use case for that is state hoisting.
The most typical means you’ll encounter the primary case is MaterialTheme as default in your composables. Right here is an instance — spotlight the colour parameter @dynamic
is regular on this case.
restartable skippable scheme("[androidx.compose.ui.UiComposable, [androidx.compose.ui.UiComposable]]") enjoyable RoundedCornersSurface(
secure modifier: Modifier? = @static Companion
secure topPadding: Dp = @static 0.dp
secure elevation: Dp = @static 0.dp
secure shade: Colour = @dynamic MaterialTheme.colours.floor
secure content material: @[ExtensionFunctionType] Function3
)
The second case is to make use of bear in mind
. In our case, we bear in mind
state of scroll:
restartable skippable scheme("[androidx.compose.ui.UiComposable]") enjoyable CommentsList(
secure modifier: Modifier? = @static Companion
secure scrollState: ScrollState? = @dynamic rememberScrollState(0, $composer, 0, 0b0001)
secure feedback: ImmutableList
secure onActionClick: Function1? = @static { it: Motion -> }
secure topPadding: Dp = @static 0.dp
)
In all different instances when the default parameter is marked with @dynamic
it is best to attempt to take away the defaults or use @Steady
be aware. For instance, in NowPlayingAlbumScreen
parameter perform insets
marked as @dynamic
.
@Composable
enjoyable NowPlayingAlbumScreen(
insets: DpInsets = DpInsets.Zero,
)
Please annotate it with @Steady
to indicate the compiler that this parameter is secure by default.
companion object {
@Steady
enjoyable from(topInset: Dp, bottomInset: Dp) = DpInsets(DpSize(topInset, bottomInset))
+ @Steady
val Zero: DpInsets get() = DpInsets(DpSize.Zero)
}
One other case on this venture was specifying a default parameter to reorder a perform as a unit. On this case, we simply eliminated the default parameter.
@Composable
@Steady
enjoyable rememberCollapsingHeaderState(
- key: Any = Unit,
+ key: Any,
topInset: Dp
) = bear in mind(key1 = key) {
CollapsingHeaderState(topInset = topInset)
}
Nevertheless, one other case occurred when a default parameter was specified as one other default parameter from the identical perform.
@Composable
enjoyable AnimatedVolumeLevelBar(
modifier: Modifier = Modifier,
barWidth: Dp = 2.dp,
- gapWidth: Dp = barWidth,
+ gapWidth: Dp = 2.dp,
barColor: Colour = MaterialTheme.colours.onPrimary,
isAnimating: Boolean = false,
)
Conclusion
The results of all this work is that every one restartable features are skippable, all courses within the report file are secure, and we have eliminated the default parameter expressions of @dynamic
when not wanted. The profiler does not present extra jink frames when working the app.
This is a earlier than and after chart of the time it took to attract every body. The improved implementation by no means exceeds the exhausting restrict time to render a body, with a sane margin remaining.
Earlier than:
Afterward:
This text concludes the miniseries “Copying Dribbling in Jetpack Compose” (Half 1, Half 2, Half 3, Half 4). As you may see, utilizing Jetpack Compose you may create complicated screens and animations utilizing declarative method. It might be totally different from what you might be used to with AndroidView, however it’s nonetheless a robust software and can turn into increasingly common sooner or later.
The repo comprises the complete implementation. You’ll want to take a look at our weblog for extra Jetpack Compose and iOS SwiftUI tutorials and open supply libraries!
John Wick: Chapter 4 (FREE) FULLMOVIE The Super Mario Bros Movie avatar 2 Where To Watch Creed 3 Free At Home Knock at the Cabin (2023) FullMovie Where To Watch Ant-Man 3 and the Wasp: Quantumania Cocaine Bear 2023 (FullMovie) Scream 6 Full Movie
Supply hyperlink