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

Tolriq

02/19/2022, 3:37 PM
Starting to become mad at debugging state recomposition. I'm using `
Copy code
val playlistEntriesItems = remember(playlistEntries) {
    Log.e("AAA", "Update entries")
    playlistEntries.toMutableStateList()
}
Then use that state list in a LazyColumn. All works correctly and change to items order properly triggers recomposition. If that function is called again with a new playlistEntries the state is properly updated, but then the LazyColumn no more react on the changes. Am I missing something obvious?
Ok so using the same snapshot does work :
Copy code
val playlistEntriesItems = remember { mutableListOf<PlaylistEntry>().toMutableStateList() }

    val refresher = remember(playlistEntries) {
        Log.e("AAA", "Update entries")
        playlistEntriesItems.clear()
        playlistEntriesItems.addAll(playlistEntries)
        1
    }
@Adam Powell sorry to ping but since you are here and talking about that, is this something expected? Should not the LazyColumn properly see that's it's another state and reset whatever it needs to understand the change ?
a

Adam Powell

02/19/2022, 4:33 PM
I think the code where the problem started is probably missing from the OP here. When you create a new mutable state list you're forking the source of truth you're referring to. If you're making changes to that list, they're erased whenever
playlistEntries
changes, since the
remember
will create a new list from
playlistEntries
as the source, dropping any changes that may have happened to the mutable state list
but in both examples it looks like you're trying to perform bidirectional syncing between an upstream source of truth (
playlistEntries
) and a local mutable copy, which is adding a lot of complexity you probably don't need or want.
t

Tolriq

02/19/2022, 4:37 PM
The purpose is reorderable items by drag and drop. In this case I've triple checked and composition due to the change of first source of trust only occurs when the source list actually change.
I don't understand why the second case work and the first one only works once.
So to resume. That function is called with a list of items the list of item does not change and the remember is not called when not wanted. Internally the mutablestate is used to track and display the drag and drop reordering and everything is fine and working. The function notify the caller of the result. Then the source list is updated and the function is recomposed with the new list. Everything works as expected with proper recomposition counts.
But only once for the first case, and every time in the second case.
a

Adam Powell

02/19/2022, 4:49 PM
I'd venture a guess that somewhere you have a lambda capturing the instance of
playlistEntriesItems
that doesn't update if the actual list instance is recreated
the second one works since you're using the same list instance
t

Tolriq

02/19/2022, 4:55 PM
Updating the list does properly trigger a recomposition , that part always works. What is not working is modifying the list state content by moving items in it.
It's like the second tomutablestatelist is not seen as a state by lazycolumn on the second creation.
For the record the full function that works :
Copy code
@Composable
fun PlaylistEntryList(
    lazyListState: LazyListState,
    reorder: Boolean,
    header: @Composable () -> Unit,
    playlistEntries: List<PlaylistEntry>,
    onMoreClick: ((PlaylistEntry, Int) -> Unit)? = null,
    onItemDragged: ((from: Int, to: Int) -> Unit)? = null,
    onClick: (PlaylistEntry, Int) -> Unit,
) {
    val reorderState = rememberReorderState(listState = lazyListState)
    val playlistEntriesItems = remember { mutableListOf<PlaylistEntry>().toMutableStateList() }

    val refresher = remember(playlistEntries) {
        playlistEntriesItems.clear()
        playlistEntriesItems.addAll(playlistEntries)
    }
    @Suppress("UNUSED_EXPRESSION") refresher
    LazyColumn(
        state = reorderState.listState,
        contentPadding = PaddingValues(bottom = 8.dp),
        modifier = Modifier.reorderable(state = reorderState,
            onMove = { from, to -> runCatching { playlistEntriesItems.move(from.index - 1, to.index - 1) } },
            onDragEnd = { from, to -> onItemDragged?.invoke(from - 1, to - 1) })
    ) {
        item {
            header()
        }
        itemsIndexed(items = playlistEntriesItems, key = { _, item -> item.id }) { index, item ->
            if (onMoreClick == null) {
                PlaylistEntryListEntry(item, reorder = reorder, reorderState = reorderState, onMoreClick = null, onRowClick = { onClick(item, index) })
            } else {
                PlaylistEntryListEntry(item, reorder = reorder, reorderState = reorderState, onMoreClick = { onMoreClick(item, index) }, onRowClick = { onClick(item, index) })
            }
        }
    }
}
3 Views