{"id":18266043,"url":"https://github.com/dokar3/lazyrecycler","last_synced_at":"2025-10-07T08:25:16.756Z","repository":{"id":37828295,"uuid":"344170886","full_name":"dokar3/LazyRecycler","owner":"dokar3","description":"A library that provides LazyColumn like APIs to build lists with RecyclerView.","archived":false,"fork":false,"pushed_at":"2024-09-19T04:27:53.000Z","size":2398,"stargazers_count":20,"open_issues_count":5,"forks_count":1,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-09-09T05:09:38.885Z","etag":null,"topics":["android","diffutil","jetpack-compose","kotlin","recyclerview"],"latest_commit_sha":null,"homepage":"","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dokar3.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-03-03T15:21:38.000Z","updated_at":"2025-08-30T10:50:45.000Z","dependencies_parsed_at":"2023-02-18T03:16:28.871Z","dependency_job_id":"23fac572-559e-4222-827b-22c6304d4458","html_url":"https://github.com/dokar3/LazyRecycler","commit_stats":null,"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"purl":"pkg:github/dokar3/LazyRecycler","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dokar3%2FLazyRecycler","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dokar3%2FLazyRecycler/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dokar3%2FLazyRecycler/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dokar3%2FLazyRecycler/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dokar3","download_url":"https://codeload.github.com/dokar3/LazyRecycler/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dokar3%2FLazyRecycler/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":278742218,"owners_count":26037774,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-07T02:00:06.786Z","response_time":59,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["android","diffutil","jetpack-compose","kotlin","recyclerview"],"created_at":"2024-11-05T11:21:22.109Z","updated_at":"2025-10-07T08:25:16.729Z","avatar_url":"https://github.com/dokar3.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"# RecyclerView，Compose like\n\n![](arts/lazyrecycler.png)\n\n**LazyRecycler** is a library that provides [LazyColumn](https://developer.android.com/reference/kotlin/androidx/compose/foundation/lazy/package-summary) like APIs to build lists with RecyclerView. \n\n### Quick start\n\nAdd the dependency [![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.github.dokar3/lazyrecycler/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.github.dokar3/lazyrecycler):\n\n```groovy\nimplementation 'io.github.dokar3:lazyrecycler:latest_version'\n```\n\nThen create a list by using the `lazyRecycler()` function. Adapter, LayoutManager, DiffUtil, OnItemClickListener and more, these are **all in one** block:\n\n```kotlin\nlazyRecycler(recyclerView, spanCount = 3) {\n    // Header\n    item(layout = R.layout.header) {}\n\n    // Create a section\n    items(\n        items = listOfNews,\n        layout = ItemNewsBinding::inflate,\n        clicks = { view, item -\u003e\n            // Handle item clicks\n        },\n        longClicks = { view, item -\u003e \n            // Handle long clicks\n            true\n        },\n        diffCallback = diffCallback {\n            areItemsTheSame { oldItem, newItem -\u003e\n                oldItem.id == newItem.id\n            }\n            areContentsTheSame { oldItem, newItem -\u003e\n                oldItem.title == newItem.title \u0026\u0026 ...\n            }\n        },\n        span = { position -\u003e\n            if (position % 3 == 0) 3 else 1\n        },\n    ) { binding, news -\u003e\n        // Bind item\n    }\n    \n    // Some other sections\n    items(...)\n    items(...)\n    \n    // Footer\n    item(layout = R.layout.footer) {}\n}\n```\n\n\n\n# Basic\n\n### Sections\n\nIn LazyRecycler, every `item` and `items` call will create a section, sections will be added to the Adapter by the creating order.\n\n```kotlin\nitem(...) { ... }\n\nitems(...) { ... }\n```\n\nWhen 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](#reactive-data-sources) section).\n\n```kotlin\nval recycler = lazyRecycler {\n    items(\n        items = news,\n        layout = R.layout.item_news,\n        id = SOME_ID,\n    ) { ... }\n}\n\n// Update\nrecycler.updateSection(SOME_ID, items)\n// Remove\nrecycler.removeSection(SOME_ID)\n```\n\n### Layout\n\n```kotlin\n// xml layout id\nitems(items = news, layout = R.layout.item_news) { ... }\n\n// ViewBinding, DataBinding\nitems(items = news, layout = ItemNewsBinding::inflate) { binding, item -\u003e ... }\n\n// Instantiate views\nitems(items = news) { parent -\u003e\n    val itemView = NewsItemView(context)\n    ...\n    itemView\n}\n```\n\n### Bind\n\nFor ViewBinding item/items:\n\n```kotlin\nitems(items = news, layout = ItemNewsBinding::inflate) { binding, item -\u003e\n    binding.title.text = item.title\n    binding.image.load(item.cover)\n    ...\n}\n```\n\nFor DataBinding item/items:\n\n```kotlin\nitems(items = news, layout = ItemNewsDataBinding::inflate) { binding, item -\u003e\n    binding.news = item\n}\n```\n\nFor layoutId item/items:\n\n```kotlin\nitems(items = news, layout = R.layout.item_news) { view -\u003e\n    ...\n    val tv: TextView = view.findViewById(R.id.title)\n    bind { item -\u003e\n        tv.text = item.title\n    }\n}\n```\n\nFor view instantiation item/items:\n\n```kotlin\nitems(items = news) { parent -\u003e\n    val itemView = CustomNewsItemView(context)\n    bind { item -\u003e\n        itemView.title(item.title)\n        ...\n    }\n    itemView\n}\n```\n\n### Generics\n\n```kotlin\nitems\u003cI\u003e(items = news, layout = layoutId) { ... }\nitems\u003cV, I\u003e(items = news, layout = ItemViewBinding::inflate) { ... }\nitems\u003cI\u003e(items = news) { ... }\n```\n\n* `I` for item type\n* `V` for the View Binding\n\nIn most cases, the compiler is smart enough so type parameter(s) can be omitted.\n\n### LayoutManager\n\nLazyRecycler creates a LinearLayoutManager by default, if `spanCount`  \u003e 1, GridLayoutManager will be used,  to skip the LayoutManager setup, set `setupLayoutManager` to `false`:\n\n```kotlin\nlazyRecycler(\n    recyclerView,\n    setupLayoutManager = false,\n) { ... }\n....\nrecyclerView.layoutManager = YourOwnLayoutManager(...)\n```\n\n`span` is used to define `SpanSizeLookup` for GridLayoutManager:\n\n```kotlin\nitems(\n    ...,\n    span = { position -\u003e\n        if (position == 0) 3 else 1 \n    },\n) {\n    ...\n}\n```\n\n\n\n# Reactive data sources\n\nTo support reactive data sources like `Flow`, `LiveData`, or `RxJava`, add the dependencies:\n\n```groovy\n// Flow\nimplementation 'io.github.dokar3:lazyrecycler-flow:latest_version'\n// LiveData\nimplementation 'io.github.dokar3:lazyrecycler-livedata:latest_version'\n// RxJava3\nimplementation 'io.github.dokar3:lazyrecycler-rxjava3:latest_version'\n// Paging 3\nimplementation 'io.github.dokar3:lazyrecycler-paging3:latest_version'\n```\n\nLatest version: [![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.github.dokar3/lazyrecycler/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.github.dokar3/lazyrecycler)\n\n### Flow\n\n```kotlin\nval entry: Flow\u003cI\u003e = ...\nitem(data = entry.toMutableValue(coroutineScope), ...) { ... }\n\nval source: Flow\u003cList\u003cI\u003e\u003e = ...\nitems(data = source.toMutableValue(coroutineScope), ...) { ... }\n```\n\n### LiveData\n\n```kotlin\nval entry: LiveData\u003cI\u003e = ...\nitem(data = entry.toMutableValue(lifecycleOwner), ...) { ... }\n\nval source: LiveData\u003cList\u003cI\u003e\u003e = ...\nitems(data = source.toMutableValue(lifecycleOwner), ...) { ... }\n```\n\n### RxJava\n\n```kotlin\nval entry: Observable\u003cI\u003e = ...\nitem(data = entry.toMutableValue(), ...) { ... }\n\nval source: Observable\u003cList\u003cI\u003e\u003e = ...\nitems(data = source.toMutableValue(), ...) { ... }\n```\n\n\n### Paging 3\n\nTo use Paging 3 with LazyRecycler, some additional setups are required.\n\nFirst, create a `PagingValue` from your `PagingData` flow.\n\n```kotlin\n// Required by the AsyncPagingDataDiffer\nval diffCallback = object : DiffUtil.ItemCallback\u003cPost\u003e() {\n    override fun areItemsTheSame(oldItem: Post, newItem: Post): Boolean =\n        oldItem.id == newItem.id\n\n    override fun areContentsTheSame(oldItem: Post, newItem: Post): Boolean =\n        oldItem == newItem\n}\n// A PagingValue is a MutableValue so we can use it in our items() setup\nval pagingValue = PagingValue(\n    scope = lifecycleScope,\n    // The PagingData flow: Flow\u003cPagingData\u003cPost\u003e\u003e\n    flow = viewModel.postFlow,\n    diffCallback = diffCallback,\n)\n```\n\nSecond, setup `PagingLazyAdapter` to trigger loads when the list scrolls to the end.\n\n```kotlin\nlazyRecycler(\n    recyclerView = recyclerView,\n    adapterCreator = ::PagingLazyAdapter,\n) { ... }\n```\n\nFinally, setup `items` to use the paging value.\n\n```kotlin\nitems(\n    data = pagingValue,\n    layout = ItemPostBinding::inflate,\n    diffCallback = pagingValue.diffCallback,\n) {\n    // binding\n}\n```\n\n\n### Observe/stop observing data sources:\n\nLazyRecycler 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:\n\n```kotlin\nrecycler.observeChanges()\n```\n\nWhen 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:\n\n```kotlin\nrecycler.stopObserving()\n```\n\n\n# Advanced\n\n### Alternate sections\n\nUse `template()`  to reuse bindings:\n\n```kotlin\nlazyRecycler {\n    // layout id template\n    val sectionHeader = template\u003cString\u003e(R.layout.section_header) {\n        // Bind item\n        bind { ... }\n    }\n    // ViewBinding template\n    val normalItem = template\u003cItem\u003e(ItemFriendBubbleBinding::inflate) { binding, item -\u003e\n        // Bind item\n    }\n    \n    item(item = \"section 1\", template = sectionHeader)\n    items(items = someItems, template = normalItem)\n    \n    item(item = \"section 2\", template = sectionHeader)\n    items(items = otherItems, template = normalItem)\n    ...\n}\n```\n\n### Single items with multiple view types\n\nUse `template()` + `extraViewTypes`: \n\n```kotlin\nlazyRecycler {\n    val friendBubble = template\u003cMessage\u003e(ItemFriendBubbleBinding::inflate) { binding, msg -\u003e\n        // Bind alternative items\n    }\n    \n    items(\n        items = messages,\n        layout = ItemOwnerBubbleBinding::inflate,\n        extraViewTypes = listOf(\n            ViewType(friendBubble) { position, item -\u003e /* predicate */ },\n        ),\n    { binding, msg -\u003e\n        // Bind default items\n    }\n    ...\n}\n```\n\n### Add new sections dynamically\n\nUse `recycler.newSections()`:\n\n```kotlin\nval recycler = lazyRecycler { ... }\nrecycler.newSections {\n    item(...) { ... }\n    items(...) { ... }\n    ...\n}\n// If new sections contain any observable data source\nrecycler.observeChanges()\n```\n\n### Build list in background\n\n```kotlin\nbackgroundThread {\n    // Do not pass RecyclerView to the lazyRecycler()\n    val recycler = lazyRecycler {\n        ...\n    }\n    uiThread {\n        recycler.attachTo(recyclerView)\n    }\n}\n```\n\n\n\n# Demos\n\n### 1. Multiple sections\n\n| ![](arts/screenshot_gallery.png) | ![](arts/screenshot_gallery_night.png) |\n| :------------------------------: | :------------------------------------: |\n|               Day                |                 Night                  |\n\n### 2. Chat screen\n\n| ![](arts/screenshot_chat.png) | ![](arts/screenshot_chat_night.png) |\n| :---------------------------: | :---------------------------------: |\n|              Day              |                Night                |\n\n### 3. Tetris game (Just for fun)\n\n| ![](arts/screenshot_tetris.png) | ![](arts/screenshot_tetris_night.png) |\n| :-----------------------------: | :-----------------------------------: |\n|               Day               |                 Night                 |\n\n\n\n# Links\n\n* [Jetpack Compose](https://developer.android.com/jetpack/compose)\n* [LazyColumn](https://developer.android.com/reference/kotlin/androidx/compose/foundation/lazy/package-summary#lazycolumn)\n* [square/cycler](https://github.com/square/cycler)\n\n\n\n# License\n\n[Apache License 2.0](./LICENSE.txt)","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdokar3%2Flazyrecycler","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdokar3%2Flazyrecycler","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdokar3%2Flazyrecycler/lists"}