{"id":15040979,"url":"https://github.com/gotev/recycler-adapter","last_synced_at":"2025-08-22T08:11:53.816Z","repository":{"id":46675567,"uuid":"70579715","full_name":"gotev/recycler-adapter","owner":"gotev","description":"RecyclerView-driven declarative UIs","archived":false,"fork":false,"pushed_at":"2021-10-14T17:15:11.000Z","size":3153,"stargazers_count":124,"open_issues_count":0,"forks_count":15,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-07-02T09:48:08.275Z","etag":null,"topics":["adapter","android","android-development","android-library","android-ui","declarative","drag-and-drop","kotlin","kotlin-android","list","listview","model","mvvm","nested-recyclerviews","recyclerview","table","ui","view","viewholder"],"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/gotev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null},"funding":{"custom":["https://paypal.me/alexgt"]}},"created_at":"2016-10-11T09:54:12.000Z","updated_at":"2025-06-16T07:40:06.000Z","dependencies_parsed_at":"2022-08-24T05:31:48.442Z","dependency_job_id":null,"html_url":"https://github.com/gotev/recycler-adapter","commit_stats":null,"previous_names":[],"tags_count":23,"template":false,"template_full_name":null,"purl":"pkg:github/gotev/recycler-adapter","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gotev%2Frecycler-adapter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gotev%2Frecycler-adapter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gotev%2Frecycler-adapter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gotev%2Frecycler-adapter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gotev","download_url":"https://codeload.github.com/gotev/recycler-adapter/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gotev%2Frecycler-adapter/sbom","scorecard":{"id":441985,"data":{"date":"2025-08-11","repo":{"name":"github.com/gotev/recycler-adapter","commit":"daafddf483d55ec612c25fccd2de14b0c2e8b7fe"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":2.9,"checks":[{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Code-Review","score":0,"reason":"Found 2/29 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/android.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Binary-Artifacts","score":7,"reason":"binaries present in source code","details":["Warn: binary detected: app/demo/gradle/wrapper/gradle-wrapper.jar:1","Warn: binary detected: app/gradle/wrapper/gradle-wrapper.jar:1","Warn: binary detected: gradle/wrapper/gradle-wrapper.jar:1"],"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/android.yml:14: update your workflow using https://app.stepsecurity.io/secureworkflow/gotev/recycler-adapter/android.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/android.yml:16: update your workflow using https://app.stepsecurity.io/secureworkflow/gotev/recycler-adapter/android.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/android.yml:27: update your workflow using https://app.stepsecurity.io/secureworkflow/gotev/recycler-adapter/android.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/android.yml:29: update your workflow using https://app.stepsecurity.io/secureworkflow/gotev/recycler-adapter/android.yml/master?enable=pin","Info:   0 out of   4 GitHub-owned GitHubAction dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: Apache License 2.0: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":0,"reason":"Project has not signed or included provenance with any releases.","details":["Warn: release artifact 4.1.1 not signed: https://api.github.com/repos/gotev/recycler-adapter/releases/51379113","Warn: release artifact 4.1.0 not signed: https://api.github.com/repos/gotev/recycler-adapter/releases/50575699","Warn: release artifact 4.0.0 not signed: https://api.github.com/repos/gotev/recycler-adapter/releases/44145698","Warn: release artifact 3.2.0 not signed: https://api.github.com/repos/gotev/recycler-adapter/releases/41584361","Warn: release artifact 3.1.2 not signed: https://api.github.com/repos/gotev/recycler-adapter/releases/40727499","Warn: release artifact 4.1.1 does not have provenance: https://api.github.com/repos/gotev/recycler-adapter/releases/51379113","Warn: release artifact 4.1.0 does not have provenance: https://api.github.com/repos/gotev/recycler-adapter/releases/50575699","Warn: release artifact 4.0.0 does not have provenance: https://api.github.com/repos/gotev/recycler-adapter/releases/44145698","Warn: release artifact 3.2.0 does not have provenance: https://api.github.com/repos/gotev/recycler-adapter/releases/41584361","Warn: release artifact 3.1.2 does not have provenance: https://api.github.com/repos/gotev/recycler-adapter/releases/40727499"],"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 4 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-19T05:47:37.833Z","repository_id":46675567,"created_at":"2025-08-19T05:47:37.833Z","updated_at":"2025-08-19T05:47:37.833Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":271606595,"owners_count":24788979,"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-08-22T02:00:08.480Z","response_time":65,"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":["adapter","android","android-development","android-library","android-ui","declarative","drag-and-drop","kotlin","kotlin-android","list","listview","model","mvvm","nested-recyclerviews","recyclerview","table","ui","view","viewholder"],"created_at":"2024-09-24T20:45:21.702Z","updated_at":"2025-08-22T08:11:53.784Z","avatar_url":"https://github.com/gotev.png","language":"Kotlin","funding_links":["https://paypal.me/alexgt"],"categories":[],"sub_categories":[],"readme":"# Recycler Adapter ![Maven Central](https://img.shields.io/maven-central/v/net.gotev/recycleradapter)\n#### [Latest version Release Notes and Demo App](https://github.com/gotev/recycler-adapter/releases/latest) | [Demo App Sources](https://github.com/gotev/recycler-adapter/tree/master/app/demo/src/main/java/net/gotev/recycleradapterdemo)\n\nRecyclerView-driven declarative UIs.\n\nUsing stock Android View system and Recycler Adapter, you can already write UIs similar to [JetPack Compose](https://developer.android.com/jetpack/compose) and use it in production.\n\n```kotlin\nrender {\n    +Items.leaveBehind(\"swipe to left to leave behind\", \"option\")\n\n    (0..random.nextInt(200) + 50).map { number -\u003e\n        if (randomNumber % 2 == 0)\n            +Items.Card.titleSubtitle(\"Item $number\", \"subtitle $number\")\n        else\n            +Items.Card.labelWithToggle(\"Toggle $number\")\n    }\n}\n```\nEvery time you call `render`, RecyclerAdapter takes care of updating the RecyclerView and make the needed diffings to reflect the new status. Underneath it uses `DiffUtil` for maximum performance and efficiency.\n\n\u003cimg width=\"324\" alt=\"Schermata 2021-05-08 alle 07 39 59\" src=\"https://user-images.githubusercontent.com/16792495/117528089-bb938e00-afd0-11eb-8564-eeb1d0b8efb3.png\"\u003e\n\nStandard `RecyclerView.Adapter` is tedious to work with, because you have to write repetitive boilerplate and spaghetti code and to concentrate all your items view logic and binding into the adapter itself, which is really bad. This library was born to be able to have the following for each element in a recycler view:\n\n* a `model`, which is a simple `data class`\n* a programmatic `View` or an XML layout file, in which to define the item's view hierarchy\n* a `view model` file (called `AdapterItem`), in which to specify the binding between the model and the view and in which to handle user interactions with the item.\n\nIn this way every item of the recycler view has its own set of files, resulting in a cleaner and easier to maintain code base.\n\n## Examples\nBefore diving into some details, it's worth mentioning you can download and try those example apps which are using the library:\n- [Recycler Adapter Demo App](https://github.com/gotev/recycler-adapter/tree/master/app)\n- [Star Wars Api Demo App](https://github.com/gotev/swapi-android)\n\n# Index\n\n* [Setup](#setup)\n* [Basic usage tutorial](#basicTutorial)\n* [Diffing strategy](#diffingStrategy)\n* [Stable IDs](#stableIDs)\n* [Adding different kind of items](#differentItems)\n* [Carousels and nested RecyclerViews](#carousels)\n* [Paged Lists](#pagedLists)\n* [Empty item in Paged Lists](#emptyItemPagedLists)\n* [Filter items (to implement searchBar)](#filterItems)\n* [Sort items](#sortItems)\n* [Using ButterKnife](#butterKnife)\n* [Using Kotlin Android Extensions](#kotlinAndroidExt)\n* [Reorder items with drag \u0026 drop](#dragDrop)\n* [Handle clicks](#handleClicks)\n* [Handle item status](#handleItemStatus)\n* [Event lifecycle](#eventLifecycle)\n* [Single and Multiple selection of items in groups](#itemsSelection)\n* [Leave Behind pattern](#leaveBehind)\n* [Lock scrolling while inserting](#lockScroll)\n* [Contributors](#contributors)\n\n## \u003ca name=\"setup\"\u003e\u003c/a\u003eSetup\nIn your gradle dependencies add:\n```groovy\ndef recyclerAdapterVersion = \"x.y.z\" // change it with the version you want to use\nimplementation \"net.gotev:recycleradapter:$recyclerAdapterVersion\"\nimplementation \"net.gotev:recycleradapter-extensions:$recyclerAdapterVersion\"\n```\nThis is the latest version: ![Maven Central](https://img.shields.io/maven-central/v/net.gotev/recycleradapter)\n\n## \u003ca name=\"basicTutorial\"\u003e\u003c/a\u003eBasic usage tutorial\n### 1. Declare the RecyclerView\nIn your layout resource file or where you want the `RecyclerView` (e.g. `activity_main.xml`) add the following:\n```xml\n\u003candroidx.recyclerview.widget.RecyclerView\n    android:id=\"@+id/recycler_view\"\n    android:scrollbars=\"vertical\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\" /\u003e\n```\n\n### 2. Create your item layout\nCreate your item layout (e.g. `item_example.xml`). For example:\n```xml\n\u003c?xml version=\"1.0\" encoding=\"utf-8\"?\u003e\n\u003cTextView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:gravity=\"center_vertical\"\n    android:layout_width=\"wrap_content\"\n    android:layout_margin=\"8dp\"\n    android:layout_height=\"48dp\"\n    android:textSize=\"18sp\" /\u003e\n```\n\n### 3. Create the item\n```kotlin\nopen class ExampleItem(private val context: Context, private val text: String)\n    : AdapterItem\u003cExampleItem.Holder\u003e(text) {\n\n    // Variant using XML inflation\n    override fun getView(parent: ViewGroup): View = parent.inflating(R.layout.item_example)\n\n    // Variant using code only\n    /*\n    override fun getView(parent: ViewGroup): View = TextView(parent.context).apply {\n        layoutParams = ViewGroup.MarginLayoutParams(parent.layoutParams).apply {\n            width = WRAP_CONTENT\n            height = 48.dp(context)\n            gravity = CENTER_VERTICAL\n\n            val margin = 8.dp(context)\n            leftMargin = margin\n            rightMargin = margin\n            topMargin = margin\n            bottomMargin = margin\n        }\n\n        setTextSize(TypedValue.COMPLEX_UNIT_SP, 18f)\n    }\n    */\n\n    override fun bind(firstTime: Boolean, holder: ExampleItem.Holder) {\n        // you can use firstTime to discriminate between bindings you\n        // need only the first time the item is binded from the others\n        holder.titleField.text = text\n    }\n\n    class Holder(itemView: View)\n        : RecyclerAdapterViewHolder(itemView), LayoutContainer {\n\n        override val containerView: View?\n            get() = itemView\n\n        internal val titleField: TextView by lazy { title }\n\n        override fun prepareForReuse() {\n            // Here you can perform operations to clear data from the holder\n            // and free used resources, like bitmaps or other heavy weight\n            // things\n        }\n    }\n}\n```\n\n`LayoutContainer` is from Kotlin Android Extensions and is not mandatory, but it prevents memory leaks. [Read the article linked here](#kotlinAndroidExt)\n\n### 4. Instantiate RecyclerView and add items\nIn your Activity (`onCreate` method) or Fragment (`onCreateView` method):\n\n```kotlin\nval recyclerAdapter = RecyclerAdapter()\n\nrecycler_view.apply { // recycler_view is the id of your Recycler View in the layout\n    layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)\n    adapter = recyclerAdapter\n}\n\n//add items\nrecyclerAdapter.add(ExampleItem(\"test\"))\n```\n## \u003ca name=\"diffingStrategy\"\u003e\u003c/a\u003eDiffing Strategy\nPrior to 2.9.0, you had to implement `diffingId` method yourself. Starting from 2.9.0, all you need to do is to pass your model, which can be a primitive type or a complex data class.\n\nThis model, combined with your item's class name, is used to retrieve a diffingId to identify every single instance of your items uniquely.\n\n```kotlin\n// Example\ndata class YourModel(val text1: String, val text2: String)\n\nopen class YourItem(private val context: Context, private val yourModel: YourModel)\n    : AdapterItem\u003cExampleItem.Holder\u003e(yourModel) {\n\n    // In this case YourItem diffing id will be `YourItem.javaClass.name + yourModel.hashCode()`\n\n    ...\n}\n```\nThis means that in most cases this is what you are looking for and what you need in your project.\n\nN.B: If you are migrating from previous version of the library, you have to do a little refactor by following this simple steps:\n1. Provide a model instance to your items constructors\n2. Remove diffingId() overrides if it's a combination of solely javaClass.name and model properties values (even if its a subset of model properties)\n3. Remove hasToBeReplacedBy() implementations if it consist of elementary diffing of old model properties values with the new ones.\n\n## \u003ca name=\"stableIDs\"\u003e\u003c/a\u003eStable IDs\nStarting from 2.4.2, `RecyclerAdapter` has stable IDs out of the box. If you want to know more about what they are:\n* https://medium.com/@hanru.yeh/recyclerviews-views-are-blinking-when-notifydatasetchanged-c7b76d5149a2\n* https://developer.android.com/reference/androidx/recyclerview/widget/RecyclerView.Adapter.html#hasStableIds()\n\n## \u003ca name=\"differentItems\"\u003e\u003c/a\u003eAdding different kind of items\nYou can have more than one kind of item in your `RecyclerView`. Just implement a different `AdapterItem` for every type you want to support, and then just add it into the adapter:\n\n```kotlin\nrecyclerAdapter.add(ExampleItem(\"example item\"))\nrecyclerAdapter.add(TextWithButtonItem(\"text with button\"))\n```\n\nCheckout the example app provided to get a real example in action.\n\n## \u003ca name=\"carousels\"\u003e\u003c/a\u003eCarousels and nested RecyclerViews\nWhen more complex layouts are needed in a recycler view, you have two choices:\n\n* use a combination of existing layout managers and nest recycler views\n* create a custom layout manager\n\nSince the second strategy is really hard to implement and maintain, also due to lack of documentation and concrete working examples without huge memory leaks or crashes, in my experience resorting to the first strategy has always paid off both in terms of simplicity and maintainability.\n\nOne of the most common type of nested RecyclerViews are Carousels, like those you can find in Google Play Store. How to achieve that? First of all, include `recycleradapter-extensions` in your gradle:\n\n```groovy\nimplementation \"net.gotev:recycleradapter-extensions:$recyclerAdapterVersion\"\n```\n\nThe concept is really simple. You want to have a whole recycler view inside a single `AdapterItem`. To make things modular and to not reinvent the wheel, you want to be able to use a `RecyclerAdapter` in this nested `RecyclerView`. Please welcome `NestedRecyclerAdapterItem` which eases things for you. Override it to implement your custom nested recycler views. You can find a complete example in [Carousels Activity](https://github.com/gotev/recycler-adapter/blob/master/app/demo/src/main/java/net/gotev/recycleradapterdemo/activities/CarouselsActivity.kt) together with a custom [CarouselItem](https://github.com/gotev/recycler-adapter/blob/master/app/demo/src/main/java/net/gotev/recycleradapterdemo/adapteritems/CarouselItem.kt)\n\nSince having nested recycler views consumes a lot of memory and you may experience lags in your app, it's recommended to share a single `RecycledViewPool` across all your root and nested `RecyclerView`s. In that way all the `RecyclerView`s will use a single recycled pool like there's only one `RecyclerView`. You can see the performance difference by running the demo app on a low end device and trying Carousels both with pool and without pool.\n\n## \u003ca name=\"pagedLists\"\u003e\u003c/a\u003ePaged Lists\nStarting from `2.6.0` onwards, RecyclerAdapter integrates with Android JetPack's [Paging Library](https://developer.android.com/topic/libraries/architecture/paging) which allows you to have maximum performance when dealing with very long lists loaded from network, database or both.\n\nAdd this to your dependencies:\n```groovy\nimplementation \"net.gotev:recycleradapter-paging:$recyclerAdapterVersion\"\n```\n\nIt's strongly advised to study Google's Paging Library first so you can better understand how everything works and the motivation behind it. [Check this codelab which is great to learn](https://codelabs.developers.google.com/codelabs/android-paging/#0). When you are ready, check the demo provided in [PagingActivity](https://github.com/gotev/recycler-adapter/blob/master/app/demo/src/main/java/net/gotev/recycleradapterdemo/activities/PagingActivity.kt).\n\nThe paging module aims to provide an essential and thin layer on top of Google's `Paging Library`, to allow you to benefit the RecyclerAdapter abstractions and reuse all your existing Adapter items. `PagingAdapter` does not have all the features of the standard `RecyclerAdapter` on purpose, because `PagingAdapter` doesn't have the entire list in memory and it's intended to be used for different use cases.\n\n## \u003ca name=\"emptyItemPagedLists\"\u003e\u003c/a\u003eEmpty item in Paged Lists\nIf you want to add an Empty Item when RecyclerView is empty also when you're using the `PagingAdapter` extension, you have to implement a new `Item` (like described before) then, in your `DataSource` `LoadInitial` method, use the `withEmptyItem` enxtension for your callback instead of `onResult`:\n\n```kotlin\ncallback.withEmptyItem(emptyItem,response.results)\n```\n\n## \u003ca name=\"filterItems\"\u003e\u003c/a\u003eFilter items\nIf you need to search items in your recycler view, you have to override `onFilter` method in each one of your items implementation. Let's say our `AdapterItem` has a `text` field and we want to check if the search term matches it:\n\n```kotlin\n/**\n * Gets called for every item when the [RecyclerAdapter.filter] method gets called.\n * @param searchTerm term to search for\n * @return true if the items matches the search term, false otherwise\n */\nopen fun onFilter(searchTerm: String): Boolean {\n    return text.contains(searchTerm)\n}\n```\n\nTo filter the recycler view, call:\n\n```kotlin\nrecyclerAdapter.filter(\"search item\")\n```\nand only the items which matches the search term will be shown. To reset the search filter, pass `null` or an empty string.\n\n## \u003ca name=\"sortItems\"\u003e\u003c/a\u003eSort items\nTo sort items, you have the following possible approaches. Be sure to have included `recycleradapter-extensions` in your project.\n\n### 1. Implement `compareTo` and call `sort` on the `RecyclerAdapter`\nThis is the recommended approach if you have to sort all your items by a single criteria and you have a list with only one type of `Item`. Check [compareTo JavaDoc reference](https://developer.android.com/reference/java/lang/Comparable.html#compareTo(T)) for further information. In your `AdapterItem` implement:\n\n```kotlin\noverride fun compareTo(other: AdapterItem\u003c*\u003e): Int {\n    if (other.javaClass != javaClass)\n        return -1\n\n    val item = other as SyncItem\n\n    if (id == item.id)\n        return 0\n\n    return if (id \u003e item.id) 1 else -1\n}\n```\n\nThen call:\n\n```kotlin\n// ascending order\nrecyclerAdapter.modifyItemsAndRender { it.sorted() }\n\n// descending order\nrecyclerAdapter.modifyItemsAndRender { it.sortedDescending() }\n```\nYou can see an example in action by looking at the code in the `SyncActivity` and `SyncItem` of the demo app.\n\n### 2. Provide a custom comparator implementation\nYour items doesn't necessarily have to implement `compareTo` for sorting purposes, as you can provide also the sorting implementation outside of them, like this:\n```kotlin\nrecyclerAdapter.modifyItemsAndRender { items -\u003e\n    items.sortedWith { itemA, itemB -\u003e\n        // compare itemA and itemB and return -1, 0 or 1 (standard Java and Kotlin Comparator)\n    }\n}\n```\nThis is the recommended approach if you want to be able to sort your items by many different criteria, as you can simply pass the `Comparator` implementation of the sort type you want.\n\n### 3. Combining the two techniques\nYou can also combine the two techniques described above. This is the recommended approach if you have a list with different kind of items, and you want to perform different kind of grouping between items of different kind, maintaining the same sorting strategy for elements of the same type. You can implement `compareTo` in everyone of your items, to sort the items of the same kind, and a custom `Comparable` which will handle comparison between diffent kinds of items, like this:\n```kotlin\nrecyclerAdapter.modifyItemsAndRender { items -\u003e\n    items.sortedWith { itemA, itemB -\u003e\n        // handle ordering of items of the same type with their\n        // internal compareTo implementation\n        if (itemA.javaClass == RobotItem::class.java \u0026\u0026 itemB.javaClass == RobotItem::class.java) {\n            val first = itemA as RobotItem\n            val second = itemB as RobotItem\n            return first.compareTo(second)\n        }\n\n        if (itemA.javaClass == PersonItem::class.java \u0026\u0026 itemB.javaClass == PersonItem::class.java) {\n            val first = itemA as PersonItem\n            val second = itemB as PersonItem\n            return first.compareTo(second)\n        }\n\n        // in this case, we want to put all the PersonItems\n        // before the RobotItems in our list\n        return if (itemA.javaClass == PersonItem::class.java \u0026\u0026 itemB.javaClass == RobotItem::class.java) {\n            -1\n        } else 0\n    }\n}\n```\n\n## \u003ca name=\"butterKnife\"\u003e\u003c/a\u003eUsing ButterKnife\nYou can safely use [ButterKnife](https://github.com/JakeWharton/butterknife) in your ViewHolders, however Kotlin Android Extensions are more widely used and recommended.\n\n## \u003ca name=\"kotlinAndroidExt\"\u003e\u003c/a\u003eUsing Kotlin Android Extensions\n\n\u003e WARNING! JetBrains officially deprecated Kotlin Android Extensions starting from Kotlin 1.4.20: https://youtrack.jetbrains.com/issue/KT-42121\n\nIf you use Kotlin in your project, you can also use Kotlin Android Extensions to bind your views in ViewHolder, but be careful to not fall in a common pitfall, explained very well here: https://proandroiddev.com/kotlin-android-extensions-using-view-binding-the-right-way-707cd0c9e648\n\n## \u003ca name=\"dragDrop\"\u003e\u003c/a\u003eReorder items with drag \u0026 drop\nTo be able to change the items order with drag \u0026 drop, be sure to have imported `recycleradapter-extensions` in your project\nand just add this line:\n\n```kotlin\nrecyclerAdapter.enableDragDrop(recyclerView)\n```\nJava users have to write: `RecyclerViewExtensionsKt.enableDragDrop(recyclerAdapter, recyclerView);`\n\n## \u003ca name=\"handleClicks\"\u003e\u003c/a\u003eHandle clicks\nOne of the things which you may need is to set one or more click listeners to every item. How do you do that? Let's see an example.\n\n`item_example.xml`:\n```xml\n\u003c?xml version=\"1.0\" encoding=\"utf-8\"?\u003e\n\u003cLinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:orientation=\"vertical\" android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_margin=\"8dp\"\u003e\n\n    \u003cTextView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:id=\"@+id/title\" /\u003e\n\n    \u003cTextView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:textColor=\"@android:color/secondary_text_dark\"\n        android:id=\"@+id/subtitle\" /\u003e\n\n\u003c/LinearLayout\u003e\n```\n\n`ExampleItem.kt`:\n```kotlin\nopen class ExampleItem(private val context: Context, private val text: String)\n    : AdapterItem\u003cExampleItem.Holder\u003e(text) {\n\n    override fun getView(parent: ViewGroup): View = parent.inflating(R.layout.item_example)\n\n    override fun onFilter(searchTerm: String) = text.contains(searchTerm)\n\n    override fun bind(firstTime: Boolean, holder: Holder) {\n        holder.titleField.text = text\n        holder.subtitleField.text = \"subtitle\"\n    }\n\n    private fun onTitleClicked(position: Int) {\n        showToast(\"clicked TITLE at position $position\")\n    }\n\n    private fun onSubTitleClicked(position: Int) {\n        showToast(\"clicked SUBTITLE at position $position\")\n    }\n\n    private fun showToast(message: String) {\n        Toast.makeText(context, message, Toast.LENGTH_SHORT).show()\n    }\n\n    class Holder(itemView: View)\n        : RecyclerAdapterViewHolder(itemView), LayoutContainer {\n\n        override val containerView: View?\n            get() = itemView\n\n        internal val titleField: TextView by lazy { title }\n        internal val subtitleField: TextView by lazy { subtitle }\n\n        init {\n            titleField.setOnClickListener {\n                withAdapterItem\u003cExampleItem\u003e {\n                    onTitleClicked(adapterPosition)\n                }\n            }\n\n            subtitleField.setOnClickListener {\n                withAdapterItem\u003cExampleItem\u003e {\n                    onSubTitleClicked(adapterPosition)\n                }\n            }\n        }\n    }\n}\n```\n\nAs you can see, to handle click events on a view, you have to create a click listener in the ViewHolder and propagate an event to the `AdapterItem`:\n```kotlin\ntitleField.setOnClickListener {\n    withAdapterItem\u003cExampleItem\u003e {\n        onTitleClicked(adapterPosition)\n    }\n}\n```\nYou can call any method defined in your `AdapterItem` and pass whatever parameters you want. It's important that you honor nullability, as each ViewHolder has a weak reference to its `AdapterItem`, so to prevent crashes at runtime always use the form:\n\n```kotlin\nwithAdapterItem\u003cExampleItem\u003e {\n    // methods to call on the adapter item\n}\n```\n\nIn this case, the following method has been implemented to handle title clicks:\n```kotlin\nprivate fun onTitleClicked(position: Int) {\n    showToast(\"clicked TITLE at position $position\")\n}\n```\nLook at the [event lifecycle](#eventLifecycle) to have a complete understaning.\n\n## \u003ca name=\"handleItemStatus\"\u003e\u003c/a\u003eHandle item status and save changes into the model\nIt's possible to also change the status of the model associated to an item directly from the ViewHolder. Imagine we need to persist a toggle button status when the user presses on it. How do we do that? Let's see an example.\n\n`item_text_with_button.xml`:\n```xml\n\u003c?xml version=\"1.0\" encoding=\"utf-8\"?\u003e\n\u003cLinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:orientation=\"vertical\" android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_margin=\"8dp\"\u003e\n\n    \u003cTextView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:id=\"@+id/textView\" /\u003e\n\n    \u003cToggleButton\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:id=\"@+id/toggleButton\" /\u003e\n\u003c/LinearLayout\u003e\n```\n\n`TextWithButtonItem.kt`:\n```kotlin\nclass TextWithButtonItem(private val text: String) : AdapterItem\u003cTextWithButtonItem.Holder\u003e(text) {\n\n    private var pressed = false\n\n    override fun onFilter(searchTerm: String) = text.contains(searchTerm)\n\n    override fun getView(parent: ViewGroup): View = parent.inflating(R.layout.item_text_with_button)\n\n    override fun bind(firstTime: Boolean, holder: Holder) {\n        holder.textViewField.text = text\n        holder.buttonField.isChecked = pressed\n    }\n\n    class Holder(itemView: View)\n        : RecyclerAdapterViewHolder(itemView), LayoutContainer {\n\n        override val containerView: View?\n            get() = itemView\n\n        internal val textViewField: TextView by lazy { textView }\n        internal val buttonField: ToggleButton by lazy { toggleButton }\n\n        init {\n            buttonField.setOnClickListener {\n                withAdapterItem\u003cTextWithButtonItem\u003e {\n                    pressed = buttonField.isChecked\n                    notifyItemChanged()\n                }\n            }\n        }\n    }\n}\n```\nIn the `Holder` we have added a click listener to the `ToggleButton`. When the user presses the toggle button, the `AdapterItem` `pressed` status gets changed and then the `RecyclerAdapter` gets notified that the model has been changed by invoking `notifyItemChanged()`. This triggers the rebinding of the ViewHolder to reflect the new model.\n\nSo, to recap, the \u003ca name=\"eventLifecycle\"\u003e\u003c/a\u003eevent lifecycle is:\n```kotlin\n// gets ViewHolder's AdapterItem\nwithAdapterItem\u003cYourAdapterItem\u003e {\n    // methods to call on the adapter item\n\n    // (optional)\n    // if you want the current ViewHolder to be rebinded, call\n    notifyItemChanged()\n}\n```\nAs a rule of thumb, if an event does not directly change the UI, you should not call `notifyItemChanged()`.\n\n## \u003ca name=\"itemsSelection\"\u003e\u003c/a\u003eSingle and Multiple selection of items\nOften RecyclerViews are used to implement settings toggles, bottom sheets and other UI to perform selections. What is needed in the vast majority of the cases is:\n\n* a way to select a single item from a list of items\n* a way to select many items from a list of items\n\nTo complicate things, many times a single RecyclerView has to contain various groups of selectable items, for example let's imagine an online order form in which the user has to select:\n\n* a payment method from a list of supported ones (only one selection)\n* additions like extra support, gift packaging, accessories, ... (many selections)\n* shipping address (only one selection)\n* billing address (only one selection)\n\nSo it gets pretty complicated, huh 😨? Don't worry, `RecyclerAdapter` to the rescue! 🙌🏼\n\nCheck the example app implementations in GroupsSelectionActivity and SubordinateGroupsSelectionActivity to see what you can achieve!\n\n![Subordinate Groups Selections](images/subordinate-groups.gif)\n\n## \u003ca name=\"leaveBehind\"\u003e\u003c/a\u003eLeave Behind pattern example implementation\nIn the demo app provided with the library, you can also see how to implement the [leave behind material design pattern](https://material.io/guidelines/components/lists-controls.html#lists-controls-types-of-list-controls). All the changes involved into the implementation can be seen in [this commit](https://github.com/gotev/recycler-adapter/commit/fa240519025f98ba609395034f42e89d5bb777fd). This implementation has not been included into the base library deliberately, to avoid depending on external libraries just for a single kind of item implementation. You can easily import the needed code in your project from the demo app sources if you want to have leave behind implementation.\n\n## \u003ca name=\"lockScroll\"\u003e\u003c/a\u003eLock scrolling while inserting\nWhen dynamically loading many data at once in the RecyclerView, specially when we are inserting new items at the first position, the default behavior of the RecyclerView, which scrolls down automatically may not be what we want. To lock the scrolling while inserting new items, be sure to have included `recycleradapter-extensions` in your project, then simply call:\n\n```kotlin\nrecyclerAdapter.lockScrollingWhileInserting(layoutManager)\n```\nTo get a better comprehension of this behavior, try commenting `lockScrollingWhileInserting` in [SyncActivity](https://github.com/gotev/recycler-adapter/blob/master/app/demo/src/main/java/net/gotev/recycleradapterdemo/activities/SyncActivity.kt) and run the demo app again pressing the `shuffle` button to see the difference.\n\n## \u003ca name=\"contributors\"\u003e\u003c/a\u003eContributors and Credits\nThanks to:\n* [Kristiyan Petrov](https://github.com/kristiyanP) for the beta testing and code review\n* [Nicola Gallazzi](https://github.com/ngallazzi) for helping transitioning the library to AndroidX\n* [Federico Monti](https://github.com/Fed93) for helping integrating the paging library\n* [FlatIcon](https://flaticon.com) for the demo app's icon\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgotev%2Frecycler-adapter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgotev%2Frecycler-adapter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgotev%2Frecycler-adapter/lists"}