Is there any way to use the previous value of a re...
# compose
t
Is there any way to use the previous value of a remembered val when calculating the new value? To provide an example, I have an index that I want to keep track of using a MutableState, but when the data the index points to changes I'd like to make sure the index is still within bounds and coerce it into its range. I'd expect it to look something like this:
Copy code
var selectedIndex by remember(myList) { prev -> 
    // prev should be null on the first composition while 
    // it should be the previous remembered value on recompositions
    mutableStateOf(prev?.coerceIn(myList.indices) ?: 0) 
}
I currently do the following but this resets the index to 0 each time the list changes:
Copy code
var selectedIndex by remember(myList) { mutableStateOf(0) }
Does anyone know if this is possible?
e
if you split it up as
Copy code
var mutableSelectedIndex by remember { mutableStateOf(0) }
val selectedIndex = remember { derivedStateOf { mutableSelectedIndex.coerceIn(myList.indices) } }
(terrible naming aside, pick something more meaningful for your use case) read from the latter, write to the former, doesn't that work?
t
That mostly seems to do the trick! The only thing I've noticed is that the mutable version does not mirror the non-mutable version. For example, say the selected index is set to 3 and the list changes to a size of 2, mutableSelectedIndex will still be 3 while selectedIndex is 2. This isn't really a problem until we change our list again to be of size 3. Now the selectedIndex will also be 3 again without the user having changed the selection. This isn't a huge issue but it would be nice if I could keep them consistent.
e
hmm true. a side effect of
mutIndex = index
would resolve that but it's a bit annoying to have to do that in addition…
z
Trying to do this sort of thing is usually a code smell. It breaks unidirectional data flow and might behave weirdly since a composable should be a pure function and can be recomposed arbitrarily. Writing code that depends on exactly how many times it’s recomposed is going to be brittle at best.
Can you store the selection as an ID instead of an index?
Using index as identifier for things like selection can introduce other bugs too, e.g. if the order of the list changes
It sounds like what you want to do is update the selected set when the list contents change. Which suggests that the selection should be stored by whatever the source of truth for the list is, and so when the list changes you can explicitly clear the selection if you need to.
t
Thank you for the responses! To provide some more context, the list in question is a list of tabs (for a
TabRow
) where I want to keep track of the currently selected tab. When the screen orientation changes, the amount of tabs can also change due to some information being moved from the main view to an extra tab in portrait mode. What I'd hoped to achieve was to keep track of the selected tab index (which is necessary for the TabRow) without exceeding the bounds of the list of tabs, truncating/coercing the index when applicable. As you mentioned I could store an ID instead of the index, with the caveat being that when the amount of tabs changes from e.g. 3 to 2, and our selection is on the last tab, we no longer have enough information to move the selected element from the 3rd element to the 2nd element (as we don't know at what index the last selected tab was). In other words, I want to keep the selected tab as close as possible to the previous location should that tab no longer be available. Any thoughts? I'd love to do this properly but I can't really find a "clean" way to do this.
z
Wouldn’t it be a better UX to keep the same tab selected but scroll it into view?
t
I might be misunderstanding your answer, but the problem is that the same tab can't always remain selected. To sketch it out a bit more, We have a detail page that shows an image, a description section and finally a section with tabs in which you can find recommendations amongst other things. When the device is in portrait mode, parts of the description are truncated and moved from the main description into a separate tab to save space. If the device changes to landscape, this extra information is moved back into the main description section. This means that, depending on the orientation of the device, the amount of tabs can change. If the user has the tab with extra information selected and then rotates to landscape, that content is no longer present in any of the tabs which means that we have to change our currently selected tab to something else. Not sure if this clears things up but if it does not, feel free to drop this thread as I don't want to waste too much of your time.
z
Ah, gotcha, that makes more sense