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

Colton Idle

07/05/2022, 12:03 AM
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

shikasd

07/05/2022, 12:08 AM
(I totally did write that lint) Try ide suggestions ;)
c

Colton Idle

07/05/2022, 12:12 AM
lmao. thats awesome luck that i have then. here the result of applying the IDE suggestion
s

shikasd

07/05/2022, 12:13 AM
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

Colton Idle

07/05/2022, 12:14 AM
lol
s

shikasd

07/05/2022, 12:14 AM
I was checking it on a bit different examples, but some space for improvement
c

Colton Idle

07/05/2022, 12:14 AM
was that all it was!?! omg. let me see.
s

shikasd

07/05/2022, 12:15 AM
You probably want to do the mapping inside the derive state, btw, to avoid recalculations as well
b

Ben Trengrove [G]

07/05/2022, 12:16 AM
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

Colton Idle

07/05/2022, 12:18 AM
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

shikasd

07/05/2022, 12:19 AM
^ see my previous comment, you want to also map the values inside derived state, otherwise it doesn't diff anything
c

Colton Idle

07/05/2022, 12:21 AM
So
Copy code
val visibleItems = remember {
    derivedStateOf {
      listState.layoutInfo.visibleItemsInfo.map {
        Log.e("TEST", "ITEM VISIBLE!")
        it.index
      }
    }
  }
?
s

shikasd

07/05/2022, 12:21 AM
Yep, that :)
Editing code from the phone is mildly confusing
b

Ben Trengrove [G]

07/05/2022, 12:24 AM
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

Colton Idle

07/05/2022, 12:25 AM
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

Ben Trengrove [G]

07/05/2022, 12:32 AM
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

Colton Idle

07/05/2022, 12:33 AM
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

shikasd

07/05/2022, 12:34 AM
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

Colton Idle

07/05/2022, 12:38 AM
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

shikasd

07/05/2022, 12:40 AM
Does it start lagging only after adding snapshot flow?
b

Ben Trengrove [G]

07/05/2022, 12:40 AM
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

Colton Idle

07/05/2022, 12:49 AM
@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

Ben Trengrove [G]

07/05/2022, 12:50 AM
Maybe visibleItemsInfo doesn't work well with the distinctUntilChanged?
🤷‍♂️ 1
c

Colton Idle

07/05/2022, 12:50 AM
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

Ben Trengrove [G]

07/05/2022, 12:52 AM
Hopefully you learnt something along the way!
💯 1
c

Colton Idle

07/05/2022, 12:53 AM
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

Ben Trengrove [G]

07/05/2022, 1:04 AM
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

Francesc

07/05/2022, 1:07 AM
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

Colton Idle

07/05/2022, 1:08 AM
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

Ben Trengrove [G]

07/05/2022, 1:09 AM
Yes
🙏 1
f

Francesc

07/05/2022, 1:09 AM
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

Colton Idle

07/05/2022, 1:11 AM
I'm looking to essentially lazily load more info for an item in the list as the item is displayed to the user.
p

pepos

07/05/2022, 1:12 AM
using listState index to fetch data seems wrong anyway, if you add another item like a (eg: separator ) that would break your code
f

Francesc

07/05/2022, 1:12 AM
ok, you could use the
index
property in that case for the lazy list info as well
p

pepos

07/05/2022, 1:12 AM
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

Colton Idle

07/05/2022, 1:14 AM
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

pepos

07/05/2022, 1:17 AM
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

Francesc

07/05/2022, 1:18 AM
wouldn't that call it at each recomposition?
p

pepos

07/05/2022, 1:20 AM
not if you use
remember(item.id)
or LaunchEffect
f

Francesc

07/05/2022, 1:21 AM
so yes then as stated
c

Colton Idle

07/05/2022, 1:22 AM
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

pepos

07/05/2022, 1:24 AM
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

Francesc

07/05/2022, 1:25 AM
using the list state seems more natural, your items do not need to know about the viewmodel, making them more reusable
1
b

Ben Trengrove [G]

07/05/2022, 1:26 AM
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

pepos

07/05/2022, 1:26 AM
in the video, impression is discussed, a library like this would be more helpful: https://github.com/abema/compose-impression-tracker
32 Views