Hi everyone! Can you explain me what is the differ...
# compose
d
Hi everyone! Can you explain me what is the different between the codes below ? And on LaunchedEffect block, if the key is changed during the block the LaunchedEffect is re launched again before the block is finished? @galex @Mor Amit
Copy code
LaunchedEffect(shouldLoadMore) {
        if (shouldLoadMore.value) {
            lastTotalItems = state.layoutInfo.totalItemsCount
            onEndReached()
        }
    }

=========================

 LaunchedEffect(shouldLoadMore) {
        snapshotFlow { shouldLoadMore.value }
            .collect { shouldLoadMore ->
                if (shouldLoadMore){
                    lastTotalItems = state.layoutInfo.totalItemsCount
                    onEndReached()
                }
            }
    }
s
shouldLoadMore
appears to be MutableState, so it will never restart the LaunchedEffect in the first one
LaunchedEffect(shouldLoadMore.value)
is probably what you want there
the second one uses snapshotFlow to observe the changes to
shouldLoadMore.value
inside the coroutine
d
Thnks you are right
so is the same?
Copy code
LaunchedEffect(shouldLoadMore.value) {
        if (shouldLoadMore.value) {
            lastTotalItems = state.layoutInfo.totalItemsCount
            onEndReached()
        }
    }
s
very similar, and for most cases they are the same
.collect
won't cancel previous when new emit() in flow
LaunchedEffect(keyChange)
will cancel previous when key changes
d
and what about my second question? “And on LaunchedEffect block, if the key is changed during the block the LaunchedEffect is re launched again before the block is finished?”
s
Uh it is cancelled, then restarted. Let me check the exact ordering
d
So if I want to make sure the block will be executed, I need to use flow (suspend function)
the new coroutine does not join previous before entering block
I'm suspicious of the goal of trying to avoid cancellation. It sounds likely you should check the UDF interaction here as you likely have some underhoisted state. But, yes, the flow version will not cancel and will allow you to respond to every state change. (edit: every state change that is not conflated and makes it to the flow observer)
d
for example: I want to implement pagination list. When the user get the end I want to execute api call to get more data. So I want to make sure is will be executed
Copy code
var lastTotalItems by remember { mutableStateOf(-1) }
    val shouldLoadMore = remember {
        derivedStateOf {
            val totalItems =  state.layoutInfo.totalItemsCount
            val lastVisibleItem = state.layoutInfo.visibleItemsInfo.lastOrNull() ?: return@derivedStateOf false
            (lastVisibleItem.index >= state.layoutInfo.totalItemsCount - 1 - buffer) && (lastTotalItems != totalItems)
        }
    }

    LaunchedEffect(shouldLoadMore) {
        snapshotFlow { shouldLoadMore.value }
            .collect { shouldLoadMore ->
                if (shouldLoadMore){
                    lastTotalItems = state.layoutInfo.totalItemsCount
                    onEndReached()
                }
            }
    }
s
Got it. Yes - in this case consider hoisting scroll position up to the same level that produces the items. Then wiring it up becomes a event->event instead of a state->event
d
Not sure Im understaning what you mean
s
so right now, you're writing to a state variable
shouldLoadMore
(this appears derived as a pure function from scroll position and items) which creates an event
onEndReached
If you hoist all of that to the thing that implements
onEndReached
then this problem becomes internal to that - and possibly outside of composition
TL;DR - I think you're doing a bit much in composition here, with what appears to be a mutableState driven state machine that generates events. You'll probably find this a lot easier if you move the states and logic into a Kotlin class.
ViewModel or other 🙂
d
Ya it much more easy. Something like that
Copy code
var viewModelsItems: ArrayList<Any> =object : ArrayList<Any>() {
        override fun get(index: Int): Any {
            val res = super.get(index)
            if (index + 20 >= size) {
                loadMore()
            }
            return res
        }
    }
s
yea that's in the general direction - you can also read mutableState in a viewModel using
snapshotFlow
A way I typically implement this is to keep track of the last viewed item in reactive state (mutableState, StateFlow, whatever) then if it's past some threshold start the next refresh in a loading loop
that way there's only ever one request out
retry logic is a downstream concern from refresh
d
Intresting. Something like: the viewmodel collect the visible indexes by flow and the composable function is the producer that publish it?
s
so ideally the composable function has two inputs 1. List of items 2. Complex hoisted state object (ScrollState) It generates no events related to refresh, but instead scrollState is modified (note this means you shouldn't multi-home the scroll state) The VM can then respond to scroll state changes and use that to start loading as appropriate.
d
@Sean McQuillan [G] Thanks a lot!