https://kotlinlang.org logo
#compose
Title
# compose
a

Aaron Waller

10/04/2022, 6:17 AM
Hey, I heard Flows are very bad for the performance of Jetpack Compose due to the fact that they are not stable. My whole app is using flows, what else should I use? (MVVM)
🤔 6
c

czuckie

10/04/2022, 7:31 AM
do you have a reference for this? (Blog post/stackoverflow). Main thing everyone will tell you is: measure it. I think any tool when used wrong might bring about some disadvantages, but I'm struggling to imagine a world where using a flow over any other kind of concurrency mechanism would bring noticeable performance issues, but I'd love to learn more.
z

Zoltan Demant

10/04/2022, 8:18 AM
Are you referring to whats mentioned in this article?
If thats the case. I wouldnt say that them being unstable is by itself a problem. Are you passing
Flow<T>
(or subclasses) to a bunch of composable functions in your codebase? In terms of skippability, its identical to passing
List<T>
(which is also unstable). This article has awesome details about skippability and how you can find out if your composables are skippable or not 🙂
a

Aaron Waller

10/04/2022, 8:27 AM
I am referring this article: https://proandroiddev.com/6-jetpack-compose-guidelines-to-optimize-your-app-performance-be18533721f9 “Even though they might seem stable since they are observable, `Flow`s do not notify composition when they emit new values. This makes them inherently unstable. Use them only if absolutely necessary.”
z

Zoltan Demant

10/04/2022, 8:42 AM
Im not sure what thats supposed to mean.
Flow.collectAsState()
does exactly that. Maybe drop a comment on the article about it?
q

qlitzler

10/04/2022, 9:11 AM
In terms of skippability, its identical to passing
List<T>
(which is also unstable).
Just a note about this: You can use kotlin collections
ImmutableList<T>
to male it
stable
. It’s not much of a hassle and it works well.
b

Ben Trengrove [G]

10/04/2022, 4:15 PM
While Flows are unstable, that doesn't make them bad for performance. If you had a composable like
Copy code
@Composable
fun MyComp(viewState: Flow<ViewState>) {
   val state = viewState.collectAsState()
   LazyColumn {
      items(state.items) {}
   }
}
Sure, MyComp wouldn't be skippable but that doesn't matter as it's not the bit of state that is changing. The bit that is recomposing is the list, you would be more concerned about the stability of your items class as that will determine if your whole LazyList recomposes or just the row. +1 on measuring these things, not just blindly following best practices. We always say "tools not rules" when it comes to performance. You can write a benchmark to measure the performance and then test out that your changes are actually helping. https://medium.com/androiddevelopers/inspecting-performance-95b76477a3d7 I would also add my article on Stability to the list of reading. https://medium.com/androiddevelopers/jetpack-compose-stability-explained-79c10db270c8
d

Daniele Segato

10/04/2022, 5:03 PM
@Ben Trengrove [G] are you saying that using an unstable List is fine (for performance) even in a LazyList as long as its items are stable?
q

qlitzler

10/04/2022, 5:06 PM
This short thread might help to get a picture of what can be done with unstable classes and the
@Stable / @Immutable
annotations
Not answering instead of Ben, but in short, if you want to maximise performance, you should indicate the compiler that the
List
is stable. Either by using the annotations, or by using immutable collections. This way the
LazyList
can ke skipped during the recomposition phase. Example:
Copy code
@Composable
fun ImmutableList<T>.Compose() {
   LazyList {
     ...
   }
}
⬆️ Optimised (skippable)
Copy code
@Composable
fun List<T>.Compose() {
   LazyList {
     ...
   }
}
⬆️ Unoptimised (not skippable)
d

Daniele Segato

10/04/2022, 5:17 PM
Thanks @qlitzler but this isn't quite what i was asking. I'm aware of Stable and Immutable annotation and what they do. I'm specifically asking when it doesn't matter if your composable isn't skipped. In this thread they were talking about Flow but then the example had a state.items. and Ben was saying that the important but is that the item state is stable.
q

qlitzler

10/04/2022, 5:23 PM
Ok, got you. I would say that it matters for performance to maximise the skipability, hence the stability, of your compose methods when possible Having a
@Compose
method which depends on a
Flow<>
, for example with
flow.collectAsState()
, would not be stable and hence skippable, but that’s ok.
Copy code
data class StableItem(val string: string)

// Unskippable, but it doesn't matter performance wise
@Composable
fun Flow<ImmutableList<StableItem>>.Screen() {
   val list = collectAsState()
  
   list.value.List()
}

// Skippable
@Composable
fun ImmutableList<StableItem>.List() {
  LazyList {
    items(this) { it.Cell() }
  }
}

// Skippable
@Composable
fun StableItem.Cell() {
  Text(string)
}
b

Ben Trengrove [G]

10/04/2022, 5:25 PM
It depends, you should measure it. But you would probably want List to be immutable and the items the list has to be immutable as well. What most likely doesn't matter is the composable that takes in the Flow. Stability is what allows composables to be skipped if none of their inputs have changed, so only matters when composables above them are recomposing.
d

Daniele Segato

10/04/2022, 5:26 PM
Sure but when it's ok for a Composable to not be skippable and why? My understanding is that if your composable has no UI (just call other composables) it's ok if it isn't skipped.
b

Ben Trengrove [G]

10/04/2022, 5:28 PM
You can't just make a blanket statement about when it is or isn't, it greatly depends on how expensive that composable is. But above, I am trying to say if a composable is never evaluated to be skipped because something above it never recomposes or it happens very rarely, then it doesn't matter if it's skippable or not
d

Daniele Segato

10/04/2022, 5:34 PM
Thanks. I'll have to build my own experience for when than.. cause i don't like passing lambda providers everywhere to delay reads :-) i want to do it only if it is actually needed
b

Ben Trengrove [G]

10/04/2022, 5:38 PM
I know it's easy to just say this but you should only ever do performance optimizations when proven needed. Write a benchmark, measure it and see if you are janking. If you are, then analyse where your bottlenecks are and fix. Just blindly applying tips leads to hard to understand and maintain code. Tools not rules.
d

Daniele Segato

10/04/2022, 5:41 PM
I agree with that. But i still think guidelines and rules build on experience are needed. Hunting performance issues is time consuming. If i can figure out upfront when i need to optimize and when i don't in most scenarios I'd rather follow some good practice rules :-)
q

qlitzler

10/04/2022, 5:45 PM
Hm. Though I do agree that writing benchmarks is essential, I don’t agree that you shouldn’t care about writing optimised code from the start. Performance issues usually are an accumulation of small mistakes over long period of time. If you have the prior knowledge on how to write optimised code, why not do it from the start ? Daniele, what I do personally is to write code with the compose compiler metric enabled. I make sure all my classes are stable and my method skippable, unless not possible (The Flow case for example) I don’t find that it creates harder code to write or less maintainable, on the contrary.
b

Ben Trengrove [G]

10/04/2022, 6:07 PM
Agreed, you definitely build up an instinct for this with experience about what to do from the start. Just be really careful with chasing skippability from the start, if incorrectly mark things as stable when they aren't you will end up in situations where your composables aren't recomposing and it will be hard to work out why
q

qlitzler

10/04/2022, 6:38 PM
Oh yeah definitely. I don’t mark things as @Stable explicitly except in a very few cases. I just let the compiler do the work, and checking the reports to confirm it’s going well. Also the layout inspector tracking recomposition count is amazing.
405 Views