Yannick
Yannick

Reputation: 5912

Jetpack Compose Performance Issue that only occurs in multi module project

I have developed a quite complex keyboard layout in Jetpack Compose. Initially, the layout works fine and animations run very smoothly when pressign the keys. Unfortunately, after a few recompositions due to events the animations and everything becomes very slow and laggy. I have tried to sample Jave Method calls and I can only see that some function take a lot longer than usual.

Edit: I have been able to isolate the issue. I have discovered that the performance issue only appears in multi module projects. When I put the exact same compose code in the root app module the issue disappears.

I have created a project that reproduces the issue:

If you want to reproduce the issue in the multi module project you have to switch between the categories multiple times and then everything becomes very laggy

Example Video

Note: I know Jetpack Compose is still in alpha and this issue might be a bug in Jetpack Compose. But I want to make sure that it's not a bug of my code or a general limitation of Compose

Upvotes: 9

Views: 9344

Answers (4)

EpicPandaForce
EpicPandaForce

Reputation: 81568

The cause in a multi-module project is that the Compose Compiler must be executed over your data classes and models in order to calculate their "argument stability metrics".

These are automatically generated by the Compose Compiler. If your module did NOT have the Compose Compiler execute on it (you didn't run Compose on your module) then your "immutable"-seeming data classes will be considered unstable by the Compose Compiler, which in turn makes it so that any @Composable function that ever gets recomposed and gets one of these "unstable" arguments will be re-evaluated (non-skippable, restartable).

Once you add these up, your Composables can re-render the entire UI on every frame. That's when you need to consider @Stable deferred read state holders, running the Compose Compiler on your modules including the "non-UI" modules so that you have the proper Compose Stability Metrics, and just make sure you add remember {} (so either pass viewModel::login or = remember {{ viewModel.login() }}) for lambdas you might be passing that have unstable arguments. This can change performance for the positive drastically.

Upvotes: 0

Skaldebane
Skaldebane

Reputation: 97

I've had the same issue (and I have a single-module project) with LazyColumn and LazyVerticalGrid, which were horribly slow and laggy to the point where I lost faith in Jetpack Compose's promise.

However, it turned out this was all because of Jetpack Compose's internal workings in the debug build type, as there's a lot going on under the hood to allow the direct editing of literals and debugging of composables.

The solution was basically to build a release version of the app. This optimizes the code and disables any debugging features, making my app run lag-free, smoothly and rapidly.

Shout out to this reddit comment for suggesting this.

Upvotes: 1

Haris
Haris

Reputation: 4230

The solution is to use remember composable for Keyboard @Composable to prevent layouts computation during recomposition.

val refs: List<List<Pair<Key, ConstrainedLayoutReference>>> =
         remember {
             keyboard.map { row ->
                row.map {
                   it to createRef()
               }
          }
}

val modifier = remember { ... }

val modifierPressed = remember { ... }        

Source code: https://github.com/dautovicharis/example_compose-keyboard-multimodule/commits/main

Upvotes: 5

Ryan M
Ryan M

Reputation: 20167

In general, there's nothing special about it being a Jetpack Compose app. You can use the CPU profiler in Android Studio and record a trace to see more specifically what's happening within your app. It's hard to know what is causing some functions to take longer than usual without seeing the code, but it might actually be that they're getting called repeatedly (sampling methods can't tell the difference).

You can also put print statements or breakpoints in your @Composable functions. Make sure that the functions aren't getting called (recomposed) when you're not expecting them to. If they are, you'll want to investigate what's causing them to get called again.

Upvotes: 2

Related Questions