Compose Stable Marker
✒️ Compose stable markers were originated Compose runtime, which improves Compose performance by telling stable and skippable guarantees to the compose compiler from non-compose dependent modules. This library supports Kotlin Multiplatform.
Agenda
This library contains a few extracted Compose stable markers, such as Stable, Immutable, and StableMarker, which are used to communicate some guarantees to the compose compiler and mark class as producing immutable instances. You can utilize this library when you want to mark your properties/classes/functions with Stable or Immutable, which are from compose-runtime in your pure Kotlin module or non-compose dependent modules. So if you don't want to depend on the compose-runtime
library, but you still want to improve your Compose performance by marking your models as stable in multiple module structure, you can use this library. If you want to learn more about Skippable, Stable, and Immutable, check out 6 Jetpack Compose Guidelines to Optimize Your App Performance and Composable metrics by Chris Banes.
Download
Gradle
Add the compileOnly
dependency below to your module's build.gradle.kts
file:
dependencies {
compileOnly("com.github.skydoves:compose-stable-marker:1.0.3")
}
For Kotlin Multiplatform, add the compileOnly
dependency below to your module's build.gradle.kts
file:
sourceSets {
val commonMain by getting {
dependencies {
compileOnly("com.github.skydoves:compose-stable-marker:$version")
}
}
}
Stable
These hold data that is mutable but notify Composition upon mutating. This renders them stable since Composition is always informed of any changes to the state. This annotation implies are used for optimizations by the compose compiler if the assumptions below are met:
- The result of
equals
will always return the same result for the same two instances. - When a public property of the type changes, composition will be notified.
- All public property types are stable.
You can utilize the Stable annotation like a normal annotation in your pure Kotlin module or non-compose dependent modules.
Without Stable Annotation (Unskippable, and Unstable)
Let's assume that you have a normal class without the Stable
annotation:
// data module (pure Kotlin module)
public data class UnstableUser(
public val name: String,
public val devices: List<String>,
public val createdAt: Instant,
)
// feature module
@Composable
private fun UnstableUserFun(unstableUser: UnstableUser) {
Text(text = unstableUser.toString())
}
Following the Compose Compiler Metrics, you'll get the result below:
restartable scheme("[androidx.compose.ui.UiComposable]") fun UnstableUserFun(
unstable unstableUser: UnstableUser
)
As you can see in the above metrics, the UnstableUserFun
Composable function is not Skippable, and the UnstableUser
class is marked as unstable.
With Stable Annotation (Skippable, and Stable)
Let's assume that you have a normal class with the Stable
annotation:
// data module (pure Kotlin module)
@Stable
public data class StableUser(
public val name: String,
public val devices: List<String>,
public val createdAt: Instant,
)
// feature module
@Composable
private fun StableUserFun(stableUser: StableUser) {
Text(text = stableUser.toString())
}
Following the Compose Compiler Metrics, you'll get the result below:
restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun StableUserFun(
stable stableUser: StableUser
)
As you can see in the above metrics, the StableUserFun
Composable function is Skippable, and the StableUser
class is marked as stable. So your function will be completely skipped calling a function if the parameters haven't changed since the last call since all of your properties were marked as Stable.
Immutable
With the same approach of the Stable
annotation, you can use this annotation like a normal compose-runtime
's one. As the name suggests, these hold data that is immutable. Since the data never changes, Compose can treat this as stable data. Composition enables optimizations based on the assumption that values read from the type will not change, using this annotation.
Let's assume that you have a normal class with the Immutable
annotation:
// data module (pure Kotlin module)
@Immutable
public data class ImmutableUser constructor(
public val name: String,
public val devices: List<String>,
public val createdAt: Instant,
)
// feature module
@Composable
private fun ImmutableUserComposable(immutableUser: ImmutableUser) {
Text(text = immutableUser.toString())
}
Following the Compose Compiler Metrics, you'll get the result below:
restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun ImmutableUser(
stable immutableUser: ImmutableUser
)
StableMarker
An annotation marked as StableMarker
indicates a stable type, which obeys the following assumptions:
- The result of [equals] will always return the same result for the same two instances.
- When a public property of the type changes, composition will be notified.
- All public property types are stable.
License
Designed and developed by 2023 skydoves (Jaewoong Eum)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.