Skip to content

Commit

Permalink
Add Kotlin DSL to create Adapters (#68)
Browse files Browse the repository at this point in the history
* First draft

* New ListAdapterDelegateDsl implementatino

* Moved dsl stuff in own submodule

* Removed unusded files

* Code duplication for adapter delegates

* Tests added

* Added dsl for other recyclerview releavant callbacks

* Updated README

* Updated Main Example

* Update

* More Docs

* Fix a test

* More assertions

* More Readme

* Better tests
  • Loading branch information
sockeqwe authored Jul 17, 2019
1 parent d18dc60 commit 16551b1
Show file tree
Hide file tree
Showing 33 changed files with 1,579 additions and 132 deletions.
104 changes: 102 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
# AdapterDelegates
Read the motivation for this project in [my blog post](http://hannesdorfmann.com/android/adapter-delegates).

For Kotlin, there is a convenient DSL. Check out that section in the documentation below.

## Dependencies
This library is available on maven central:

```groovy
compile 'com.hannesdorfmann:adapterdelegates4:4.0.0'
implementation 'com.hannesdorfmann:adapterdelegates4:4.1.0'
```
[![Build Status](https://travis-ci.org/sockeqwe/AdapterDelegates.svg?branch=master)](https://travis-ci.org/sockeqwe/AdapterDelegates)

Expand All @@ -14,7 +16,7 @@ Please note that since 4.0 the group id has been changed to `adapterdelegates4`.
### Snapshot

```groovy
compile 'com.hannesdorfmann:adapterdelegates4:4.0.1-SNAPSHOT'
implementation 'com.hannesdorfmann:adapterdelegates4:4.1.1-SNAPSHOT'
```

You also have to add the url to the snapshot repository:
Expand Down Expand Up @@ -200,6 +202,104 @@ public class DiffAdapter extends AsyncListDifferDelegationAdapter<Animal> {
}
```

## Kotlin DSL
There are 2 more artifacts for kotlin users that allow you to write Adapter Delegates more convenient by providing a `DSL`:

```
implementation 'com.hannesdorfmann:adapterdelegates4-kotlin-dsl:4.1.0'
// If you use Kotlin Android Extensions and synthetic properties (alternative to findViewById())
implementation 'com.hannesdorfmann:adapterdelegates4-kotlin-dsl-layoutcontainer:4.1.0'
```

Now instead of creating your own class which extends `AdapterDelegate<T>` and implement the `onCreateViewHolder` and `onBindViewHolder` you can use the following Kotlin DSL to write the same `CatListItemAdapterDelegate` shown in the example above:


```kotlin
fun catAdapterDelegate(itemClickedListener : (Cat) -> Unit) = adapterDelegate<Cat, Animal>(R.layout.item_cat) {

// This is the initializer block where you initialize the ViewHolder.
// Its called one time only in onCreateViewHolder.
// this is where you can call findViewById() and setup click listeners etc.

val name : TextView = findViewById(R.id.name)
name.setClickListener { itemClickedListener(item) } // Item is automatically set for you. It's set lazily though (set in onBindViewHolder()). So only use it for deferred calls like clickListeners.

bind { diffPayloads -> // diffPayloads is a List<Any> containing the Payload from your DiffUtils
// This is called anytime onBindViewHolder() is called
name.text = item.name // Item is of type Cat and is the current bound item.
}
}
```

In case you want to use kotlin android extensions and synthetic properties (as alternative to findViewById()) use `adapterDelegateLayoutContainer` instead of `adapterDelegate` like this:

```kotlin
fun catAdapterDelegate(itemClickedListener : (Cat) -> Unit) = adapterDelegateLayoutContainer<Cat, Animal>(R.layout.item_cat) {

name.setClickListener { itemClickedListener(item) } // no need for findViewById(). Name is imported as synthetic property from kotlinx.android.synthetic.main.item_cat

bind { diffPayloads ->
name.text = item.name
}
}
```

As you see, thanks to Kotlin DSL you can write the same adapter in much less code.
`isForViewType()` is implemented by checking the two generic parameters.
In the example above it is `Cat instanceof Animal`.
If you want to provide your own `isForViewType()` implementation you have to provide a parameter `on` and return true or false:

```kotlin
adapterDelegate<Cat, Animal> (
layout = R.layout.item_cat,
on = { item: Animal, items: List, position: Int ->
if (item is Cat && position == 0)
true // return true: this adapterDelegate handles it
else
false // return false
}
){
...
bind { ... }
}
```

The same `on` parameter is available for `adapterDelegateLayoutContainer()` DSL.


### Danger: Memory leaks!
Never ever use a top level `val` to hold a reference as top level `val` are static and will hold a reference to the adapter delegate and underlying ViewHolder and underlying android context (like activity) forever.
**Don't do this:**


```kotlin
// top level property inside CatDelegate.kt
val catDelegate = adapterDelegate<Cat, Animal> {
...
bind { ... }
}
```

**Instead use top level functions:**

```kotlin
// top level function inside CatDelegate.kt
fun catAdapterDelegate() = adapterDelegate<Cat, Animal> {
...
bind { ... }
}
```

## Pagination
There is an additional artifact for the pagination library:

```gradle
implementation 'com.hannesdorfmann:adapterdelegates4-pagination:4.1.0'
```

Use `PagedListDelegationAdapter`.

## Fallback AdapterDelegate
What if your adapter's data source contains a certain element you don't have registered an `AdapterDelegate` for? In this case the `AdapterDelegateManager` will throw an exception at runtime. However, this is not always what you want. You can specify a fallback `AdapterDelegate` that will be used if no other `AdapterDelegate` has been found to handle a certain view type.

Expand Down
12 changes: 12 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'

androidExtensions {
experimental = true
}

android {
compileSdkVersion rootProject.ext.compileSdk
Expand Down Expand Up @@ -36,5 +42,11 @@ dependencies {

implementation project(':library')
implementation project(':paging')
implementation project(':kotlin-dsl')
implementation project(':kotlin-dsl-layoutcontainer')
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
}
repositories {
mavenCentral()
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ protected void onCreate(Bundle savedInstanceState) {

RecyclerView rv = (RecyclerView) findViewById(R.id.recyclerView);
rv.setLayoutManager(new LinearLayoutManager(this));
MainAdapter adapter = new MainAdapter(this, getAnimals());
MainListAdapter adapter = new MainListAdapter(this);
adapter.setItems(getAnimals());
rv.setAdapter(adapter);


Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.hannesdorfmann.adapterdelegates4.sample

import android.app.Activity
import com.hannesdorfmann.adapterdelegates4.ListDelegationAdapter
import com.hannesdorfmann.adapterdelegates4.sample.adapterdelegates.AdvertisementAdapterDelegate
import com.hannesdorfmann.adapterdelegates4.sample.adapterdelegates.DogAdapterDelegate
import com.hannesdorfmann.adapterdelegates4.sample.adapterdelegates.GeckoAdapterDelegate
import com.hannesdorfmann.adapterdelegates4.sample.adapterdelegates.SnakeListItemAdapterDelegate
import com.hannesdorfmann.adapterdelegates4.sample.dsl.catAdapterDelegate
import com.hannesdorfmann.adapterdelegates4.sample.model.DisplayableItem

class MainListAdapter(activity: Activity) : ListDelegationAdapter<List<DisplayableItem>>(
AdvertisementAdapterDelegate(activity),
catAdapterDelegate(),
DogAdapterDelegate(activity),
GeckoAdapterDelegate(activity),
SnakeListItemAdapterDelegate(activity)
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.hannesdorfmann.adapterdelegates4.sample.dsl

import android.util.Log
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateLayoutContainer
import com.hannesdorfmann.adapterdelegates4.sample.R
import com.hannesdorfmann.adapterdelegates4.sample.model.Cat
import com.hannesdorfmann.adapterdelegates4.sample.model.DisplayableItem
import kotlinx.android.synthetic.main.item_cat.*

// Example
fun catAdapterDelegate() = adapterDelegateLayoutContainer<Cat, DisplayableItem>(R.layout.item_cat) {

name.setOnClickListener {
Log.d("Click", "Click on $item")
}

bind {
name.text = item.name
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,26 @@

package com.hannesdorfmann.adapterdelegates4.sample.model;

import androidx.annotation.NonNull;

/**
* @author Hannes Dorfmann
*/
public class Animal implements DisplayableItem {

private String name;
private String name;

public Animal(String name) {
this.name = name;
}

public Animal(String name) {
this.name = name;
}
public String getName() {
return name;
}

public String getName() {
return name;
}
@NonNull
@Override
public String toString() {
return name + " " + super.toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,4 @@ public class Cat extends Animal {
public Cat(String name) {
super(name);
}


}
4 changes: 3 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
ext.kotlin_version = '1.3.40'
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.3.2'
classpath 'com.android.tools.build:gradle:3.4.1'
classpath 'com.vanniktech:gradle-maven-publish-plugin:0.6.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"

// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true

VERSION_NAME=4.0.1-SNAPSHOT
VERSION_NAME=4.1.0-SNAPSHOT
VERSION_CODE=401
GROUP=com.hannesdorfmann

Expand Down
4 changes: 2 additions & 2 deletions gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#Sun Mar 31 17:59:48 CEST 2019
#Wed Jul 03 19:42:09 CEST 2019
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip
1 change: 1 addition & 0 deletions kotlin-dsl-layoutcontainer/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
Loading

0 comments on commit 16551b1

Please sign in to comment.