Skip to content

Commit

Permalink
Merge pull request #13 from jacek-marchwicki/readme-v2
Browse files Browse the repository at this point in the history
Update README.md
  • Loading branch information
marcin-adamczewski authored Mar 12, 2021
2 parents 2b8e5d4 + 6202170 commit 0782487
Showing 1 changed file with 108 additions and 203 deletions.
311 changes: 108 additions & 203 deletions README.md
Original file line number Diff line number Diff line change
@@ -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()
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 (optional)

## How it looks

Expand All @@ -21,238 +24,140 @@ repositories {
}
dependencies {
// UniversalAdapter with changes detector with RxJava
implementation 'com.github.jacek-marchwicki.recyclerview-changes-detector:universal-adapter-rx:<look-on-release-tab>'
// UniversalAdapter with changes detector without RxJava
implementation 'com.github.jacek-marchwicki.recyclerview-changes-detector:universal-adapter:<look-on-release-tab>'
// Changes Detector and Adapter items (without Android dependencies)
implementation 'com.github.jacek-marchwicki.recyclerview-changes-detector:universal-adapter-java:<look-on-release-tab>'
// Changes Detector (without Android dependencies)
implementation 'com.github.jacek-marchwicki.recyclerview-changes-detector:changes-detector:<look-on-release-tab>'
// RX support
implementation 'com.github.jacek-marchwicki.recyclerview-changes-detector:universal-adapter-rx:<look-on-release-tab>'
}
```

## How to use

### With Universal Adapter

Implement some Pojo with your data:
Let's assume that your list consits of headers, songs and a footer.

```java
private class Data implements BaseAdapterItem {
- Implement data models for all list elements

private final long id;
@Nonnull
private final String name;
private final int color;
```kotlin
data class HeaderItem(val text: String, val songsCount: Int, override val itemId: Any = text) : DefaultAdapterItem()

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);
}
data class SongItem(val id: String, val title: String, val imageUrl: String, override val itemId: Any = id, val onSongClick: (id: String) -> Unit) : DefaultAdapterItem()

/**
* Return true if items are equal
*/
@Override
public boolean same(@Nonnull BaseAdapterItem item) {
return equals(item);
}
data class FooterItem(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:
- 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.

```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));
}

private class ViewHolder extends BaseViewHolder<Data> {

@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);
}

@Override
public void bind(@Nonnull Data item) {
text.setText(item.name);
cardView.setCardBackgroundColor(item.color);
```kotlin
class HeaderViewHolder : LayoutViewHolderManager<HeaderItem>(
R.layout.item_header, HeaderItem::class, { HeaderViewHolder(it) }
) {
class HeaderViewHolder(itemView: View) : ViewHolderManager.BaseViewHolder<HeaderItem>(itemView) {
override fun bind(item: HeaderItem) {
itemView.item_header_tv.text = "${item.text} - ${item.songsCount}"
}
}
}
```

Setup recycler view:

```java
final UniversalAdapter adapter = new UniversalAdapter(Collections.<ViewHolderManager>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 SongViewHolder(val imageLoader: ImageLoader) : LayoutViewHolderManager<SongItem>(
R.layout.song_item, SongItem::class, { ViewHolder(it) }
) {
class ViewHolder(itemView: View) : ViewHolderManager.BaseViewHolder<SongItem>(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)
}
}

// 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<Data> {

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;
}

/**
* Return true if id matches
*/
@Override
public boolean matches(@Nonnull Data item) {
return ((Data) item).id == id;
}

/**
* 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;
class FooterViewHolder : LayoutViewHolderManager<FooterItem>(
R.layout.item_footer, FooterItem::class, { ViewHolder(it) }
) {
class ViewHolder(itemView: View) : ViewHolderManager.BaseViewHolder<FooterItem>(itemView) {
override fun bind(item: FooterItem) {}
}
}

```

Setup changes detector for your adapter
- Setup the adapter and bind data:

```kotlin
val adapter = UniversalAdapter(listOf(headerViewHolder, songViewHolder, footerViewHolder))
recyclerView.adapter = adapter

// 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
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<Data, Data> changesDetector =
new ChangesDetector<>(new SimpleDetector<Data>());
- `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. 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 are 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> 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> 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)


### Frequently asked questions

[FAQ](FAQ.md)
For more look at the sample app at [app/](app/) directory.


## License
Expand Down

0 comments on commit 0782487

Please sign in to comment.