From dd22ec6c76631aac844ebbda2689245792ad1072 Mon Sep 17 00:00:00 2001 From: marcin-adamczewski Date: Thu, 11 Mar 2021 18:07:45 +0100 Subject: [PATCH 1/3] Update README.md --- README.md | 296 ++++++++++++++++++------------------------------------ 1 file changed, 99 insertions(+), 197 deletions(-) diff --git a/README.md b/README.md index 111c358..795be30 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,16 @@ # Recycler View Changes Detector +[V1 documentation](https://github.com/jacek-marchwicki/recyclerview-changes-detector/blob/1.0.2/README.md) + [![Build Status](https://travis-ci.org/jacek-marchwicki/recyclerview-changes-detector.svg?branch=master)](https://travis-ci.org/jacek-marchwicki/recyclerview-changes-detector) [![Jitpack Status](https://jitpack.io/v/jacek-marchwicki/recyclerview-changes-detector.svg)](https://jitpack.io/#jacek-marchwicki/recyclerview-changes-detector) -Library allow to automatically detect changes in your data and call methods: -- notifyItemRangeInserted() -- notifyItemRangeChanged() -- notifyItemRangeRemoved() -- notifyItemMoved() +Library simplifies creation of RecyclerView's Adapter: +- Less boilerplate. No need to implement `onCreateViewHolder`, `getItemViewType`, `onBindViewHolder`, `getItemId` +- Out of the box DiffUtil support. You don't have to implement the `DiffUtil.ItemCallback()` anymore. +- Plug and play data models and view holders +- Cleaner tests +- RX support ## How it looks @@ -21,233 +24,132 @@ repositories { } dependencies { - - // UniversalAdapter with changes detector with RxJava - implementation 'com.github.jacek-marchwicki.recyclerview-changes-detector:universal-adapter-rx:' - - // UniversalAdapter with changes detector without RxJava implementation 'com.github.jacek-marchwicki.recyclerview-changes-detector:universal-adapter:' - - // Changes Detector and Adapter items (without Android dependencies) - implementation 'com.github.jacek-marchwicki.recyclerview-changes-detector:universal-adapter-java:' - - // Changes Detector (without Android dependencies) - implementation 'com.github.jacek-marchwicki.recyclerview-changes-detector:changes-detector:' + // RX support + implementation 'com.github.jacek-marchwicki.recyclerview-changes-detector:universal-adapter-rx:' } ``` ## How to use -### With Universal Adapter - -Implement some Pojo with your data: +Let's assume that your list consits of a header, songs and footer. -```java -private class Data implements BaseAdapterItem { - - private final long id; - @Nonnull - private final String name; - private final int color; - - Data(long id, @Nonnull String name, int color) { - this.id = id; - this.name = name; - this.color = color; - } - - @Override - public long adapterId() { - return id; - } - - /** - * Return true if id matches - */ - @Override - public boolean matches(@Nonnull BaseAdapterItem item) { - return item instanceof Data && (((Data) item).id == id); - } +- Implement data models for all list elements - /** - * Return true if items are equal - */ - @Override - public boolean same(@Nonnull BaseAdapterItem item) { - return equals(item); - } +```kotlin +data class HeaderItem(val text: String, val songsCount: Int, override val itemId: Any = text) : DefaultAdapterItem() +data class SongItem(val id: String, val title: String, val imageUrl: String, override val itemId: Any = id, val onSongClick: (id: String) -> Unit) : DefaultAdapterItem() +data class Footer(override val itemId: Any = NO_ID) : DefaultAdapterItem() - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof Data)) return false; - final Data data = (Data) o; - return id == data.id && - name.equals(data.name) && - color == data.color; - } -} ``` -Implement your holder: - -```java -public class DataViewHolder implements ViewHolderManager { - - @Override - public boolean matches(@Nonnull BaseAdapterItem baseAdapterItem) { - return baseAdapterItem instanceof Data; - } - - @Nonnull - @Override - public BaseViewHolder createViewHolder(@Nonnull ViewGroup parent, @Nonnull LayoutInflater inflater) { - return new ViewHolder(inflater.inflate(R.layout.data_item, parent, false)); - } +- Implement a holder for each data model to bind its data to the view. You have to specify item layout id and data model class. - private class ViewHolder extends BaseViewHolder { - - @Nonnull - private final TextView text; - @Nonnull - private final CardView cardView; - - ViewHolder(@Nonnull View itemView) { - super(itemView); - text = (TextView) itemView.findViewById(R.id.data_item_text); - cardView = (CardView) itemView.findViewById(R.id.data_item_cardview); +```kotlin +class HeaderViewHolder : LayoutViewHolderManager( + R.layout.item_header, HeaderItem::class, { HeaderViewHolder(it) } +) { + class HeaderViewHolder(itemView: View) : ViewHolderManager.BaseViewHolder(itemView) { + override fun bind(item: HeaderItem) { + itemView.item_header_tv.text = "${item.text} - ${item.songsCount}" } + } +} - @Override - public void bind(@Nonnull Data item) { - text.setText(item.name); - cardView.setCardBackgroundColor(item.color); +class SongViewHolder(val imageLoader: ImageLoader) : LayoutViewHolderManager( + R.layout.song_item, SongItem::class, { ViewHolder(it) } +) { + class ViewHolder(itemView: View) : ViewHolderManager.BaseViewHolder(itemView) { + override fun bind(item: SongItem) { + itemView.song_item.text = item.title + itemView.setOnClickListener { item.onSongClick(item.id) } + imageLoader.load(item.imageUrl).into(itemView.song_cover_iv) } } } -``` - -Setup recycler view: - -```java -final UniversalAdapter adapter = new UniversalAdapter(Collections.singletonList(new DataViewHolder())); -recyclerView.setAdapter(adapter); -``` - -Give new data to adapter: - -```java -adapter.call(Arrays.toList(new Data(1, "Cow"), new Data(2, "Dg"), new Data(3, "Cat")); -``` - -And another data so recycler view will be nice animated: - -```java -adapter.call(Arrays.toList(Data(2, "Dog"), new Data(3, "Cat"), new Data(4, "Elephant")); -``` - -### With auto-value (recommended) -Usage like above with small improvement: - -```java -@AutoValue -private class Data implements BaseAdapterItem { - - @Nonnull - @AdapterId - public abstract String id(); - @Nonnull - public abstract String name(); - public abstract int color(); - - public Data create(@Nonull String id, @Nonnull String name, int color) { - return AutoValue_Data(id, name, color); +class FooterViewHolder : LayoutViewHolderManager( + R.layout.item_footer, FooterItem::class, { ViewHolder(it) } +) { + class ViewHolder(itemView: View) : ViewHolderManager.BaseViewHolder(itemView) { + override fun bind(item: FooterItem) {} } - - // methods equal, adapterId, matches and same will be generated for you } -``` - - -For more info look: [AutoValue: BaseAdapterItem Extension](https://github.com/m-zagorski/auto-value-base-adapter-item) +``` -### Without Universal Adapter - -Implement some Pojo with your data: - -```java -private class Data implements SimpleDetector.Detectable { - - private final long id; - @Nonnull - private final String name; - private final int color; - - Data(long id, @Nonnull String name, int color) { - this.id = id; - this.name = name; - this.color = color; - } +- Setup adapter and bind data: - /** - * Return true if id matches - */ - @Override - public boolean matches(@Nonnull Data item) { - return ((Data) item).id == id; - } +```kotlin +val adapter = UniversalAdapter(listOf(headerViewHolder, songViewHolder, footerViewHolder)) +recyclerView.adapter = adapter - /** - * Return true if items are equal - */ - @Override - public boolean same(@Nonnull Data item) { - return equals(item); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof Data)) return false; - final Data data = (Data) o; - return id == data.id && - name.equals(data.name) && - color == data.color; - } -} +// Whenever new data are applied, the RecyclerView is nicely animated +viewModel.adapterItems.subscribe { adapter.submitList(it) } ``` -Setup changes detector for your adapter - +## DiffUtil support +DiffUtil is an androidx tool that calculates the difference between two lists submitted to adapter. +Thanks to this, only modified elements are updated and not the whole list. It also applies a very nice animation that you can see on the GIF above. +Normally you have to implement the `DiffUtil.ItemCallback` on your own and decide when your adapter elements has changed. +Thanks to the data models you create by extending `DefaultAdapterItem` class, you don't have to do this any more. There are two conditions though +that you have to satisfy: +- you have to override `val itemId: Any` +- your data models have to be either Kotlin data classes or override `equals()` and `hashCode()` methods. + +Let's have a look at the example below: +```kotlin +data class SongItem(val id: String, val title: String, override val itemId: Any = id) : DefaultAdapterItem() ``` -@Nonnull -private final ChangesDetector changesDetector = - new ChangesDetector<>(new SimpleDetector()); +- `itemId` is required to identify specific element in the list. In this case it is the song id as it is unique to the song. This is being used in the `DiffUtil.ItemCallback.areItemsTheSame()` method. +- `SongItem` is a Kotlin data class so it overrides `equals` and `hashCode` by default. If you'd like to alter this behaviour you can override `equals` and `hashCode` methods on your own. This is being used in the `DiffUtil.ItemCallback.areContentsTheSame()` method to identify whether element's content changed and need to be updated. + +## Plug and play data models and view holders +As your models and view holders are not bound to any adapter, you can reuse them in every adapter. +You don't have to specify the `itemViewType` in each adapter, just pass you view holder to adapter's constructor. + +## Cleaner tests +As your data models and being created in a ViewModel/Presenter, your test logic is very clean and simple + +```kotlin +@Test +fun `when 2 of 4 registered students are attendees then student items correctly divided into sections`() { + every { classDao.registeredStudents } returns Observable.just( + listOf( + ClassStudent("name1", attended = true), + ClassStudent("name3", attended = false), + ClassStudent("name2", attended = true), + ClassStudent("name4", attended = false) + ) + ) + viewModel = PastClassStudentsPresenter("fake_id", classDaos) + + viewModel.adapterItems + .test() + .assertValue( + listOf( + SectionNameItem("ATTENDED"), + PastClassStudentItem("name1", true), + PastClassStudentItem("name2", true), + SectionNameItem("REGISTERED"), + PastClassStudentItem("name3", false), + PastClassStudentItem("name4", false) + ) + ) +} ``` -Give new data to your adapter: - -```java -List data = Arrays.toList(new Data(1, "Cow"), new Data(2, "Dg"), new Data(3, "Cat")); -yourAdapter.swapData(data) -changesDetector.newData(yourAdapter, data, false); +## RX support +`universal-adapter-rx` module lets you subscribe directly to adapter like this: +```kotlin +viewModel.adapterItems.subscribe(adapter) ``` -And another data so recycler view will be nice animated: -```java -List data = Arrays.toList(Data(2, "Dog"), new Data(3, "Cat"), new Data(4, "Elephant")); -yourAdapter.swapData(data) -changesDetector.newData(yourAdapter, data, false); -``` ### More -For more: -- look on sample app at [app/](app/) directory. -- look on [AutoValue: BaseAdapterItem Extension](https://github.com/m-zagorski/auto-value-base-adapter-item) +For more look at the sample app at [app/](app/) directory. ### Frequently asked questions From 72d1ce17be7b47d4e046b38ff2326fb10b1f6568 Mon Sep 17 00:00:00 2001 From: marcin-adamczewski Date: Fri, 12 Mar 2021 10:09:56 +0100 Subject: [PATCH 2/3] Update README.md --- README.md | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 795be30..7920560 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Library simplifies creation of RecyclerView's Adapter: - Less boilerplate. No need to implement `onCreateViewHolder`, `getItemViewType`, `onBindViewHolder`, `getItemId` -- Out of the box DiffUtil support. You don't have to implement the `DiffUtil.ItemCallback()` anymore. +- Out of the box DiffUtil support. You don't have to implement the `DiffUtil.ItemCallback()` anymore. - Plug and play data models and view holders - Cleaner tests - RX support @@ -43,7 +43,7 @@ data class Footer(override val itemId: Any = NO_ID) : DefaultAdapterItem() ``` -- Implement a holder for each data model to bind its data to the view. You have to specify item layout id and data model class. +- Implement a view holder for each data model to bind its data to the view. You have to specify item layout id and data model class. ```kotlin class HeaderViewHolder : LayoutViewHolderManager( @@ -78,7 +78,7 @@ class FooterViewHolder : LayoutViewHolderManager( ``` -- Setup adapter and bind data: +- Setup the adapter and bind data: ```kotlin val adapter = UniversalAdapter(listOf(headerViewHolder, songViewHolder, footerViewHolder)) @@ -102,14 +102,14 @@ Let's have a look at the example below: data class SongItem(val id: String, val title: String, override val itemId: Any = id) : DefaultAdapterItem() ``` - `itemId` is required to identify specific element in the list. In this case it is the song id as it is unique to the song. This is being used in the `DiffUtil.ItemCallback.areItemsTheSame()` method. -- `SongItem` is a Kotlin data class so it overrides `equals` and `hashCode` by default. If you'd like to alter this behaviour you can override `equals` and `hashCode` methods on your own. This is being used in the `DiffUtil.ItemCallback.areContentsTheSame()` method to identify whether element's content changed and need to be updated. +- `SongItem` is a Kotlin data class so it overrides `equals` and `hashCode` by default. This is being used in the `DiffUtil.ItemCallback.areContentsTheSame()` method to identify whether element's content changed and need to be updated. If you'd like to alter this behaviour you can override `equals` and `hashCode` methods on your own. ## Plug and play data models and view holders As your models and view holders are not bound to any adapter, you can reuse them in every adapter. You don't have to specify the `itemViewType` in each adapter, just pass you view holder to adapter's constructor. ## Cleaner tests -As your data models and being created in a ViewModel/Presenter, your test logic is very clean and simple +As your data models are being created in a ViewModel/Presenter, your test logic is very clean and simple ```kotlin @Test @@ -146,17 +146,11 @@ viewModel.adapterItems.subscribe(adapter) ``` - ### More For more look at the sample app at [app/](app/) directory. -### Frequently asked questions - -[FAQ](FAQ.md) - - ## License Copyright [2016] [Jacek Marchwicki ] From 62021702cb892267fb4a443cf62bc08be0a8559d Mon Sep 17 00:00:00 2001 From: marcin-adamczewski Date: Fri, 12 Mar 2021 12:06:15 +0100 Subject: [PATCH 3/3] Update README.md --- README.md | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 7920560..a20dde9 100644 --- a/README.md +++ b/README.md @@ -5,12 +5,12 @@ [![Build Status](https://travis-ci.org/jacek-marchwicki/recyclerview-changes-detector.svg?branch=master)](https://travis-ci.org/jacek-marchwicki/recyclerview-changes-detector) [![Jitpack Status](https://jitpack.io/v/jacek-marchwicki/recyclerview-changes-detector.svg)](https://jitpack.io/#jacek-marchwicki/recyclerview-changes-detector) -Library simplifies creation of RecyclerView's Adapter: +Lightweight library that simplifies creation of RecyclerView's Adapter: - Less boilerplate. No need to implement `onCreateViewHolder`, `getItemViewType`, `onBindViewHolder`, `getItemId` - Out of the box DiffUtil support. You don't have to implement the `DiffUtil.ItemCallback()` anymore. - Plug and play data models and view holders - Cleaner tests -- RX support +- RX support (optional) ## How it looks @@ -32,14 +32,16 @@ dependencies { ## How to use -Let's assume that your list consits of a header, songs and footer. +Let's assume that your list consits of headers, songs and a footer. - Implement data models for all list elements ```kotlin data class HeaderItem(val text: String, val songsCount: Int, override val itemId: Any = text) : DefaultAdapterItem() + data class SongItem(val id: String, val title: String, val imageUrl: String, override val itemId: Any = id, val onSongClick: (id: String) -> Unit) : DefaultAdapterItem() -data class Footer(override val itemId: Any = NO_ID) : DefaultAdapterItem() + +data class FooterItem(override val itemId: Any = NO_ID) : DefaultAdapterItem() ``` @@ -84,8 +86,15 @@ class FooterViewHolder : LayoutViewHolderManager( val adapter = UniversalAdapter(listOf(headerViewHolder, songViewHolder, footerViewHolder)) recyclerView.adapter = adapter -// Whenever new data are applied, the RecyclerView is nicely animated -viewModel.adapterItems.subscribe { adapter.submitList(it) } +// You'd rather create the items in a ViewModel/Presenter +adapter.submitList(listOf( + HeaderItem(text="Album1"), + Song(id="1", title="Song1"), + Song(id="2", title="Song2"), + HeaderItem(text="Album2"), + Song(id="3", title="Song1"), + FooterItem(), +)); ``` ## DiffUtil support