I'm using the code from this video to load some in...
# compose
c
I'm using the code from this video to load some info about items when they're shown on the screen. When I do this I get a warning from the IDE. Ideas on the correct way to do this?
s
(I totally did write that lint) Try ide suggestions ;)
c
lmao. thats awesome luck that i have then. here the result of applying the IDE suggestion
s
Practically, what will happen in the screenshot above is
getMoreInfo
will be called with the same items on each measure, so most likely you want a derived state
Leaves me with a broken code
Oh well, there's
.value
missing afterwards
c
lol
s
I was checking it on a bit different examples, but some space for improvement
c
was that all it was!?! omg. let me see.
s
You probably want to do the mapping inside the derive state, btw, to avoid recalculations as well
b
Do you actually need to use
visibleItems
in your composition?
snapshotFlow
might be better here, you could avoid composition altogether. Just move everything into the
LaunchedEffect
Similar to https://developer.android.com/jetpack/compose/side-effects#snapshotFlow
c
Interesting... i can look into that. With @shikasd tip my stuff works now, but its pretty laggy. I see in my logs that
Copy code
val visibleItems = remember { derivedStateOf { listState.layoutInfo } }.value.visibleItemsInfo.map {
  Log.e("TEST", "ITEM VISIBLE!")
  it.index
}
ITEM VISIBILE just keeps repeating forever... even when I'm not scrolling. Is that expected?
s
^ see my previous comment, you want to also map the values inside derived state, otherwise it doesn't diff anything
c
So
Copy code
val visibleItems = remember {
    derivedStateOf {
      listState.layoutInfo.visibleItemsInfo.map {
        Log.e("TEST", "ITEM VISIBLE!")
        it.index
      }
    }
  }
?
s
Yep, that :)
Editing code from the phone is mildly confusing
b
Copy code
LaunchedEffect(listState) {
   snapshotFlow { listState.layoutInfo.visibleItemsInfo }
      .map { it.index }
      .distinctUntilChanged()
      .collect { it.forEach { viewModel... } }
}
If you want to try snapshotFlow. It's a moot point if you are using visibleItems for something after the LaunchedEffect but if you are just using it to notify the viewModel then you can avoid the recomposition you'd be causing by switching to snapshotFlow
👌 1
c
Hm. I don't think that ended up working as expected Andrei. Here is my current code
Copy code
val visibleItems = remember {
  derivedStateOf {
    listState.layoutInfo.visibleItemsInfo.map {
      Log.e("TEST", "ITEM VISIBLE")
      it.index
    }
  }
}

LaunchedEffect(
  key1 = visibleItems, block = {
    visibleItems.value.forEach {
      Log.e("TEST", "Getting more info for: $it")
      viewModel.getMoreInfoFor(it)
    }
  })
ITEM VISIBLE is called like 6 times, but I only get more info for the first items on the screen (3 items). No updates while I scroll.
@Ben Trengrove [G] going for your solution I end up with this which I don't understand? I'll play around with both options a bit more.
b
Copy code
LaunchedEffect(listState) { 
        snapshotFlow { listState.layoutInfo.visibleItemsInfo.map { it.index } }
            .distinctUntilChanged()
            .collect {}
    }
Also just writing code in slack sorry, it's probably that instead
c
All good. I appreciate the help. Sometimes its just hard to judge whether the snippet was typod or if im just missing something basic here. That seems to compile. let me run it and see how it goes!
s
I do like the
snapshotFlow
solution more, but if you want to make derived state work, just make sure you read it in composition. You want
LaunchedEffect(visibleItems.value)
or just use
val visibleItems by remember { ... }
c
Intersting. Cool. I will play around with getting the derived state work just to learn. The snapshotFlow seems to work fine but its still super laggy. Adding some logging now and will try a release build. 🤞
lag is gone in release builds. 🦜
My final code for the snapShotFlow
Copy code
LaunchedEffect(listState) {
  snapshotFlow { listState.layoutInfo.visibleItemsInfo.map { it.index } }
    .distinctUntilChanged()
    .collect { indexes ->
      indexes.forEach {
        viewModel.getMoreInfoFor(it)
      }
    }
}
s
Does it start lagging only after adding snapshot flow?
b
Copy code
LaunchedEffect(listState) { 
        snapshotFlow { listState.layoutInfo.visibleItemsInfo }
            .distinctUntilChanged()
            .collect { 
               it.forEach {
                   viewMode.getMoreInfo(it.index)
               }
            }
    }
Probably slightly more performant at a guess? Nope def not
c
@Ben Trengrove [G] your message above was actually really laggy in a release build. which is weird. i too thought it would seem more performany. i thought maybe it was a fluke. but I reverted to the previous solution you gave and it had no lag in a release build.
b
Maybe visibleItemsInfo doesn't work well with the distinctUntilChanged?
🤷‍♂️ 1
c
I only fell down this rabbit whole because I thought the snippet of code in the video was all I was going to need. 😂
b
Hopefully you learnt something along the way!
💯 1
c
Copy code
val visibleItems = remember {
  derivedStateOf {
    listState.layoutInfo.visibleItemsInfo.map {
      Log.e("TEST", "ITEM VISIBLE")
      it.index
    }
  }
}

LaunchedEffect(
  key1 = visibleItems.value, block = {
    visibleItems.value.forEach {
      Log.e("TEST", "Getting more info for: $it")
      viewModel.getMoreInfoFor(it)
    }
  })
@shikasd this is the code that I have that works with "your" derivedState approach. This also does not lag in release builds 🦜 What I did find interesting though is that my column has 20 items total. 3 items fit on the screen typically... but ITEM VISIBLE shows up 60 times in the logs just when i scroll by 1px. Still seems like something inefficient is going on... but it doesn't actually seem to affect the perf since it has parity with one of the approaches suggested by ben.
b
Oh just realised visibleItemsInfo would change with every frame of the scroll so the distinct wouldn't be doing much. That's why the first code snippet was much faster
f
visibleItemsInfo
has a list of
LazyListItemInfo
which, among other things, has the item offset in pixels, so any scroll will trigger an update because the object has changed
👍 1
c
So it still seems like this would be the way to go?
Copy code
LaunchedEffect(listState) {
  snapshotFlow { listState.layoutInfo.visibleItemsInfo.map { it.index } }
    .distinctUntilChanged()
    .collect { indexes ->
      indexes.forEach {
        viewModel.getMoreInfoFor(it)
      }
    }
}
b
Yes
🙏 1
f
well, what do you need to know, when a new item shows? you could use that, or a derived state on a different property (like the index) that will not change on scroll unless a new item enters/exits the viewport
c
I'm looking to essentially lazily load more info for an item in the list as the item is displayed to the user.
p
using listState index to fetch data seems wrong anyway, if you add another item like a (eg: separator ) that would break your code
f
ok, you could use the
index
property in that case for the lazy list info as well
p
i guess each item in your list has a linked data class, why not use your own id to recognize it? and load when the item itself is composed
c
Yeah. I could probably do that. Not sure why the code was written like this the first way around. But I could see myself just doing
viewModel.getMoreInfoForItemWithId(item.id)
p
right and write it inside the
item { }
block which you have access to the item directly
and you can remove the whole code above 🙂
f
wouldn't that call it at each recomposition?
p
not if you use
remember(item.id)
or LaunchEffect
f
so yes then as stated
c
interesting...... that seems like it should work also. i wonder why i initially went down this path. i guess its really because i was watching this video and was like "oh wait i need to do something kinda like this" timestamped:

https://youtu.be/1ANt65eoNhQ?t=263

p
if you want to still use listState to trigger that kind of logic would be better to cache using a property that is going to change less frequently (not any pixel scrolled) like listState.firstVisibleItemIndex
f
using the list state seems more natural, your items do not need to know about the viewmodel, making them more reusable
1
b
Yeah not sure I like adding the side effect to all of my items but personal preference. The one launchedeffect at the top seems better to me and to fix the index problem, just key your items with your id and then in the launchedeffect do it.key instead of it.index
👍 1
p
in the video, impression is discussed, a library like this would be more helpful: https://github.com/abema/compose-impression-tracker