Skip to main content

Interactions

note

Within Litho, lists are implemented using the Lazy Collection API.

Clicking items

Clicking items in the Lazy Collection can be done by simply adding Style.onClick to children.

private val ONE_TO_TEN =
listOf("One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten")

class BasicList : KComponent() {
override fun ComponentScope.render(): Component {
val clickItem = useState { "Nothing" }
val callBack = useCallback { index: Int -> clickItem.update(ONE_TO_TEN[index]) }

return LazyList {
child(Text(text = "Click us!"))
ONE_TO_TEN.forEachIndexed { index, str ->
child(
id = str,
component =
Text(
str,
style = Style.onClick { callBack(index) }.padding(all = 16.dp),
))
}
child(Text(text = "${clickItem.value} was clicked"))
}
}
}

Scrolling the Lazy Collection

Scrolling the Lazy Collection by a given distance, or to a child with a given index/id, is a two-step process:

  1. Create a LazyCollectionController and attach it to the Lazy Collection via the lazyCollectionController parameter. Note that it should be defined in a useState hook.
  2. Use one of the LazyCollectionController’s scroll functions.
class ScrollingExample() : KComponent() {

override fun ComponentScope.render(): Component {
val controller = useState { LazyCollectionController() }.value

// Use one of these lambdas to scroll, e.g. in an onClick callback
val scrollToTenth = useCallback<ClickEvent, Unit> { controller.scrollToIndex(index = 10) }
val smoothScrollToEnd = useCallback<ClickEvent, Unit> { controller.smoothScrollToId("End") }

return LazyList(
lazyCollectionController = controller,
) {
child(Text(style = Style.onClick(scrollToTenth), text = "Scroll to item 10"))
child(Text(style = Style.onClick(smoothScrollToEnd), text = "Smooth Scroll to End"))
children(items = (0..20), id = { it }) { Text("$it") }
child(id = "End", component = Text("End"))
}
}
}

Responding to scroll events

An OnNearCallback can be applied to the Lazy Collection's onNearEnd parameter, or a child's onNearViewport parameter.

For example, onNearEnd = OnNearCallback(offset = 10) { /* callback */ }.

Where:

  • onNearEnd is invoked when the Lazy Collection is scrolled to the last position or is scrolled within offset items away.
  • onNearViewport is invoked when the child enters the viewport or is offset items away.

For more complex scroll handling (such as for animations), you can access the RecyclerView directly via a LazyCollectionController and use a RecyclerView.OnScrollListener to receive callbacks during scroll. To use this technique, take the following steps:

  1. Create a LazyCollectionController and attach it to the Lazy Collection via the lazyCollectionController parameter. Note that it should be defined in a useState hook.
  2. Access the RecyclerView via LazyCollectionController.recyclerView
  3. Add a custom OnScrollListener using RecyclerView.addOnScrollListener(..)
note

It is currently unsafe to trigger a scroll event inside a useEffect callback as it will be invoked before the Lazy Collection's contents have been mounted. To trigger a scroll immediately on entering a screen, use the LazyCollection's onDataBound callback.

Paging

Lists of data are often retrieved from the server in pages; additional data is requested only if the user scrolls. To achieve this behaviour, add a callback to the Lazy Collection’s onNearEnd parameter that fetches more data. This will be triggered when the Lazy Collection is scrolled near to the end. Optionally, add a request indicator to the bottom of the list.

class PagedExample(private val pagedList: PaginatedList<Item>) : KComponent() {
override fun ComponentScope.render(): Component =
LazyList(
onNearEnd = OnNearCallback { if (pagedList.hasNextPage) pagedList.fetchNextPage() },
) {
// Add the retrieved items
children(items = pagedList.list, id = { it.id }) { Text(it.text) }

// Optionally add a progress spinner
if (pagedList.hasNextPage) {
child(ProgressSpinner())
}
}
}

Pull to refresh

To implement the 'pull to refresh' behaviour, provide a callback to the Lazy Collection's onPullToRefresh parameter. This callback will be responsible for fetching fresh data.

To dismiss the refreshing indicator, take the following two steps:

  1. Create a LazyCollectionController and attach it to the Lazy Collection via the lazyCollectionController parameter. Note that it should be defined in a useState hook.
  2. Call LazyCollectionController.setRefreshing(false).
class PullToRefreshExample(
val data: List<String>,
val refresh: () -> Unit,
) : KComponent() {

override fun ComponentScope.render(): Component {
val controller = useState { LazyCollectionController() }.value
return LazyList(
lazyCollectionController = controller,
onPullToRefresh = {
refresh()
controller.setRefreshing(false)
},
) { /* Add children */
}
}
}