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

Se7eN

01/07/2021, 1:17 PM
Copy code
@Composable
fun Main() {
    val state = remember { PaletteState(...) }
    Scaffold(
        topBar = { TopBar(onSave = { viewModel.save(state.toPalette()) } }
    ) {
        ColorWheel(state)
    }
}

@Composable
fun ColorWheel(state: PaletteState) {
    val colorWheel = rememeber { ColorWheel(...) }
    state.colors = colorWheel.getColors()

    Content(colorWheel)
}
I have a
state
that needs to be updated by a child composable. Is this the right way to do it?
s

sindrenm

01/07/2021, 1:23 PM
I think I would rather pass in a callback to the child (
onColorSelected: (Color) -> Unit
or something) to the child.
💯 2
👆 1
l

Lukas Sztefek

01/07/2021, 1:23 PM
Better pass just a lambda into the child component:
Copy code
fun ColorWheel(onPalletteChanged: (PaletteState) -> Unit) {
s

Se7eN

01/07/2021, 1:26 PM
I could do that but the main concern is that I don't really have a way to get the initial colors of the color wheel. What can I do about that?
And calling
onPaletteChanged()
or
state.colors = colorWheel.getColors()
without a callback doesn't seem right
l

Lukas Sztefek

01/07/2021, 1:38 PM
Pass current colors as an parameter to the child component?
s

Se7eN

01/07/2021, 1:39 PM
The child is responsible for initializing the palette using a color scheme
l

Lukas Sztefek

01/07/2021, 1:47 PM
You’re saying
I don't really have a way to get the initial colors of the color wheel
but also
The child is responsible for initializing the palette
. This statements are going against each other aren’t they? Colors != palette?
s

Se7eN

01/07/2021, 1:52 PM
Like the child creates an object which initializes the palette and stores the drag positions of the color picker. Then there's a drag modifier where I can call onPaletteChanged. But I don't know where to call onPaletteChanged for the initial colors. Do I call onPaletteChanged in the
init { }
of the class that is responsible for initalizing the palette?
And yeah the palette is just a List<Color>
j

jim

01/07/2021, 2:08 PM
You've got your state ownership inverted. The
ColorWheel
should not be the one who controls the initial colors; if the parent needs to maintain the current color, then they should own that state, including owning the initial color.
It is a little hard to understand your code snippets above without being able to see into the types of your data structures, but the important thing is that the parent controls the child. Whatever the parent chooses as the initial color is the one that happens to be selected when the ColorWheel is first opened, but the app owns the data that the child renders. Your result should probably look something more like one of the following:
Copy code
fun ColorWheel(palette: List<Color>, currentColor: Color, onColorSelected: (Color) -> Unit) {
   ...
}
Or:
Copy code
fun ColorWheel(palette: List<Color>, onPaletteSelected: (List<Color>) -> Unit) {
   ...
}
Or whatever it is exactly your widget is allowing you to select. The key is that the child is just rendering the state the parent passes down, and then informing the parent when the child thinks that state should be changed. The parent owns the data. When first getting started with Compose, it's often useful to ask yourself "How would I do this if I were only permitted to use immutable data structures", as that will often lead you to the proper data flow.
👍 2
s

Se7eN

01/07/2021, 2:20 PM
I'm generating the palette (list of colors) using a color scheme and a few other parameters.
Copy code
class ColorWheel(hueDistance, numColors, ...) {
    // Initialize the list of colors using hueDistance and numColors and a few other parameters
}
That's why I can't pass any initial color list to the color wheel. You think I should do the initial palette calculation outside the
ColorWheel
?
l

Lukas Sztefek

01/07/2021, 2:21 PM
Jim was first ⬆️, but it this might help you understand how to deal with lambdas:
Copy code
class PaletteState(
    var colors: List<Color>
)

@Composable
fun Main() {
    val paletteState = remember { PaletteState(listOf(Color.Red, Color.Blue)) }

    ColorWheel(
        currentColors = paletteState.colors,
        onColorsChanged = { paletteState.colors = it }
    )
}

@Composable
fun ColorWheel(currentColors: List<Color>, onColorsChanged: (List<Color>) -> Unit) {
    Content(
        currentColors = currentColors,
        onDrag = { selectedColors -> onColorsChanged(selectedColors) }
    )
}
s

Se7eN

01/07/2021, 2:26 PM
Yep I know lambdas but that wasn't my question. I didn't phrase it correctly
l

Lukas Sztefek

01/07/2021, 2:33 PM
Oh I now understand what do you want to achieve. You want to “extract” computed colors from ColorWheel on save action. I see no other option then compute & hold colors in Main composable and pass it to ColorWheel.
j

jim

01/07/2021, 2:33 PM
Yes, you can hoist the initial palette calculation outside the 
ColorWheel
, perhaps into something like
PaletteState
or
ColorWheelState
, but keep these things immutable and just re-create them any time one of the parameters changes. That will keep your data flow correct.
s

Se7eN

01/07/2021, 2:35 PM
Yep got it. I didn't think about this when writing the ColorWheel class. Guess I gotta refactor it to fit the compose way. Thanks for the help guys!