https://kotlinlang.org logo
#compose-android
Title
# compose-android
t

Travis Griggs

10/11/2023, 9:01 PM
I was doing some good old fashion println debugging, and noticed that in some state (that I haven't figured out yet), I get endless compose updates in one of my Canvas compositions. It's like there's a cyclic update or something. This has led me to wonder if there are caveats on what kinds of things you can/should put in a
remember { mutableStateOf() }
thing. For example, in one place I have a
var visibleInterval by remember { mutableStateOf<ClosedRange<Duration>> }
and in another a
var selections by remember { mutableStateOf<Set<Int>>(emptySet()) }
. Are either of these expressions problem prone?
b

Ben Trengrove [G]

10/11/2023, 9:12 PM
I can't spot anything wrong with those statements, have you perhaps done a backwards write? https://developer.android.com/jetpack/compose/performance/bestpractices#avoid-backwards
t

Travis Griggs

10/11/2023, 9:42 PM
I don't think I am 🙂 My top level code just looks like:
Copy code
var selections by remember { mutableStateOf<Set<Int>>(emptySet()) }
// LaunchedEffect(selections) {
//    selections.logged("SELECTIONS CHANGED")
// }
   SpansView(
      plan = PlansRepo.rapidPrecisionPlan(),
      selectAction = { op, arg -> selections = op.update(selections, arg) },
      selections = selections,
      modifier = Modifier.fillMaxSize()
   )
}
As soon as that selectAction fires, the SpansView starts repeatedly composing. But the selectAction itself is not repeteadly firing.
p

Pablichjenkov

10/11/2023, 9:42 PM
Check Inside the effects, if you are not triggering changes that causes State to change and so on. Happens to my once in some code that I had in the composable function scope rather than in the DisposableEffect {} block
t

Travis Griggs

10/11/2023, 9:46 PM
Yeah, I don't have any other effects other than the one shown as commented out there
p

Pablichjenkov

10/11/2023, 9:48 PM
I see, well start removing code chunks until you find the culprit
b

Ben Trengrove [G]

10/11/2023, 9:49 PM
You might be able to see what's changing by putting a breakpoint in the canvas and checking the "Recomposition State"
p

Pablichjenkov

10/11/2023, 9:50 PM
I see the call to rapidPrecissionPlan() happens in a the Composable scope. Is the only suspicious thing
t

Travis Griggs

10/11/2023, 9:54 PM
it's just my preview
wow. i've never thrown a breakpoint in one of these, there's lots of stuff in here:
p

Pablichjenkov

10/11/2023, 10:19 PM
Interesting 🤔 Wonder what changed and dirty means
t

Travis Griggs

10/11/2023, 10:20 PM
Sigh. I should have @Pablichjenkov right away. It didn't don on me that my top level Preview would be recomposing as well. I hadn't checked that. But alas, it was. Which means it was fetching a new instance of the PlansRepo.rapidPrecisionPlan(). Paying attention to the SpansView stack on each rerun, I noticed that the Plan object address was changing! So I was getting a new one. Since it has mutableStates down in there, that must have been triggering recompositions? Fixing it was even weirder. My first though was, "oh, i guess since the remember selections is right here, this too (re)composes, not just the functions that are using it. So I'll isolate those:
Copy code
private fun SpanViewPreview() {
   val plan = PlansRepo.rapidPrecisionPlan()
   Box(modifier = Modifier.fillMaxSize()) {
      var selections by remember { mutableStateOf<Set<Int>>(emptySet()) }
      SpansView(
         plan = plan,
         selectAction = { op, arg -> selections = op.update(selections, arg) },
         selections = selections,
         modifier = Modifier.fillMaxSize()
      )
   }
}
but alas, this had no effect. Then, I remembered that Box is an inline function, so no real no composition nesting is actually happening there. So changing it to
Copy code
private fun SpanViewPreview() {
   val plan = PlansRepo.rapidPrecisionPlan()
   Surface(modifier = Modifier.fillMaxSize()) {
      var selections by remember { mutableStateOf<Set<Int>>(emptySet()) }
      SpansView(
         plan = plan,
         selectAction = { op, arg -> selections = op.update(selections, arg) },
         selections = selections,
         modifier = Modifier.fillMaxSize()
      )
   }
}
suddenly works. Because, for the time being, apparently, Surface is not an inline function for its content.
p

Pablichjenkov

10/11/2023, 10:23 PM
You can use a LaunchEffect or a produceState effect I guess.
The idea is taking the call out of the Composable execution scope
t

Travis Griggs

10/11/2023, 10:27 PM
That PlansRepo.rapidPrecisionPlan() produces equal (but not identical) instances. If I implemented hash/equal for Plan such that the hash equality was stable, even though they're different instances, would that have headed this issue off?
the answer to that appears to be no
All I really needed to do was wrap a remember { } around that Plans.rapidPrecisionPlan().
👍 1
Lessons learned here: • I seem to get in trouble when I "remember" too many things. But sometimes, it's really the right thing to do • Remember that even the Preview function follows the same composable rules. Despite putting the annotation there, I think my brain was seeing it as a top level function that only ran once.
👆 2
p

Pablichjenkov

10/11/2023, 10:50 PM
That is the trick, don't write code in composable functions that is not Composable. Used effects for that, or remember. And last but not least, it is a good practice to remember with keys if you want it to get updated with the input params. Otherwise, it will keep the value from the previous composition. It probably doesn't apply to you since you are using a singleton but just in case
b

Ben Trengrove [G]

10/12/2023, 1:03 AM
"Wonder what changed and dirty means" changed and dirty are bitmasks that are part of the code the compose compiler is generating, it's what it is using to determine whether to skip or not. The recomposition state part of the debugger is parsing those bitmasks for you to something more readable
👍 2
👍🏾 1
Oh I just noticed you don't have the compose debugger, if you use a newer Android Studio it should show up for you.

https://www.youtube.com/watch?v=Kp-aiSU8qCU

💯 2
thank you color 1