{"id":15030193,"url":"https://github.com/evant/binding-collection-adapter","last_synced_at":"2025-05-14T20:05:55.139Z","repository":{"id":32928942,"uuid":"36524737","full_name":"evant/binding-collection-adapter","owner":"evant","description":"Easy way to bind collections to listviews and recyclerviews with the new Android Data Binding framework","archived":false,"fork":false,"pushed_at":"2024-05-23T02:07:34.000Z","size":720,"stargazers_count":1920,"open_issues_count":31,"forks_count":255,"subscribers_count":48,"default_branch":"main","last_synced_at":"2025-04-06T10:08:07.736Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Java","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/evant.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2015-05-29T19:29:31.000Z","updated_at":"2025-03-20T02:53:28.000Z","dependencies_parsed_at":"2024-11-19T17:36:34.100Z","dependency_job_id":"44d9a2de-a95f-4da4-9d4e-a6c99855c08a","html_url":"https://github.com/evant/binding-collection-adapter","commit_stats":{"total_commits":159,"total_committers":19,"mean_commits":8.368421052631579,"dds":"0.23899371069182385","last_synced_commit":"ac7972e14a21e28d2fb74f1470caedec6b84d985"},"previous_names":[],"tags_count":16,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/evant%2Fbinding-collection-adapter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/evant%2Fbinding-collection-adapter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/evant%2Fbinding-collection-adapter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/evant%2Fbinding-collection-adapter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/evant","download_url":"https://codeload.github.com/evant/binding-collection-adapter/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248719059,"owners_count":21150683,"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-09-24T20:12:42.823Z","updated_at":"2025-04-13T13:24:57.990Z","avatar_url":"https://github.com/evant.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# BindingCollectionAdapter\n[![Maven Central](https://maven-badges.herokuapp.com/maven-central/me.tatarka.bindingcollectionadapter2/bindingcollectionadapter/badge.svg?style=flat)](https://maven-badges.herokuapp.com/maven-central/me.tatarka.bindingcollectionadapter2/bindingcollectionadapter)\n\nEasy way to bind collections to listviews and recyclerviews with the new [Android Data Binding framework](https://developer.android.com/tools/data-binding/guide.html).\n\n## Download\n\nIf you are using androidx use version `4.0.0`, this also uses databinding v2\n\n```groovy\nimplementation 'me.tatarka.bindingcollectionadapter2:bindingcollectionadapter:4.0.0'\nimplementation 'me.tatarka.bindingcollectionadapter2:bindingcollectionadapter-recyclerview:4.0.0'\nimplementation 'me.tatarka.bindingcollectionadapter2:bindingcollectionadapter-viewpager2:4.0.0'\n```\n\nor use the previous stable version\n\n```groovy\nimplementation 'me.tatarka.bindingcollectionadapter2:bindingcollectionadapter:2.2.0'\nimplementation 'me.tatarka.bindingcollectionadapter2:bindingcollectionadapter-recyclerview:2.2.0'\n```\n\n## Usage\n\nYou need to provide your items and an `ItemBinding` to bind to the layout. You should use an\n`ObservableList` to automatically update your view based on list changes. However, you can\nuse any `List` if you don't need that functionality.\n\n```java\npublic class ViewModel {\n  public final ObservableList\u003cString\u003e items = new ObservableArrayList\u003c\u003e();\n  public final ItemBinding\u003cString\u003e itemBinding = ItemBinding.of(BR.item, R.layout.item);\n}\n```\n\nThen bind it to the collection view with `app:items` and `app:itemBinding`.\n\n```xml\n\u003c!-- layout.xml --\u003e\n\u003clayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\u003e\n    \u003cdata\u003e\n      \u003cimport type=\"com.example.R\" /\u003e\n      \u003cvariable name=\"viewModel\" type=\"com.example.ViewModel\"/\u003e\n    \u003c/data\u003e\n\n    \u003cListView\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"match_parent\"\n      app:items=\"@{viewModel.items}\"\n      app:itemBinding=\"@{viewModel.itemBinding}\"/\u003e\n\n    \u003candroidx.recyclerview.widget.RecyclerView\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"match_parent\"\n      app:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\"\n      app:items=\"@{viewModel.items}\"\n      app:itemBinding=\"@{viewModel.itemBinding}\"/\u003e\n\n    \u003candroidx.viewpager.widget.ViewPager\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"match_parent\"\n      app:items=\"@{viewModel.items}\"\n      app:itemBinding=\"@{viewModel.itemBinding}\"/\u003e\n\n    \u003cSpinner\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"match_parent\"\n      app:items=\"@{viewModel.items}\"\n      app:itemBinding=\"@{viewModel.itemBinding}\"\n      app:itemDropDownLayout=\"@{R.layout.item_dropdown}\"/\u003e\n\u003c/layout\u003e\n```\n\nIn your item layout, the collection item will be bound to the variable with the\nname you passed into the `ItemBinding`.\n\n```xml\n\u003c!-- item.xml --\u003e\n\u003clayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\u003e\n    \u003cdata\u003e\n      \u003cvariable name=\"item\" type=\"String\"/\u003e\n    \u003c/data\u003e\n\n    \u003cTextView\n      android:id=\"@+id/text\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:text=\"@{item}\"/\u003e\n\u003c/layout\u003e\n```\n\nNote: if `app:itemBinding` is null, then the adapter will be set to null. This is useful if you\ndon't have an `itemBinding` right away (ex: need to wait till you load data). If you aren't seeing\nany views, make sure you have `itemBinding` defined!\n\n## Multiple View Types\n\nYou can use multiple view types by using `OnItemBind` instead. You can still bind\nit to the view with `app:itemBinding`.\n\n```java\npublic final OnItemBind\u003cString\u003e onItemBind = new OnItemBind\u003cString\u003e() {\n  @Override\n  public void onItemBind(ItemBinding itemBinding, int position, String item) {\n    itemBinding.set(BR.item, position == 0 ? R.layout.item_header : R.layout.item);\n  }\n};\n```\n\nIf you are binding to a ListView, you must also provide the number of item types you have with\n`app:itemTypeCount=\"@{2}`.\n\nNote that `onItemBind` is called many times so you should not do any complex processing in there. If \nyou don't need to bind an item at a specific position (a static footer for example) you can use\n`ItemBinding.VAR_NONE` as the variable id.\n\n## Bind Extra Variables\n\nYou can bind additional variables to items in the list with `itemBinding.bindExtra(BR.extra, value)`.\nThis is useful for components that you don't want the items themselves to care about. For example, \nyou can implement an item click listener as such\n\n```java\npublic interface OnItemClickListener {\n    void onItemClick(String item);\n}\n\nOnItemClickListener listener = ...;\nItemBinding\u003cItem\u003e itemBinding = ItemBinding.\u003cItem\u003eof(BR.item, R.layout.item)\n    .bindExtra(BR.listener, listener);\n```\n\n```xml\n\u003clayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\u003e\n    \u003cdata\u003e\n      \u003cvariable name=\"item\" type=\"String\"/\u003e\n      \u003cvariable name=\"listener\" type=\"OnItemClickListener\"/\u003e\n    \u003c/data\u003e\n\n    \u003cTextView\n      android:id=\"@+id/text\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:onClick=\"@{() -\u003e listener.onItemClick(item)}\"\n      android:text=\"@{item}\"/\u003e\n\u003c/layout\u003e\n```\n\n## Additional Adapter Configuration\n\n### ListView\n\nYou can set a callback to give an id for each item in the list with\n\n```java\nadapter.setItemIds(new BindingListViewAdapter.ItemIds\u003cT\u003e() {\n  @Override\n  public long getItemId(int position, T item) {\n    return // Calculate item id.\n  }\n});\n```\nor by defining `app:itemIds=\"@{itemIds}\"` in the `ListView` in your layout file.\nSetting this will make `hasStableIds` return true which can increase performance of data changes.\n\nYou can set a callback for `isEnabled()` as well with\n```java\nadapter.setItemEnabled(new BindingListViewAdapter.ItemEnabled\u003cT\u003e() {\n  @Override\n  public boolean isEnabled(int position, T item) {\n    return // Calculate if item is enabled.\n  }\n});\n```\nor by defining `app:itemEnabled=\"@{itemEnabled}\"`in the `ListView` in you layout file.\n\n### ViewPager\n\nYou can set a callback to give a page title for each item in the list with\n\n```java\nadapter.setPageTitles(new PageTitles\u003cT\u003e() {\n  @Override\n  public CharSequence getPageTitle(int position, T item) {\n    return \"Page Title\";\n  }\n});\n```\nor by defining `app:pageTitles=\"@{pageTitles}\"` in the `ViewPager` in your layout file.\n\n### RecyclerView\n\nYou can construct custom view holders with\n\n```java\nadapter.setViewHolderFactory(new ViewHolderFactory() {\n  @Override\n  public RecyclerView.ViewHolder createViewHolder(ViewDataBinding binding) {\n    return new MyCustomViewHolder(binding.getRoot());\n  }\n});\n```\nor by defining `app:viewHolder=\"@{viewHolderFactory}\"` in the `RecyclerView` in your layout file.\n\n## Directly manipulating views\n\nData binding is awesome and all, but you may run into a case where you simply need to manipulate the\nviews directly. You can do this without throwing away the whole of databinding by subclassing an\nexisting `BindingCollectionAdapter`. You can then bind `adapter` in your layout to your subclass's\nclass name to have it use that instead. Instead of overriding the normal adapter methods, you should\noverride `onCreateBinding()` or `onBindBinding()` and call `super` allowing you to run code before\nand after those events and get access to the item view's binding.\n\n```java\npublic class MyRecyclerViewAdapter\u003cT\u003e extends BindingRecyclerViewAdapter\u003cT\u003e {\n\n  @Override\n  public ViewDataBinding onCreateBinding(LayoutInflater inflater, @LayoutRes int layoutId, ViewGroup viewGroup) {\n    ViewDataBinding binding = super.onCreateBinding(inflater, layoutId, viewGroup);\n    Log.d(TAG, \"created binding: \" + binding);\n    return binding;\n  }\n\n  @Override\n  public void onBindBinding(ViewDataBinding binding, int bindingVariable, @LayoutRes int layoutId, int position, T item) {\n    super.onBindBinding(binding, bindingVariable, layoutId, position, item);\n    Log.d(TAG, \"bound binding: \" + binding + \" at position: \" + position);\n  }\n}\n```\n\n```xml\n\u003candroidx.recyclerview.widget.RecyclerView\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  app:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\"\n  app:items=\"@{viewModel.items}\"\n  app:itemBinding=\"@{viewModel.itemBinding}\"\n  app:adapter=\"@{viewModel.adapter}\"/\u003e\n```\n\nNote: databinding will re-evaluate expressions in your layout each time there is a data source \nchange. If you are using a custom adapter you should ensure you are returning the same instance each \ntime or your scroll position etc will not be preserved.\n\n\n## OnItemBind helpers\n\nThere are a few classes to help with common implementations of `OnItemBind`.\n\n`OnItemBindClass` binds an item based on the class of the item in the list.\n\n```java\nitemBind = new OnItemBindClass\u003c\u003e()\n  .map(String.class, BR.name, R.layout.item_name)\n  .map(Footer.class, ItemBinding.VAR_NONE, R.layout.item_footer)\n  .map(Item.class, new OnItemBind\u003cItem\u003e() {\n                       @Override\n                       public void onItemBind(ItemBinding itemBinding, int position, Item item) {\n                         itemBinding.clearExtras()\n                                    .set(BR.item, position == 0 ? R.layout.item_header : R.layout.item)\n                                    .bindExtra(BR.extra, (list.size() - 1) == position);\n                       }\n                     })\n  .map(Object.class, ItemBinding.VAR_NONE, R.layout.item_other);\n```\n\n`OnItemBindModel` delegates to the items in the list themselves to determine the binding.\n\n```java\nitemBind = new OnItemBindModel\u003cModel\u003e();\n\npublic class Model implements ItemBindingModel {\n  @Override\n  public void onItemBind(ItemBinding itemBinding) {\n    itemBinding.set(BR.name, R.layout.item_name);\n  }\n}\n```\n\n## MergeObservableList\n\nThere are many times you want to merge multiple data sources together. This can be as simple as\nadding headers and footers or as complex as concatenating multiple data sources. It is hard to\nmanage these lists yourself since you have to take into account all items when updating a subset.\n\n`MergeObservableList` solves this by giving you a \"merged\" view of your data sources.\n\n```java\nObservableList\u003cString\u003e data = new ObservableArrayList\u003c\u003e();\nMergeObservableList\u003cString\u003e list = new MergeObservableList\u003c\u003e()\n  .insertItem(\"Header\")\n  .insertList(data)\n  .insertItem(\"Footer\");\n\ndata.addAll(Arrays.asList(\"One\", \"Two\"));\n// list =\u003e [\"Header\", \"One\", \"Two\", \"Footer\"]\ndata.remove(\"One\");\n// list =\u003e [\"Header\", \"Two\", \"Footer\"]\n```\n\n## DiffObservableList\n\nSay you want to update list 'a' to list 'b' and you don't want to calculate what has changed between\nthe two manually.\n\n`DiffObservableList` builds off of [DiffUtil](https://developer.android.com/reference/android/support/v7/util/DiffUtil.html)\nto automatically calculate the changes between two lists.\n\n```java\nDiffObservableList\u003cItem\u003e list = new DiffObservableList(new DiffObservableList.Callback\u003cItem\u003e() {\n    @Override\n    public boolean areItemsTheSame(Item oldItem, Item newItem) {\n        return oldItem.id.equals(newItem.id);\n    }\n\n    @Override\n    public boolean areContentsTheSame(Item oldItem, Item newItem) {\n        return oldItem.value.equals(newItem.value);\n    }\n});\n\nlist.update(Arrays.asList(new Item(\"1\", \"a\"), new Item(\"2\", \"b1\")));\nlist.update(Arrays.asList(new Item(\"2\", \"b2\"), new Item(\"3\", \"c\"), new Item(\"4\", \"d\"));\n```\n\nWith large lists diffing might be too costly to run on the main thread. In that case you can\ncalculate the diff on a background thread.\n\n```java\nDiffObservableList\u003cItem\u003e list = new DiffObservableList(...);\n\n// On background thread:\nDiffUtil.DiffResult diffResult = list.calculateDiff(newItems);\n\n// On main thread:\nlist.update(newItems, diffResult);\n```\n\n## Paging\n\nPaging is supported through the `bindingcollectionadapter-paging` artifact. First add it to your project\n\n```groovy\nimplementation 'me.tatarka.bindingcollectionadapter2:bindingcollectionadapter-paging:3.1.1'\n```\n\nThen bind a `PagedList` and `DiffUtil.ItemCallback`.\n\n```java\nLiveData\u003cPagedList\u003cItem\u003e\u003e pagedList = new LivePagedListBuilder\u003c\u003e(..., 20);\nDiffUtil.ItemCallback\u003cItem\u003e diff = ...;\n```\n\n```xml\n\u003candroidx.recyclerview.widget.RecyclerView\n  android:layout_width=\"match_parent\"\n  android:layout_height=\"match_parent\"\n  app:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\"\n  app:items=\"@{pagedList}\"\n  app:itemBinding=\"@{itemBinding}\"\n  app:diffConfig=\"@{diff}\" /\u003e\n```\n\n## Known Issues\n\n### Cannot Resolve the libraries `@BindingAdapter`'s\n\nThis is likely because you are using the [android-apt](https://bitbucket.org/hvisser/android-apt)\nplugin which [broke](https://bitbucket.org/hvisser/android-apt/issues/45/breaks-declaring-bindingadapter-in-a)\nthis in previous versions. Update to `1.6+` to fix it.\n\n### View's adapter is null\n\nIf you attempt to retrieve an adapter from a view right after binding it you may find it is null.\nThis is because databinding waits for the next draw pass to run to batch up changes. You can force\nit to run immediately by calling `binding.executePendingBindings()`.\n\n### LiveData not working\n\nLive data support has been added in `2.3.0-beta3` and `3.0.0-beta3` (androidx). For most cases it\nshould 'just work'. However, it uses a bit of reflection under the hood and you'll have to call\n`adapter.setLifecycleOwner(owner)` if your containing view does not use databinding. This will be\nfixed whenever [this issue](https://issuetracker.google.com/issues/112929938) gets resolved.\n\n## License\n\n    Copyright 2015 Evan Tatarka\n\n    Licensed under the Apache License, Version 2.0 (the \"License\");\n    you may not use this file except in compliance with the License.\n    You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n    Unless required by applicable law or agreed to in writing, software\n    distributed under the License is distributed on an \"AS IS\" BASIS,\n    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n    See the License for the specific language governing permissions and\n    limitations under the License.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fevant%2Fbinding-collection-adapter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fevant%2Fbinding-collection-adapter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fevant%2Fbinding-collection-adapter/lists"}