{"id":13611337,"url":"https://github.com/rongi/klaster","last_synced_at":"2025-04-13T04:34:04.837Z","repository":{"id":79048496,"uuid":"132181599","full_name":"rongi/klaster","owner":"rongi","description":"Declare RecyclerView adapters in a functional way, without boilerplate and subclassing. No compromises on flexibility. If it's possible to do something by subclassing, it's possible to do it with this library. ","archived":false,"fork":false,"pushed_at":"2019-05-27T23:45:16.000Z","size":375,"stargazers_count":363,"open_issues_count":3,"forks_count":9,"subscribers_count":5,"default_branch":"master","last_synced_at":"2024-08-01T19:57:17.490Z","etag":null,"topics":["android","recyclerview","recyclerview-adapter"],"latest_commit_sha":null,"homepage":"","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rongi.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":null,"security":null,"support":null,"governance":null}},"created_at":"2018-05-04T19:36:56.000Z","updated_at":"2024-04-18T23:08:11.000Z","dependencies_parsed_at":"2023-03-05T22:15:20.203Z","dependency_job_id":null,"html_url":"https://github.com/rongi/klaster","commit_stats":null,"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rongi%2Fklaster","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rongi%2Fklaster/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rongi%2Fklaster/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rongi%2Fklaster/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rongi","download_url":"https://codeload.github.com/rongi/klaster/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":223568182,"owners_count":17166612,"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":["android","recyclerview","recyclerview-adapter"],"created_at":"2024-08-01T19:01:54.333Z","updated_at":"2024-11-07T18:30:20.383Z","avatar_url":"https://github.com/rongi.png","language":"Kotlin","funding_links":[],"categories":["Kotlin"],"sub_categories":[],"readme":"[![](https://jitpack.io/v/rongi/klaster.svg)](https://jitpack.io/#rongi/klaster)\n\n# Declare RecyclerView adapters without boilerplate\n\nWith this library:\n\n```kotlin\nprivate fun articlesAdapter() = Klaster.get()\n  .itemCount { articles.size }\n  .view(R.layout.list_item, layoutInflater)\n  .bind { position -\u003e\n    val article = articles[position]\n    item_text.text = article.title\n    itemView.onClick = { presenter.onArticleClick(article) }\n  }\n  .build()\n```\n\nThe same adapter declared by subclassing:\n\n```java\nprivate class ArticlesAdapter(\n  private val layoutInflater: LayoutInflater\n) : RecyclerView.Adapter\u003cArticlesViewHolder\u003e() {\n\n  val onItemClick: (() -\u003e Unit)? = null\n\n  override fun getItemCount(): Int {\n    return articles.size\n  }\n\n  override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ArticlesViewHolder {\n    val view = layoutInflater.inflate(R.layout.list_item, parent, false)\n    return ArticlesViewHolder(view)\n  }\n\n  override fun onBindViewHolder(holder: ArticlesViewHolder, position: Int) {\n    val article = articles[position]\n    holder.articleTitle.text = article.title\n    holder.itemView.onClick = { onItemClick?.invoke() }\n  }\n\n  private class ArticlesViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {\n    val articleTitle: TextView = itemView.findViewById(R.id.item_text)\n  }\n\n}\n```\n\nEver wondered why you need to declare an extra class for each of your adapters when essentially an adapter is just two functions combined together: `onCreateViewHolder()` and `onBindViewHolder()`? Why can't we have something that takes these two functions and construct a proper adapter for us? Well, you can with this library. And with the power of Kotlin Android Extensions, you don't even need to create `ViewHolder` classes anymore.\n\nThis library doesn't compromise on flexibility and doesn't hide stuff from you. If it's possible to do something by declaring a new adapter class, it's possible to do it with this library also. It's just a more concise way to declare RecyclerView adapters. \n\nIf you ever feel like this adapter builder is too verbose, well, with Kotlin extension functions you can tailor it for your needs. Or you can build more complex things on top of it. Again, just with extension functions.\n\nUsage\n=====\n\n## Basic\n\n```kotlin\nprivate fun createAdapter() = Klaster.get()\n  .itemCount { articles.size }\n  .view(R.layout.list_item, layoutInflater)\n  .bind { position -\u003e\n    val article = articles[position]\n    item_text.text = article.title\n    itemView.onClick = { presenter.onArticleClick(article) }\n  }\n  .build()\n```\n\nThis is how it can look like inside an `Activity` implemented with MVP.\n\n```kotlin\nclass SimpleExampleActivity : AppCompatActivity(), SimpleExampleView {\n\n  private lateinit var adapter: RecyclerView.Adapter\u003c*\u003e\n\n  private lateinit var presenter: SimpleExamplePresenter\n\n  private var articles: List\u003cArticle\u003e = emptyList()\n\n  override fun onCreate(...) {\n    super.onCreate(savedInstanceState)\n    setContentView(R.layout.recycler_view_activity)\n    recycler_view.init(this)\n\n    adapter = createAdapter()\n\n    recycler_view.adapter = adapter\n\n    presenter = SimpleExamplePresenter(view = this)\n    presenter.onViewCreated()\n  }\n\n  override fun showArticles(articles: List\u003cArticle\u003e) {\n    this.articles = articles\n    adapter.notifyDataSetChanged()\n  }\n\n  private fun createAdapter() = Klaster.get()\n    .itemCount { articles.size }\n    .view(R.layout.list_item, layoutInflater)\n    .bind { position -\u003e\n      val article = articles[position]\n      item_text.text = article.title\n      itemView.onClick = { presenter.onArticleClick(article) }\n    }\n    .build()\n\n}\n```\n\n## Multiple view types\n\n```kotlin\nclass MultipleViewTypesExampleActivity : AppCompatActivity(), MultipleViewTypesExampleView {\n  // ...\n\n  private fun createAdapter() = Klaster.get()\n    .itemCount { listItems.size }\n    .getItemViewType { position -\u003e\n      when (listItems[position]) {\n        is ArticleViewData -\u003e 0\n        is HeaderViewData -\u003e 1\n      }\n    }\n    .view { viewType, parent -\u003e\n      when (viewType) {\n        0 -\u003e layoutInflater.inflate(R.layout.list_item, parent, false)\n        1 -\u003e layoutInflater.inflate(R.layout.header, parent, false)\n        else -\u003e throw IllegalStateException(\"Unknown view type: $viewType\")\n      }\n    }\n    .bind { position -\u003e\n      val listItem = listItems[position]\n\n      when (listItem) {\n        is ArticleViewData -\u003e {\n          item_text.text = listItem.article.title\n          itemView.onClick = { presenter.onArticleClick(listItem.article) }\n        }\n        is HeaderViewData -\u003e {\n          header_text.text = listItem.headerText\n        }\n      }\n    }\n    .build()\n\n  private var listItems: List\u003cListItemViewData\u003e = emptyList()\n\n  override fun showListItems(listItems: List\u003cListItemViewData\u003e) {\n    this.listItems = listItems\n    adapter.notifyDataSetChanged()\n  }\n\n  sealed class ListItemViewData\n\n  data class HeaderViewData(\n    val headerText: String\n  ): ListItemViewData()\n\n  data class ArticleViewData(\n    val article: Article\n  ): ListItemViewData()\n\n}\n```\n\nFull example is a part of library's sample app and can be found [here](https://github.com/rongi/klaster/tree/master/sample-app/src/main/java/com/github/rongi/klaster/samples/examples/multipleviewtypes).\n\n## With a custom `ViewHolder`\n\n```kotlin\nprivate fun createAdapter() = Klaster.withViewHolder\u003cMyViewHolder\u003e()\n  .itemCount { articles.size }\n  .viewHolder { _, parent -\u003e\n    val view = layoutInflater.inflate(R.layout.list_item, parent, false)\n    MyViewHolder(view)\n  }\n  .bind { position -\u003e\n    val article = articles[position]\n    articleTitle.text = article.title\n    itemView.onClick = { presenter.onArticleClick(article) }\n  }\n  .build()\n```\n\n## But what if I need to overload more functions?\n\nWith this builder, you can \"overload\" any function you want that can be overloaded by traditional subclassing of `RecyclerView.Adapter`.\n\n```kotlin\nfun createAdapter(layoutInflater: LayoutInflater) = Klaster.get()\n  .itemCount { articles.size }\n  .getItemViewType { position -\u003e position % 2 }\n  .view { viewType, parent -\u003e\n    when (viewType) {\n      ITEM_TYPE_1 -\u003e layoutInflater.inflate(R.layout.list_item1, parent, false)\n      ITEM_TYPE_2 -\u003e layoutInflater.inflate(R.layout.list_item2, parent, false)\n      else -\u003e throw IllegalStateException(\"Unknown type: $viewType\")\n    }\n  }\n  .bind { position -\u003e\n    val article = articles[position]\n    item_text.text = article.title\n  }\n  .bind { position, payloads -\u003e }\n  .getItemId {  }\n  .setHasStableIds {  }\n  .onAttachedToRecyclerView {  }\n  .onDetachedFromRecyclerView {  }\n  .registerAdapterDataObserver {  }\n  .unregisterAdapterDataObserver {  }\n  .onFailedToRecycleView {  }\n  .onViewAttachedToWindow {  }\n  .onViewDetachedFromWindow {  }\n  .onViewRecycled {  }\n  .build()\n```\n\n## Functional way to create adapters\n\nBut does list of items really belong to the `Activity`? Can I achieve better separation of concerns using this library? Yes, and here is an example of how it can be done in a clean and beautiful functional way without subclassing.\n\nThe function defined below, `createAdapter()`, creates an adapter backed by a simple `List` of items. This function returns two things:\n\n1. A `RecyclerView.Adapter`, which you can give to your `RecyclerView`.\n2. A `ListViewPresenter` interface. This interface you can use to update contents of your adapter, it has a single method that replaces all the items in the adapter with the new ones. You can even pass this presenter into your main presenter as a dependency.\n\n```kotlin\nprivate fun createAdapter(\n  layoutInflater: LayoutInflater,\n  onItemClick: (Article) -\u003e Unit\n): Pair\u003cRecyclerView.Adapter\u003c*\u003e, ListViewPresenter\u003e {\n  var articles: List\u003cArticle\u003e = emptyList()\n\n  val adapter = Klaster.get()\n    .itemCount { articles.size }\n    .view(R.layout.list_item, layoutInflater)\n    .bind { position -\u003e\n      val article = articles[position]\n      item_text.text = article.title\n      itemView.onClick = { onItemClick(article) }\n    }\n    .build()\n\n  val listViewPresenter = object : ListViewPresenter {\n    override fun setItems(items: List\u003cArticle\u003e) {\n      articles = items\n      adapter.notifyDataSetChanged()\n    }\n  }\n\n  return adapter to listViewPresenter\n}\n\ninterface ListViewPresenter {\n  fun setItems(items: List\u003cArticle\u003e)\n}\n```\n\nWhy is this preferred over inheritance? Because it's simpler (less interweaved) and you can achieve a better separation of concerns this way. For example `ListViewPresenter` implementation can be extracted from this function and reused for all other cases where the adapter is backed by a `List`.\n\n## Create your own extensions\n\nYou can tailor the builder for your needs by creating your own, even more elegant APIs using Kotlin extension functions. For example, if you want to create an adapter for a `List` of items that never change, then you may want to have a builder that can do things like this (notice no `itemCount()` function).\n\n```kotlin\nfun createAdapter(articles: List\u003cArticle\u003e, layoutInflater: LayoutInflater) = Klaster.get()\n  .view(R.layout.list_item, layoutInflater)\n  .bind(articles) { article, position -\u003e\n    item_text.text = article.title\n  }\n  .build()\n```\n\nYou can achieve it with this extension function.\n\n```kotlin\nfun \u003cT\u003e KlasterBuilder.bind(items: List\u003cT\u003e, binder: KlasterViewHolder.(item: T, position: Int) -\u003e Unit): KlasterBuilder =\n  this.itemCount(items.size)\n    .bind { position -\u003e\n      val item = items[position]\n      binder(item, position)\n    }\n```\n\nWhat if you want your adapter to get items from a list that can change? You may want to have a builder that can do this then.\n\n```kotlin\nfun createAdapter(articles: () -\u003e List\u003cArticle\u003e, layoutInflater: LayoutInflater) = Klaster.get()\n  .view(R.layout.list_item, layoutInflater)\n  .bind(articles) { article, position -\u003e\n    item_text.text = article.title\n  }\n  .build()\n```\n\nYou can get that with this extension function.\n\n```kotlin\nfun \u003cT\u003e KlasterBuilder.bind(items: () -\u003e List\u003cT\u003e, binder: KlasterViewHolder.(item: T, position: Int) -\u003e Unit): KlasterBuilder =\n  this.itemCount { items().size }\n    .bind { position -\u003e\n      val item = items()[position]\n      binder(item, position)\n    }\n```\n\nDownload\n========\n\n```groovy\nallprojects {\n    repositories {\n        ...\n        maven { url 'https://jitpack.io' }\n    }\n}\n```\n\n```groovy\ndependencies {\n    implementation 'com.github.rongi:klaster:0.3.5'\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frongi%2Fklaster","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frongi%2Fklaster","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frongi%2Fklaster/lists"}