Skip to content

Commit 0a31421

Browse files
authored
Merge pull request #40 from matbadev/release/v1.0.0
Release v1.0.0
2 parents deeea76 + 578e47e commit 0a31421

69 files changed

Lines changed: 835 additions & 419 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/check.yml

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,9 @@ name: check
22

33
on:
44
push:
5-
branches:
6-
# Trigger for pushes to develop to run when merging feature branches using squashing
7-
- develop
8-
pull_request:
95

106
env:
11-
GRADLEW_ARGUMENTS: "--console=plain --stacktrace -Pci"
7+
GRADLEW_ARGUMENTS: "--console=plain --stacktrace -Pci -Pcoverage"
128

139
jobs:
1410
build_and_test:
@@ -23,10 +19,10 @@ jobs:
2319
uses: gradle/wrapper-validation-action@v1
2420

2521
# https://github.com/marketplace/actions/setup-java-jdk
26-
- name: Setup JDK 8
22+
- name: Setup JDK 11
2723
uses: actions/setup-java@v1
2824
with:
29-
java-version: 8
25+
java-version: 11
3026

3127
- name: Setup Gradle
3228
run: ./gradlew --version

.github/workflows/publish.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@ jobs:
2222
uses: gradle/wrapper-validation-action@v1
2323

2424
# https://github.com/marketplace/actions/setup-java-jdk
25-
- name: Setup JDK 8
25+
- name: Setup JDK 11
2626
uses: actions/setup-java@v1
2727
with:
28-
java-version: 8
28+
java-version: 11
2929

3030
- name: Setup Gradle
3131
run: ./gradlew --version

.idea/codeStyles/Project.xml

Lines changed: 2 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 236 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,239 @@
1+
[![latest Maven Central release](https://maven-badges.herokuapp.com/maven-central/com.matbadev.dabirva/dabirva/badge.svg)](https://search.maven.org/artifact/com.matbadev.dabirva/dabirva)
2+
[![publish workflow state](https://github.com/matbadev/dabirva/workflows/publish/badge.svg)](https://github.com/matbadev/dabirva/actions/workflows/publish.yml)
3+
[![test coverage](https://codecov.io/gh/matbadev/dabirva/branch/master/graph/badge.svg?token=1HB0C8S7MN)](https://codecov.io/gh/matbadev/dabirva)
4+
[![Apache 2 license](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0.html)
5+
16
# Dabirva (Data Binding RecyclerView Adapter)
27

3-
[![publish](https://github.com/matbadev/dabirva/workflows/publish/badge.svg)](https://github.com/matbadev/dabirva/actions/workflows/publish.yml)
8+
Dabirva is a simple and extensible adapter for Android's RecyclerView to easily build lists using the [Data Binding Library](https://developer.android.com/topic/libraries/data-binding/).
9+
10+
Features:
11+
12+
- Seamless integration into the MVVM pattern ([recommended by Google](https://developer.android.com/jetpack/guide))
13+
- Asynchronous item diffing by default for better performance
14+
- Decorations binding
15+
- Sticky headers (for horizontal and vertical linear layouts)
16+
- Extensible classes
17+
18+
## Installation
19+
20+
Dabirva is available via the [Central Repository](https://central.sonatype.org/):
21+
22+
```groovy
23+
dependencies {
24+
implementation "com.matbadev.dabirva:dabirva:1.0.0"
25+
}
26+
```
27+
28+
This library requires at least Java 8:
29+
30+
```groovy
31+
android {
32+
compileOptions {
33+
sourceCompatibility = JavaVersion.VERSION_1_8
34+
targetCompatibility = JavaVersion.VERSION_1_8
35+
}
36+
37+
kotlinOptions {
38+
jvmTarget = JavaVersion.VERSION_1_8
39+
}
40+
}
41+
```
42+
43+
## Usage
44+
45+
### Defining item view models
46+
47+
To define an item view model (which can later be bound to a RecyclerView) start by implementing [ItemViewModel](dabirva/src/main/java/com/matbadev/dabirva/ItemViewModel.kt).
48+
For a simple note this might look like this:
49+
50+
```kotlin
51+
data class NoteViewModel(
52+
val id: Long,
53+
val text: String,
54+
) : ItemViewModel {
55+
56+
override val bindingId: Int
57+
get() = BR.viewModel
58+
59+
override val layoutId: Int
60+
get() = R.layout.item_note
61+
62+
override fun entityEquals(other: Any?): Boolean {
63+
return other is NoteViewModel && id == other.id
64+
}
65+
66+
}
67+
```
68+
69+
Item view models need to be **bindable** which requires them to define a **binding ID** (from the generated ` BR` class) and a **layout ID** (from the generated `R.layout` class).
70+
71+
Furthermore they need to be **diffable** which requires a proper ` equals` and `hashCode` implementation (e.g. by using a Kotlin data class) and an implementation of `entityEquals` which defines when two item view models describe the same entity (e.g. when they have the same ID).
72+
Dabirva uses this information to detect item positions after list updates to display proper item animations.
73+
74+
The second step is to add the referenced layout with the view model's class as a variable.
75+
For the previous note example this might look like this:
76+
77+
```xml
78+
<?xml version="1.0" encoding="utf-8"?>
79+
<layout xmlns:android="http://schemas.android.com/apk/res/android"
80+
xmlns:tools="http://schemas.android.com/tools">
81+
82+
<data>
83+
<variable
84+
name="viewModel"
85+
type="com.matbadev.dabirva.example.NoteViewModel" />
86+
</data>
87+
88+
<TextView
89+
android:layout_width="match_parent"
90+
android:layout_height="wrap_content"
91+
android:text="@{viewModel.text}"
92+
tools:text="Some text" />
93+
94+
</layout>
95+
```
96+
97+
### Binding item view models to a RecyclerView
98+
99+
To bind item view models to a RecyclerView using Dabirva as an adapter, first declare a list of [ItemViewModel](dabirva/src/main/java/com/matbadev/dabirva/ItemViewModel.kt) in your screen's view model using an observable type:
100+
101+
```kotlin
102+
// Using ObservableField
103+
val items = ObservableField<List<ItemViewModel>?>()
104+
105+
// Using NonNullObservableField (provided by Dabirva)
106+
val items = NonNullObservableField<List<ItemViewModel>>(listOf())
107+
108+
// Using MutableLiveData
109+
val items = MutableLiveData<List<ItemViewModel>?>()
110+
```
111+
112+
Next bind the items to a RecyclerView in your XML layout:
113+
114+
```xml
115+
<?xml version="1.0" encoding="utf-8"?>
116+
<layout xmlns:android="http://schemas.android.com/apk/res/android"
117+
xmlns:app="http://schemas.android.com/apk/res-auto"
118+
xmlns:tools="http://schemas.android.com/tools">
119+
120+
<data>
121+
<variable
122+
name="viewModel"
123+
type="com.matbadev.dabirva.example.ActivityViewModel" />
124+
</data>
125+
126+
<androidx.recyclerview.widget.RecyclerView
127+
android:layout_width="match_parent"
128+
android:layout_height="match_parent"
129+
android:orientation="vertical"
130+
app:dabirvaItems="@{viewModel.items}"
131+
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
132+
tools:listitem="@layout/item_note" />
133+
134+
</layout>
135+
```
136+
137+
When the `dabirvaItems` binding adapter is executed it will create an instance of `Dabirva` and attach it to the RecyclerView.
138+
The displayed list will automatically be updated with proper item animations once the `items` field in the screen's view model is changed.
139+
140+
### Add decorations to RecyclerView items
141+
142+
Dabirva provides the `itemDecorations` binding adapter for adding decorations to RecyclerView items.
143+
First they need to be defined in the screen's view model:
144+
145+
```kotlin
146+
val itemDecorations = listOf<RecyclerView.ItemDecoration>(
147+
/* some item decorations */
148+
)
149+
```
150+
151+
After that they can easily be bound to a RecyclerView in the screen's XML layout:
152+
153+
```xml
154+
<?xml version="1.0" encoding="utf-8"?>
155+
<layout xmlns:android="http://schemas.android.com/apk/res/android"
156+
xmlns:app="http://schemas.android.com/apk/res-auto"
157+
xmlns:tools="http://schemas.android.com/tools">
158+
159+
<data>
160+
<variable
161+
name="viewModel"
162+
type="com.matbadev.dabirva.example.ActivityViewModel" />
163+
</data>
164+
165+
<androidx.recyclerview.widget.RecyclerView
166+
android:layout_width="match_parent"
167+
android:layout_height="match_parent"
168+
android:orientation="vertical"
169+
app:itemDecorations="@{viewModel.itemDecorations}"
170+
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
171+
tools:listitem="@layout/item_note" />
172+
173+
</layout>
174+
```
175+
176+
This can be done without using the `dabirvaItems` binding adapter.
177+
178+
#### Sticky headers
179+
180+
Dabirva supports sticky list headers for horizontal and vertical linear layouts.
181+
**This requires an instance of `Dabirva` to be used as RecyclerView adapter.**
182+
183+
To use sticky headers simply add a corresponding instance to the `itemDecorations` field in the screen's view model:
184+
185+
```kotlin
186+
val itemDecorations = listOf<RecyclerView.ItemDecoration>(
187+
HorizontalStickyHeaderDecoration(
188+
headerPositionProvider = ItemHeaderProvider { it is HeaderViewModel },
189+
),
190+
)
191+
```
192+
193+
As you can see you need to provide a [HeaderPositionProvider](dabirva/src/main/java/com/matbadev/dabirva/decoration/HeaderPositionProvider.kt) for defining the items to use as headers.
194+
The easiest way is to implement [ItemHeaderProvider](dabirva/src/main/java/com/matbadev/dabirva/decoration/ItemHeaderProvider.kt) which just requires a predicate to decide if a specific item should be used as a header.
195+
196+
### Advanced: use custom `Executor` for item diffing
197+
198+
To perform the item diffing process on a custom `Executor` provide a suitable factory to [DabirvaConfig](dabirva/src/main/java/com/matbadev/dabirva/DabirvaConfig.kt) which is used by the `dabirvaItems` binding adapter.
199+
This can also be used to execute the diffing synchronously on the main thread:
200+
201+
```kotlin
202+
DabirvaConfig.factory = DabirvaFactory { Dabirva(diffExecutor = Runnable::run) }
203+
```
204+
205+
This might cause UI lags for complex lists and is therefore not recommended.
206+
207+
### Advanced: extend the `Dabirva` class
208+
209+
Dabirva supports subclassing the `Dabirva` class to add custom functionality:
210+
211+
```kotlin
212+
class CustomDabirva : Dabirva() {
213+
// Add custom logic here by overwriting the correspondent methods.
214+
}
215+
```
216+
217+
To make the `dabirvaItems` binding adapter instantiate your custom class instead of the default `Dabirva` one you need to supply a suitable factory to [DabirvaConfig](dabirva/src/main/java/com/matbadev/dabirva/DabirvaConfig.kt):
218+
219+
```kotlin
220+
DabirvaConfig.factory = DabirvaFactory { CustomDabirva() }
221+
```
222+
223+
## License
224+
225+
```
226+
Copyright 2021 Matthias Bäuerle
227+
228+
Licensed under the Apache License, Version 2.0 (the "License");
229+
you may not use this file except in compliance with the License.
230+
You may obtain a copy of the License at
231+
232+
http://www.apache.org/licenses/LICENSE-2.0
4233
5-
[![codecov](https://codecov.io/gh/matbadev/dabirva/branch/master/graph/badge.svg?token=1HB0C8S7MN)](https://codecov.io/gh/matbadev/dabirva)
234+
Unless required by applicable law or agreed to in writing, software
235+
distributed under the License is distributed on an "AS IS" BASIS,
236+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
237+
See the License for the specific language governing permissions and
238+
limitations under the License.
239+
```

build.gradle

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,15 @@
22
buildscript {
33
ext {
44
JAVA_VERSION = JavaVersion.VERSION_1_8
5-
KOTLIN_VERSION = "1.5.10"
6-
DOKKA_VERSION = "1.4.32"
7-
ANDROID_GRADLE_PLUGIN_VERSION = "4.2.1"
5+
KOTLIN_VERSION = "1.5.31"
6+
KOTLIN_COMPATIBILITY_VALIDATOR_VERSION = "0.7.1"
7+
ANDROID_GRADLE_PLUGIN_VERSION = "7.0.3"
88
JACOCO_VERSION = "0.8.7"
9+
JUNIT_5_VERSION = "5.8.1"
10+
JUNIT_5_PLUGIN_VERSION = "1.8.0.0"
11+
JUNIT_KTX_VERSION = "1.1.3"
12+
ESPRESSO_VERSION = "3.4.0"
13+
MOCKITO_VERSION = "3.12.4"
914
}
1015

1116
repositories {
@@ -16,16 +21,16 @@ buildscript {
1621
dependencies {
1722
classpath "com.android.tools.build:gradle:$ANDROID_GRADLE_PLUGIN_VERSION"
1823
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$KOTLIN_VERSION"
19-
classpath "de.mannodermaus.gradle.plugins:android-junit5:1.7.1.1"
20-
classpath "org.jetbrains.dokka:dokka-gradle-plugin:$DOKKA_VERSION"
24+
classpath "org.jetbrains.dokka:dokka-gradle-plugin:$KOTLIN_VERSION"
25+
classpath "de.mannodermaus.gradle.plugins:android-junit5:$JUNIT_5_PLUGIN_VERSION"
2126

2227
// NOTE: Do not place your application dependencies here; they belong
2328
// in the individual module build.gradle files
2429
}
2530
}
2631

2732
plugins {
28-
id "org.jetbrains.kotlinx.binary-compatibility-validator" version "0.5.0"
33+
id "org.jetbrains.kotlinx.binary-compatibility-validator" version "$KOTLIN_COMPATIBILITY_VALIDATOR_VERSION"
2934
id "jacoco"
3035
}
3136

@@ -61,9 +66,9 @@ task jacocoMergedReportDebug(type: JacocoReport) {
6166
description = "Collect Jacoco coverage data of dabirva and example module into a single report"
6267

6368
reports {
64-
xml.enabled = true
65-
html.enabled = false
66-
csv.enabled = false
69+
html.required = false
70+
xml.required = true
71+
csv.required = false
6772
}
6873

6974
sourceDirectories.setFrom fileTree(dir: projectDir, includes: [
@@ -74,10 +79,10 @@ task jacocoMergedReportDebug(type: JacocoReport) {
7479
])
7580
executionData.setFrom fileTree(dir: projectDir, includes: [
7681
// Unit tests coverage
77-
"dabirva/*.exec",
78-
"example/*.exec",
82+
"dabirva/build/outputs/**/*.exec",
83+
"example/build/outputs/**/*.exec",
7984
// Instrumented tests coverage
80-
"dabirva/build/outputs/code_coverage/**/*.ec",
81-
"example/build/outputs/code_coverage/**/*.ec",
85+
"dabirva/build/outputs/**/*.ec",
86+
"example/build/outputs/**/*.ec",
8287
])
8388
}

0 commit comments

Comments
 (0)