Promise compose compiler and imply when you'll change 🤞

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 as val.
  • 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 contains val 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! 😀


📚 References

Did you find this article valuable?

Support Shreyas Patil by becoming a sponsor. Any amount is appreciated!