Promise compose compiler and imply when you'll change 🤞
Let's improve the performance of your Jetpack Compose application
Hey Droid-folks and Composers 👋, this is a mini-blog where we'll discuss improving the performance of Composables by taking care and promising some things to the Compose Compiler.
Let's say you're composing your UI and the composable function looks like this 👇
@Composable
fun PersonInfo(person: Person) {
...
}
In this, let's assume you have a Person
UI model like this
class Person(
val name: String,
val email: String,
val mobileNumber: String,
val age: Int,
val skills: List<String>,
val organization: Organization
)
The above @Composable
function is responsible for showing Person
's information. Let's assume you've List<Person>
and you're showing it inside LazyRow
/ LazyColumn
. While implementing it, sometimes you may notice that there's something that is lagging on UI.
The function PersonInfo
will be recomposed whenever the parameter Person
is changed.
You can simply tell compose a compiler about when you'll change and optimize the performance, do you know?
🤔 How?
The compose runtime library has provided two annotations by which we can imply compose compiler about model's behavior. This affects the binary compatibility of code generated by the Compose compiler plugin by which compose does optimizations and it may help to improve the performance of your application.
1. @Immutable
Annotation
Immutable
can be used to mark a class that produces immutable instances. In short, a class that has all public fields declared asval
.- This is a promise to the compose compiler that once the object is constructed, the properties inside it won't change at runtime.
- Immutable is used by a composition that enables composition optimizations that can be performed based on the assumption that values read from the type will not change.
data class
that only containsval
properties that do not have custom getters can safely be marked as Immutable if the types of properties are either primitive types or also Immutable.
Let's revise our UI model
+ @Immutable
+ data class Person(
val name: String,
val email: String,
val mobileNumber: String,
val age: Int,
val skills: List<String>,
val organization: Organization
)
Here, after adding annotation @Immutable
we also have to make sure that the field organization
is of type Organization
should also be annotated with @Immutable
.
2. @Stable
Annotation
Stable is used to communicate guarantees to the compose compiler about how a certain type or function will behave. Stable annotation indicates that the function will return the same result if the same parameters are passed in. This is only meaningful if the parameters and results are themselves Stable, Immutable, or primitive.
When any class is annotated with @Stable
it promises:
- The result of
equals()
will always return the same result for the same two instances. - When a public property of the type changes, the composition will be notified.
- All public property types are stable.
The invariants that this annotation implies are used for optimizations by the compose compiler and have undefined behavior if the above assumptions are not met. As a result, we should not use this annotation unless they are certain that these conditions are satisfied.
This annotation has to be applied to a type that indicates a type that is mutable and may change during runtime after the instance is created, but the compose runtime will be notified about the change in public properties. It'll be only notified when the result of the previous invocation and change is different.
Let's say in our Person
model, we have a case where we want to support a change of organization at runtime. So want to make organization
a mutable field with a kind of State<Organization>
or MutableState<Organization>
which can be changed at any time. Also, since we are using data class
, we won't need to worry about overriding the equals()
method. But if you're not using a data class, make sure you override it.
+ @Stable
+ data class Person(
val name: String,
val email: String,
val mobileNumber: String,
val age: Int,
val skills: List<String>,
+ val organization: State<Organization>
)
In short, whenever we are exposing reactive types in the UI model, they should be annotated with @Stable
annotation.
🤔 Remember
Implementing such contract incorrectly for a type annotated as @Stable
or @Immutable
will result in incorrect behavior for @Composable
functions that accept that type as a parameter or receiver and it may lead to issues. So use it wisely and check if you're doing it in the right way.
That's all, now just do these changes and run your app and see if there are improvements 😃.
That's all, I hope you found this helpful.
"Sharing is caring"
Thank you! 😀