{"id":13428715,"url":"https://github.com/square/cycler","last_synced_at":"2025-06-13T01:39:38.196Z","repository":{"id":43051539,"uuid":"232632652","full_name":"square/cycler","owner":"square","description":null,"archived":false,"fork":false,"pushed_at":"2023-03-18T21:56:03.000Z","size":217,"stargazers_count":792,"open_issues_count":10,"forks_count":27,"subscribers_count":19,"default_branch":"main","last_synced_at":"2024-11-21T19:31:48.538Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"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/square.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null}},"created_at":"2020-01-08T18:36:46.000Z","updated_at":"2024-10-31T01:45:59.000Z","dependencies_parsed_at":"2022-07-09T02:01:24.259Z","dependency_job_id":"a50a2977-4544-431b-b994-cbab49c423cb","html_url":"https://github.com/square/cycler","commit_stats":null,"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/square%2Fcycler","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/square%2Fcycler/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/square%2Fcycler/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/square%2Fcycler/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/square","download_url":"https://codeload.github.com/square/cycler/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243814905,"owners_count":20352037,"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","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":[],"created_at":"2024-07-31T01:01:03.367Z","updated_at":"2025-03-16T01:33:25.491Z","avatar_url":"https://github.com/square.png","language":"Kotlin","funding_links":[],"categories":["Libraries"],"sub_categories":[],"readme":"# Square Cycler – a RecyclerView API\n\nThe Square Cycler API allows\nyou to **easily configure** an Android RecyclerView declaratively\nin a **succinct way**.\n\n## Design principles\n\n- It should be **declarative**. You tell us what you want, not what to do.\n- It should have **all the code regarding one type of row together**. The less switch statements the better (some existing libraries and Android recycler itself group all creation together, and all binder together elsewhere; that's close to the metal but far from developer needs).\n- It should be able to cover **common needs**, specially making adapter access unnecessary. Access to the `RecyclerView` for ad-hoc configuration is allowed.\n- It should be **strongly typed**.\n- It should include **common features**: edge decoration, sticky headers, etc.\n- It should make it **easy to inflate** rows or to create them programmatically.\n- It should make it **easy to create common custom items**.\n\n## How to use it\n\n- Configure the recycler view when you create your view.\n- Provide data each time it changes.\n\n### Configuring block\n\nThe configuring block is the essence of the recycler view.\nIt contains all the row definitions and how to bind data.\n\nYou can ask the API to create the RecyclerView object for you – using the `create` method – or \nconfigure an existing instance – through the `adopt` method. The latter is useful if you already\nhave a layout which the recycler view is part of.\n\n**Examples:**\n\n```kotlin\nval recycler = Recycler.create\u003cItemType\u003e(context, id = R.id.myrecycler) {\n  ...\n}\n```\n\n```kotlin\nval recycler = Recycler.adopt(findViewById(R.id.my_recycler)) {\n  ...\n}\n```\n\nIn both cases you will receive a Recycler object which\nrepresents the RecyclerView and allows you to set data afterwards.\n\nThe configuring block will have some general configurations,\nfor instance an item comparator, and a row definition for\nevery type of row you need.\n\n#### Generics\n\nThe generics used along this documentation are as follow:\n\n- `I: ` ItemType. General type for all the data items of the rows.\n- `S: ` ItemSubType. Data item type for the particular row being defined.\n- `V: ` ViewType. View type for the particular row being defined.\n\n#### Row definitions\n\nUsing a layout:\n\n```kotlin\nrow\u003cI, S, V\u003e {\n  forItemsWhere { subitem -\u003e ...boolean... }\n  create(R.layout.my_layout) {\n    // you can get references to sub-elements inside view\n    val subView = view.findViewById(...)\n    bind { subItem -\u003e\n      // assign values from subItem to view or sub-elements\n    }\n  }\n  ...more row options...\n}\n```\n\nThe subtype `S` will automatically make the row definitions\nonly be used for that type of item `I`.\n\n`forItemsWhere` clause is optional. In case you need to filter\nby an arbitrary predicate on `S` (notice you don't need to cast).\n\n`create` will inflate the layout and assign it to a `var view: V`.\nYou can get references to sub-components using `findViewById`.\n\n`bind` receives the subItem (again, already cast to `S`).\nYou can use `view` and your own captured references from\nthe `create` block to assign values. Notice that you don't need to\ncast `view as V`. It's already of that type.\n\nGeneral approach:\n\n```kotlin\nrow\u003cI, S, V\u003e {\n  forItemsWhere { subitem -\u003e ...boolean... }\n  create { context -\u003e\n    view = MyView(context)\n    // you can get references to sub-elements inside view\n    val subView = view.findViewById(...)\n    bind { subItem -\u003e\n      // assign values from subItem to view or sub-elements\n    }\n  }\n  ...more row options...\n}\n```\n\nThis is the general case. Instead of inflating a layout, `create`\nprovides a context for you to create a view of type `V` and assign\nit to `view`. As usual, you can use that `view` reference or any\nother reference you've obtained inside the `bind` block.\n\n#### Extra item definitions\n\nRecycler views allow for the inclusion of one extra (but optional)\nitem. This is useful when you want to show your state.\nFor example: \"no results\" or \"loading more...\".\nThe `extraItem` is independent from the main data list and\ndoesn't need to be of type `I`.\n\nDefinitions for `extraItem`s are analogous to normal rows\nand follow the same convention. However, the definitions are only applied \nto the extra item you provide along with the data (if any).\n\n```kotlin\nextraItem\u003cI, S, V\u003e {\n  forItemsWhere { subitem -\u003e ...boolean... }\n  create { context -\u003e\n    ...\n    bind { subItem -\u003e ... }\n  }\n  ...more row options...\n}\n```\n\nNotice that you can define several different `extraItem`\nblocks, with the same or different sub-types `S` and\noptional `forItemWhere`.\n\n`bind` is also provided in case your extra item has data.\nImagine you are filtering by fruit. If you've selected \"apples\"\nyou want to show \"No more apples\" instead of \"No more fruits\".\nThat can be achieved with an extra item of type\n`NoMore(val fruitName: String)`.\n\n#### More row options\n\nRecycler API offers an extension mechanism.\nExtensions are useful for cross-cutting concerns like\nedges or headers which will be discussed separately.\n\nThese extensions will be configured in the same way,\nthrough a definition block.\n\nExtensions might offer special configuration for certain\ntypes of rows. For example, edges can define a default\nedge configuration, but use different values for the rows\nof type `Banana`. In that case the `row\u003cBanana\u003e` definition\nwill include its special configuration.\n\nSee extensions section for more details.\n\n#### General configuration\n\nThe RecyclerView uses certain general definitions that can\nbe configured here as well.\n\n`stableIds { item -\u003e ...long... }`\n\nIf you provide a function that returns an id of type `Long`\nfor every item in the data, the recycler view will be able\nto identify unchanged items when data is updated, and\nanimate them accordingly.\n\n`itemComparator = ...`\n\nWhen data is updated the RecyclerView compares both datasets\nto find which item moved where, and check if they changed any\ndata at all.\n\nAndroid's RecyclerView's can do that calculation but it needs\nto compare the items. The developer must provide the comparison.\nYou can provide an `ItemComparator` implementation which is\nsimpler than the required `DiffUtil.Callback` one.\n\nAn `ItemComparator` provides two methods:\n- `areSameIdentity` returns true if they represent the\nsame thing (even if data changed).\n- `areSameContent` tells if any data changed, requiring re-binding.\n\nIf your items are `Comparable` or you have a `Comparator`\nyou can create an automatic `ItemComparator`. Just use:\n\n- `fun itemComparatorFor(Comparator\u003cT\u003e): ItemComparator\u003cT\u003e`\n- `fun naturalItemComparator(): ItemComparator\u003cT\u003e` if `T` is `Comparable\u003cT\u003e`\n\nIt will implement both: identity and content-comparison\nbased on `Comparator` or `Comparable`. That means that items\nwill either be different or identical, therefore never updated.\nBut for immutable (or practically immutable) items\nit works pretty well.\n\n## Data providing\n\nOnce you configured your recycler view you just need to\ngive it data.\n\nThe `Recycler` object returned by the configuring block\nrepresents your recycler view. It has three properties:\n\n- `view`: the RecyclerView. You can add it to your layout\n  if it was created by the API.\n- `data`: the list of items to show.\n- `extraItem`: the extra item to add to the end (or null).\n\nNotice that `data` is of type `DataSource\u003cI\u003e`.\n\n`DataSource` is a simplified `List` interface:\n\n```kotlin\ninterface DataSource\u003cout T\u003e {\n  operator fun get(i: Int): T\n  val size: Int\n}\n```\n\nYou can convert an `Array` or a `List` to a DataSource\nusing the extension method `toDataSource()`:\n`arrayOf(1, 2, 3).toDataSource()`.\n\nThe advantage over requiring a Kotlin `List` is that you\ncan implement your arbitrary DataSource without having to\nimplement the whole `List` interface, which is bigger.\n## Extensions\n\nExtensions are a mechanism to add simple-to-configure features\nto Recyclers without adding dependencies to this library.\n\n### Row type extensions\n\nYou can create extensions for common custom views in your project:\n\n```kotlin\nmyCustomItem\u003cI, S\u003e {\n  forItemsWhere { ... }\n  bind { item, view -\u003e\n    view.title  = ...\n    view.message = ...\n    ...\n  }\n}\n```\n\nThe extension method just needs to use a different row definition\nmethod that lets you define how to create the view by separate.\n\nFor instance:\n\n```kotlin\n/**\n * Extension method for a custom item, allowing full control.\n * ```\n * myCustomItem\u003cI, S\u003e { // this: BinderRowSpec\u003c...\u003e\n *    // you can configure extra stuff:\n *   forItemsWhere { ... }\n *   // and then define your bind lambda:\n *   bind { item, view -\u003e\n *     view.title  = ...\n *     view.message = ...\n *     ...\n *   }\n * }\n * ```\n */\n@RecyclerApiMarker\ninline fun \u003cI : Any, reified S : I\u003e Recycler.Config\u003cI\u003e.myCustomItem(\n  crossinline specBlock: BinderRowSpec\u003cI, S, CustomView\u003e.() -\u003e Unit\n) {\n  row(\n      creatorBlock = { creatorContext -\u003e\n        CustomView(creatorContext.context)\n        .apply { ... }\n      },\n      specBlock = specBlock\n  )\n}\n\n/**\n * Extension method for passing just a bind lambda.\n * ```\n * myCustomItem\u003cI, S\u003e { item, view -\u003e\n *   view.title  = ...\n *   view.message = ...\n *   ...\n * }\n * ```\n */\n @RecyclerApiMarker\n inline fun \u003cI : Any, reified S : I\u003e Recycler.Config\u003cI\u003e.myCustomItem(\n   noinline bindBlock: (S, CustomView) -\u003e Unit\n ) {\n   row(\n       creatorBlock = { creatorContext -\u003e\n         CustomView(creatorContext.context)\n        .apply { ... }\n       },\n       bindBlock = bindBlock\n   )\n }\n```\n\nNotice:\n- You don't need to declare extension methods for each row.\n  It's just a shorthand for those things your project uses repeatedly.\n- You can also use analogous methods that provide the index of the item\n  in binding.\n\n### Decoration extensions\n\n```\nTODO: code and documentation need to be added.\n```\n\n# License\n\nCopyright 2019 Square Inc.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\nhttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsquare%2Fcycler","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsquare%2Fcycler","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsquare%2Fcycler/lists"}