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

Fudge

07/20/2020, 7:47 PM
Consider the following code
Copy code
Column {
        var on by state { true }

        Button(onClick = { on = !on }) {
            Text(text = "Toggle", style = textStyle.h1)
        }

        val list = @Composable {
            LazyColumnItems(items = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)) {
                Text(text = it.toString(), style = textStyle.h1)
            }
        }
        if (on) {
            list()
        } else {
            list()
        }
    }
Here we have a scrolling list, with a button above it, that seemingly, does nothing. However, it doesn't - it resets the current scroll progress in the scrolling list. I have a feeling this is the intended behavior (unfortunately). I have a less trivial case where the same list is being used at multiple places, but it loses its scroll progress when it gets invoked through a different branch of code. It might be possible to somehow structure the code such that only one invocation occurs, but I find that difficult. Is there some way to make compose understand this is the same list?
r

romainguy

07/20/2020, 7:55 PM
@Leland Richardson [G] Is that something that can be addressed with
key()
or am I incredibly mistaken here?
f

Fudge

07/20/2020, 8:05 PM
I can't think of a way to use that to solve this myself
j

jim

07/20/2020, 8:08 PM
No, key won't help you here. What you need to do is to pull the call to list up out of the conditional branch or hoist the state up.
In this particular case, it's trivial, since there are no parameters. In a general case, you're probably working with some code with parameters, so you'll need to do something like:
Copy code
val myValue = if(on) ... else ...
list(myValue)
The other option is to host the state up. This is particularly common/important approach if you need to change the parent (and thus can't pull the call up). Example:
Copy code
val myState = remember { SomeState() }
if(on) list(myState)
else list(myState)
Then you store things like the scroll position into myState, and then it doesn't matter if you are in a different branch because the list function is a pure function of its inputs and thus isn't stateful so no state is lost.
f

Fudge

07/20/2020, 8:17 PM
Then you store things like the scroll position into myState
how can I store this if LazyColumnItems does not provide it?
j

jim

07/20/2020, 8:25 PM
You can't, unfortunately. That's why it's so important that all state be hoistable and why having truly private state (not hoisted) is such a bad practice. IMO,
LazyColumnItems
should expose scroll position, probably as a hoisted state object; please feel free to file a bug on
LazyColumnItems
(please be gentle on us though, keep in mind that this widget is still under active development/iteration and this API is not yet done)
4
f

Fudge

07/20/2020, 8:30 PM
I feel like I only need to get the column state because of the deeper problem, where I can't execute the same composable into different code paths. Why should I be forced to take care of
LazyColumnItems
's state? Imagine if the component had 20 different fields (or state variables, rather) that it uses to manage its state, do I then need to pull them up into my component and store them so the component I am using doesn't forget everything?
And not only does
LazyColumnItems
need to provide api to expose its state to me, it also needs parameters for me to feed my state into it (that I got from said component).
j

jim

07/20/2020, 8:34 PM
Hoisted state is generally stored recursively, such that a state object may store the state of all its children, and those children store the state of all their children. Using this pattern, a caller of a widget never needs more than one hosted-state parameter for that widget.
l

Leland Richardson [G]

07/20/2020, 8:35 PM
yeah if it’s just the scroll state that is the problem here, i think hoisting lazycolumnitem’s scroll position and using it in both branches seems like a good thing to do. if you actually want to preserve all state underneath it, i would need to understand more about the use case. we have thrown around ideas of “reparenting” APIs and similar that would allow you to do that but there’s a good chance your use case wouldn’t need you to go that far
a

Andrey Kulikov

07/20/2020, 8:45 PM
hoisting the scroll position of
LazyColumnItems
is one of my next plans, so should be available at some point
👍 6
f

Fudge

07/20/2020, 8:46 PM
The use case is something i've mentioned before, It's swiping between a list of components. As a primitive for that I have a component that is a
SplitView
, it shows 2 components side by side,
left
, and
right
. When swiping from left to right, the
left
component is the previous component, and the
right
component is the current component
. When swiping from right to left*,* the
left
component is the current component,
and the
right
component is the next component. In this case, the same component is represented as different arguments to the same method, so in the case of a scrolling list it would lose its scroll position when swiping.