LazyRecycler is a library that provides LazyColumn like APIs to build lists with RecyclerView.
implementation 'io.github.dokar3:lazyrecycler:latest_version'Then create a list by using the lazyRecycler() function. Adapter, LayoutManager, DiffUtil, OnItemClickListener and more, these are all in one block:
lazyRecycler(recyclerView, spanCount = 3) {
// Header
item(layout = R.layout.header) {}
// Create a section
items(
items = listOfNews,
layout = ItemNewsBinding::inflate,
clicks = { view, item ->
// Handle item clicks
},
longClicks = { view, item ->
// Handle long clicks
true
},
diffCallback = diffCallback {
areItemsTheSame { oldItem, newItem ->
oldItem.id == newItem.id
}
areContentsTheSame { oldItem, newItem ->
oldItem.title == newItem.title && ...
}
},
span = { position ->
if (position % 3 == 0) 3 else 1
},
) { binding, news ->
// Bind item
}
// Some other sections
items(...)
items(...)
// Footer
item(layout = R.layout.footer) {}
}In LazyRecycler, every item and items call will create a section, sections will be added to the Adapter by the creating order.
item(...) { ... }
items(...) { ... }When creating a dynamic section, it's necessary to set a unique id to update the section later, or using reactive data sources may be a better choice (see the Reactive data sources section).
val recycler = lazyRecycler {
items(
items = news,
layout = R.layout.item_news,
id = SOME_ID,
) { ... }
}
// Update
recycler.updateSection(SOME_ID, items)
// Remove
recycler.removeSection(SOME_ID)// xml layout id
items(items = news, layout = R.layout.item_news) { ... }
// ViewBinding, DataBinding
items(items = news, layout = ItemNewsBinding::inflate) { binding, item -> ... }
// Instantiate views
items(items = news) { parent ->
val itemView = NewsItemView(context)
...
itemView
}For ViewBinding item/items:
items(items = news, layout = ItemNewsBinding::inflate) { binding, item ->
binding.title.text = item.title
binding.image.load(item.cover)
...
}For DataBinding item/items:
items(items = news, layout = ItemNewsDataBinding::inflate) { binding, item ->
binding.news = item
}For layoutId item/items:
items(items = news, layout = R.layout.item_news) { view ->
...
val tv: TextView = view.findViewById(R.id.title)
bind { item ->
tv.text = item.title
}
}For view instantiation item/items:
items(items = news) { parent ->
val itemView = CustomNewsItemView(context)
bind { item ->
itemView.title(item.title)
...
}
itemView
}items<I>(items = news, layout = layoutId) { ... }
items<V, I>(items = news, layout = ItemViewBinding::inflate) { ... }
items<I>(items = news) { ... }Ifor item typeVfor the View Binding
In most cases, the compiler is smart enough so type parameter(s) can be omitted.
LazyRecycler creates a LinearLayoutManager by default, if spanCount > 1, GridLayoutManager will be used, to skip the LayoutManager setup, set setupLayoutManager to false:
lazyRecycler(
recyclerView,
setupLayoutManager = false,
) { ... }
....
recyclerView.layoutManager = YourOwnLayoutManager(...)span is used to define SpanSizeLookup for GridLayoutManager:
items(
...,
span = { position ->
if (position == 0) 3 else 1
},
) {
...
}To support reactive data sources like Flow, LiveData, or RxJava, add the dependencies:
// Flow
implementation 'io.github.dokar3:lazyrecycler-flow:latest_version'
// LiveData
implementation 'io.github.dokar3:lazyrecycler-livedata:latest_version'
// RxJava3
implementation 'io.github.dokar3:lazyrecycler-rxjava3:latest_version'
// Paging 3
implementation 'io.github.dokar3:lazyrecycler-paging3:latest_version'val entry: Flow<I> = ...
item(data = entry.toMutableValue(coroutineScope), ...) { ... }
val source: Flow<List<I>> = ...
items(data = source.toMutableValue(coroutineScope), ...) { ... }val entry: LiveData<I> = ...
item(data = entry.toMutableValue(lifecycleOwner), ...) { ... }
val source: LiveData<List<I>> = ...
items(data = source.toMutableValue(lifecycleOwner), ...) { ... }val entry: Observable<I> = ...
item(data = entry.toMutableValue(), ...) { ... }
val source: Observable<List<I>> = ...
items(data = source.toMutableValue(), ...) { ... }To use Paging 3 with LazyRecycler, some additional setups are required.
First, create a PagingValue from your PagingData flow.
// Required by the AsyncPagingDataDiffer
val diffCallback = object : DiffUtil.ItemCallback<Post>() {
override fun areItemsTheSame(oldItem: Post, newItem: Post): Boolean =
oldItem.id == newItem.id
override fun areContentsTheSame(oldItem: Post, newItem: Post): Boolean =
oldItem == newItem
}
// A PagingValue is a MutableValue so we can use it in our items() setup
val pagingValue = PagingValue(
scope = lifecycleScope,
// The PagingData flow: Flow<PagingData<Post>>
flow = viewModel.postFlow,
diffCallback = diffCallback,
)Second, setup PagingLazyAdapter to trigger loads when the list scrolls to the end.
lazyRecycler(
recyclerView = recyclerView,
adapterCreator = ::PagingLazyAdapter,
) { ... }Finally, setup items to use the paging value.
items(
data = pagingValue,
layout = ItemPostBinding::inflate,
diffCallback = pagingValue.diffCallback,
) {
// binding
}LazyRecycler will observe the data sources automatically after attaching to the RecyclerView, so it's no necessary to call it manually. But there is a function call if really needed:
recycler.observeChanges()When using a RxJava data source or a Flow data source which was not created by the lifecycleScope, should stop observing data sources manually to prevent leaks:
recycler.stopObserving()Use template() to reuse bindings:
lazyRecycler {
// layout id template
val sectionHeader = template<String>(R.layout.section_header) {
// Bind item
bind { ... }
}
// ViewBinding template
val normalItem = template<Item>(ItemFriendBubbleBinding::inflate) { binding, item ->
// Bind item
}
item(item = "section 1", template = sectionHeader)
items(items = someItems, template = normalItem)
item(item = "section 2", template = sectionHeader)
items(items = otherItems, template = normalItem)
...
}Use template() + extraViewTypes:
lazyRecycler {
val friendBubble = template<Message>(ItemFriendBubbleBinding::inflate) { binding, msg ->
// Bind alternative items
}
items(
items = messages,
layout = ItemOwnerBubbleBinding::inflate,
extraViewTypes = listOf(
ViewType(friendBubble) { position, item -> /* predicate */ },
),
{ binding, msg ->
// Bind default items
}
...
}Use recycler.newSections():
val recycler = lazyRecycler { ... }
recycler.newSections {
item(...) { ... }
items(...) { ... }
...
}
// If new sections contain any observable data source
recycler.observeChanges()backgroundThread {
// Do not pass RecyclerView to the lazyRecycler()
val recycler = lazyRecycler {
...
}
uiThread {
recycler.attachTo(recyclerView)
}
}![]() |
![]() |
|---|---|
| Day | Night |
![]() |
![]() |
|---|---|
| Day | Night |
![]() |
![]() |
|---|---|
| Day | Night |






