Skip to content

Commit

Permalink
ViewBinding support (lisawray#325)
Browse files Browse the repository at this point in the history
- Create groupie-viewbinding module
- Create example-viewbinding module
- Update AGP and viewbinding version to 3.6.1
- Update README
- Deprecate all classes in library-databinding
  • Loading branch information
nashcft authored Mar 31, 2020
1 parent a1daecb commit 4b970b0
Show file tree
Hide file tree
Showing 37 changed files with 1,136 additions and 13 deletions.
67 changes: 57 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 8,35 @@ Groupie lets you treat your content as logical groups and handles change notific

# Try it out:

[ ![Download](https://api.bintray.com/packages/lisawray/maven/groupie/images/download.svg) ](https://bintray.com/lisawray/maven/groupie/_latestVersion)

```gradle
implementation "com.xwray:groupie:2.7.0"
implementation "com.xwray:groupie:$groupie_version"
```

Groupie includes a module for Kotlin and Kotlin Android extensions. Never write a ViewHolder again—Kotlin generates view references and Groupie uses a generic holder. [Setup here.](#kotlin)

```gradle
implementation "com.xwray:groupie:2.7.0"
implementation "com.xwray:groupie-kotlin-android-extensions:2.7.0"
implementation "com.xwray:groupie:$groupie_version"
implementation "com.xwray:groupie-kotlin-android-extensions:$groupie_version"
```

Groupie also supports Android's [data binding](https://developer.android.com/topic/libraries/data-binding/index.html) to generate view holders. [Setup here.](#data-binding)

```gradle
implementation "com.xwray:groupie:2.7.0"
implementation "com.xwray:groupie-databinding:2.7.0"
implementation "com.xwray:groupie:$groupie_version"
implementation "com.xwray:groupie-databinding:$groupie_version"
```

Groupie also has a support module for Android's [view binding](https://developer.android.com/topic/libraries/view-binding). This module also supports Android data binding, so if your project uses both data binding and view binding, you don't have to add the dependency on the data binding support module. [Setup here.](#view-binding)

### Note:
`groupie-viewbinding` can also be used for projects using only data binding when using Android Gradle Plugin 3.6.0 or higher.

```gradle
implementation "com.xwray:groupie:$groupie_version"
implementation "com.xwray:groupie-viewbinding:$groupie_version"
```
You can also use Groupie with Java and your existing ViewHolders.

Which one to choose? It's up to you and what your project already uses. You can even use Kotlin and data binding together.[<sup>*</sup>](#kotlin-and-data-binding) Or all your existing hand-written Java ViewHolders, and one new Kotlin item to try it out. Go crazy!
Expand Down Expand Up @@ -179,8 190,8 @@ android {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'com.xwray:groupie:[version]'
implementation 'com.xwray:groupie-kotlin-android-extensions:[version]'
implementation 'com.xwray:groupie:$groupie_version'
implementation 'com.xwray:groupie-kotlin-android-extensions:$groupie_version'
}
```

Expand All @@ -202,7 213,8 @@ android {
}
dependencies {
compile 'com.xwray:groupie-databinding:[version]'
implementation "com.xwray:groupie:$groupie_version"
implementation "com.xwray:groupie-databinding:$groupie_version"
}
```

Expand Down Expand Up @@ -236,8 248,43 @@ Bindings are only generated for layouts wrapped with <layout/> tags, so there's

You can add a `<data>` section to directly bind a model or ViewModel, but you don't have to. The generated view bindings alone are a huge time saver.

### Kotlin AND data binding?
Sure, why not? Follow all the instructions from *both* sections above. You only need to include the `groupie-databinding` dependency, and omit the references to `android-extensions`. You'll make `BindableItem`s instead of importing and using Kotlin extensions.
## View binding

Add to your app module's `build.gradle`:

```gradle
android {
viewBinding {
enabled = true
}
}
dependencies {
implementation "com.xwray:groupie:$groupie_version"
implementation "com.xwray:groupie-viewbinding:$groupie_version"
}
```

Because ViewBinding does not have the util class that can generate an arbitrary binding like `DataBindingUtil` for DataBinding, you need to override ` initializeViewBinding` to generate the instance of specified binding:

```kotlin
class MyLayoutItem: BindableItem<MyLayoutBinding>() {

// You can also use `DataBindingUtil#bind` when using ViewBinding support with DataBinding classes
override fun initializeViewBinding(view: View): MyLayoutBinding {
return MyLayoutBinding.bind(view)
}

// Other implementations...
}
```

### Note:

If you use `groupie-viewbinding` with data binding classes and your layouts have some variables or [observable objects](https://developer.android.com/topic/libraries/data-binding/observability), don't forget to run [`executePendingBindings`](https://developer.android.com/topic/libraries/data-binding/generated-binding#immediate_binding) at the last of `bind`.

### Kotlin AND data binding / view binding?
Sure, why not? Follow all the instructions from *both* sections above. You only need to include the `groupie-databinding` or `groupie-viewbinding` dependency, and omit the references to `android-extensions`. You'll make `BindableItem`s instead of importing and using Kotlin extensions.


# Contributing
Expand Down
5 changes: 3 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 7,11 @@ def testResultsDir = "${rootProject.buildDir}/test-results"
buildscript {

ext.kotlin_version = '1.3.60'
ext.android_plugin_version = '3.5.2'
ext.android_plugin_version = '3.6.1'
ext.sdkVersion = 28
ext.minimumSdkVersion = 14
ext.databinding_version = '3.2.0'
ext.databinding_version = '3.5.3'
ext.viewbinding_version = '3.6.1'

ext.junit_version = '4.12'
ext.mockito_version = '2.16.0'
Expand Down
1 change: 1 addition & 0 deletions example-viewbinding/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 1 @@
/build
51 changes: 51 additions & 0 deletions example-viewbinding/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 1,51 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'

android {
compileSdkVersion rootProject.sdkVersion

defaultConfig {
applicationId "com.xwray.groupie.example.viewbinding"
minSdkVersion rootProject.minimumSdkVersion
targetSdkVersion rootProject.sdkVersion
versionCode 1
versionName "1.0"
vectorDrawables.useSupportLibrary true
}
buildTypes {
release {
minifyEnabled false
}
}

dataBinding {
enabled = true
}

viewBinding {
enabled = true
}

lintOptions {
abortOnError false
}

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}

dependencies {
implementation project(':library-viewbinding')
implementation project(':example-shared')
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation "androidx.core:core-ktx:1.2.0"
implementation "androidx.appcompat:appcompat:1.1.0"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0"
implementation "com.google.android.material:material:1.1.0"
implementation "androidx.cardview:cardview:1.0.0"
implementation "androidx.vectordrawable:vectordrawable:1.1.0"
implementation "androidx.vectordrawable:vectordrawable-animated:1.1.0"
}
20 changes: 20 additions & 0 deletions example-viewbinding/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="com.xwray.groupie.example.viewbinding"
xmlns:android="http://schemas.android.com/apk/res/android">

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>

<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 1,63 @@
package com.xwray.groupie.example.viewbinding

import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver
import androidx.recyclerview.widget.RecyclerView.ItemDecoration
import com.xwray.groupie.Group
import com.xwray.groupie.GroupAdapter
import com.xwray.groupie.GroupDataObserver
import com.xwray.groupie.GroupieViewHolder
import com.xwray.groupie.Item
import com.xwray.groupie.example.viewbinding.item.CarouselItem

/**
* A group that contains a single carousel item and is empty when the carousel is empty
*/
class CarouselGroup(
itemDecoration: ItemDecoration,
adapter: GroupAdapter<GroupieViewHolder>
) : Group {

private var isEmpty = true
private val adapter: RecyclerView.Adapter<*>
private var groupDataObserver: GroupDataObserver? = null
private val carouselItem: CarouselItem

init {
this.adapter = adapter
carouselItem = CarouselItem(itemDecoration, adapter)
isEmpty = adapter.itemCount == 0
adapter.registerAdapterDataObserver(object : AdapterDataObserver() {
override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
val empty = adapter.itemCount == 0
if (empty && !isEmpty) {
isEmpty = empty
groupDataObserver?.onItemRemoved(carouselItem, 0)
}
}

override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
val empty = adapter.itemCount == 0
if (isEmpty && !empty) {
isEmpty = empty
groupDataObserver?.onItemInserted(carouselItem, 0)
}
}
})
}

override fun getItemCount(): Int = if (isEmpty) 0 else 1

override fun getItem(position: Int): Item<*> =
if (position == 0 && !isEmpty) carouselItem else throw IndexOutOfBoundsException()

override fun getPosition(item: Item<*>): Int = if (item === carouselItem && !isEmpty) 0 else -1

override fun registerGroupDataObserver(groupDataObserver: GroupDataObserver) {
this.groupDataObserver = groupDataObserver
}

override fun unregisterGroupDataObserver(groupDataObserver: GroupDataObserver) {
this.groupDataObserver = null
}
}
Original file line number Diff line number Diff line change
@@ -0,0 1,44 @@
package com.xwray.groupie.example.viewbinding

import com.xwray.groupie.Group
import com.xwray.groupie.GroupDataObserver
import com.xwray.groupie.Item
import com.xwray.groupie.viewbinding.BindableItem
import java.util.*

/**
* A simple, non-editable, non-nested group of Items which displays a list as vertical columns.
*/
class ColumnGroup(items: List<BindableItem<*>>) : Group {

private val items: ArrayList<BindableItem<*>> = ArrayList()

init {
for (i in items.indices) {
// Rearrange items so that the adapter appears to arrange them in vertical columns
val index = if (i % 2 == 0) {
i / 2
} else {
// If columns are uneven, we'll put an extra one at the end of the first column,
// meaning the second column's indices will all be increased by 1
(i - 1) / 2 (items.size / 2f).toInt() if (items.size % 2 == 1) 1 else 0
}
val trackItem = items[index]
this.items.add(trackItem)
}
}

override fun registerGroupDataObserver(groupDataObserver: GroupDataObserver) {
// no real need to do anything here
}

override fun unregisterGroupDataObserver(groupDataObserver: GroupDataObserver) {
// no real need to do anything here
}

override fun getItem(position: Int): BindableItem<*> = items[position]

override fun getPosition(item: Item<*>): Int = items.indexOf(item)

override fun getItemCount(): Int = items.size
}
Original file line number Diff line number Diff line change
@@ -0,0 1,40 @@
package com.xwray.groupie.example.viewbinding

import android.graphics.drawable.Animatable
import android.view.View
import androidx.annotation.StringRes
import com.xwray.groupie.ExpandableGroup
import com.xwray.groupie.ExpandableItem
import com.xwray.groupie.example.viewbinding.databinding.ItemHeaderBinding
import com.xwray.groupie.example.viewbinding.item.HeaderItem

class ExpandableHeaderItem(
@StringRes titleStringResId: Int,
@StringRes subtitleResId: Int
) : HeaderItem(titleStringResId, subtitleResId), ExpandableItem {

private var expandableGroup: ExpandableGroup? = null

override fun bind(viewBinding: ItemHeaderBinding, position: Int) {
super.bind(viewBinding, position)

// Initial icon state -- not animated.
viewBinding.icon.visibility = View.VISIBLE
viewBinding.icon.setImageResource(if (expandableGroup!!.isExpanded) R.drawable.collapse else R.drawable.expand)
viewBinding.icon.setOnClickListener {
expandableGroup!!.onToggleExpanded()
bindIcon(viewBinding)
}
}

private fun bindIcon(viewBinding: ItemHeaderBinding) {
viewBinding.icon.visibility = View.VISIBLE
viewBinding.icon.setImageResource(if (expandableGroup!!.isExpanded) R.drawable.collapse_animated else R.drawable.expand_animated)
val drawable = viewBinding.icon.drawable as Animatable
drawable.start()
}

override fun setExpandableGroup(onToggleListener: ExpandableGroup) {
expandableGroup = onToggleListener
}
}
Original file line number Diff line number Diff line change
@@ -0,0 1,9 @@
package com.xwray.groupie.example.viewbinding

import androidx.annotation.ColorInt
import com.xwray.groupie.example.core.decoration.HeaderItemDecoration

class HeaderItemDecoration(
@ColorInt background: Int,
sidePaddingPixels: Int
) : HeaderItemDecoration(background, sidePaddingPixels, R.layout.item_header)
Original file line number Diff line number Diff line change
@@ -0,0 1,10 @@
package com.xwray.groupie.example.viewbinding

import androidx.annotation.ColorInt
import androidx.annotation.Dimension
import com.xwray.groupie.example.core.decoration.InsetItemDecoration

class InsetItemDecoration(
@ColorInt backgroundColor: Int,
@Dimension padding: Int
) : InsetItemDecoration(backgroundColor, padding, INSET_TYPE_KEY, INSET)
Loading

0 comments on commit 4b970b0

Please sign in to comment.