Skip to content

Commit

Permalink
Model group building with Kotlin DSL (#1012)
Browse files Browse the repository at this point in the history
* Add an empty constructor for `EpoxyModelGroup`, protected visibility only.

* If the model implements `ModelCollector` make the build too so the DSL can be used.

* Use the new model group DSL in the sample app

* Add `implementsModelCollector` in memoizer

* Fix memoizer implementation

* Add   `shouldSaveViewState` property to model group

* Make `EpoxyModelGroup` a  `ModelCollector` and create a `GroupModel` class with the `@EpoxyModelClass` annotation so it can be used in the DSL

* Can be simnplified

* force codegen on pr check

* revert last change

* Move ModelCollector code to GroupModel as EpoxyModelGroup can't be used directly in the DSL

* Add a small doc/usage

* Check class hierarchy for ModelCollector

* Reset shouldSaveViewState to private

Co-authored-by: Emmanuel Boudrant <eboudrant@netflix.com>
  • Loading branch information
eboudrant and eboudrant authored Jul 23, 2020
1 parent 141894c commit 1f14d64
Show file tree
Hide file tree
Showing 9 changed files with 143 additions and 6 deletions.
41 changes: 35 additions & 6 deletions epoxy-adapter/src/main/java/com/airbnb/epoxy/EpoxyModelGroup.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import androidx.annotation.CallSuper;
import androidx.annotation.LayoutRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

/**
* An {@link EpoxyModel} that contains other models, and allows you to combine those models in
Expand Down Expand Up @@ -59,9 +60,12 @@
@SuppressWarnings("rawtypes")
public class EpoxyModelGroup extends EpoxyModelWithHolder<ModelGroupHolder> {

protected final List<? extends EpoxyModel<?>> models;
/** By default we save view state if any of the models need to save state. */
private final boolean shouldSaveViewState;
protected final List<EpoxyModel<?>> models;

private boolean shouldSaveViewStateDefault = false;

@Nullable
private Boolean shouldSaveViewState = null;

/**
* @param layoutRes The layout to use with these models.
Expand All @@ -83,7 +87,7 @@ public EpoxyModelGroup(@LayoutRes int layoutRes, EpoxyModel<?>... models) {
* @param layoutRes The layout to use with these models.
* @param models The models that will be used to bind the views in the given layout.
*/
private EpoxyModelGroup(@LayoutRes int layoutRes, List<? extends EpoxyModel<?>> models) {
private EpoxyModelGroup(@LayoutRes int layoutRes, List<EpoxyModel<?>> models) {
if (models.isEmpty()) {
throw new IllegalArgumentException("Models cannot be empty");
}
Expand All @@ -99,8 +103,22 @@ private EpoxyModelGroup(@LayoutRes int layoutRes, List<? extends EpoxyModel<?>>
break;
}
}
// By default we save view state if any of the models need to save state.
shouldSaveViewStateDefault = saveState;
}

shouldSaveViewState = saveState;
/**
* Constructor use for DSL
*/
protected EpoxyModelGroup() {
models = new ArrayList<>();
shouldSaveViewStateDefault = false;
}

protected void addModel(@NonNull EpoxyModel<?> model) {
// By default we save view state if any of the models need to save state.
shouldSaveViewStateDefault |= model.shouldSaveViewState();
models.add(model);
}

@CallSuper
Expand Down Expand Up @@ -217,11 +235,22 @@ protected final int getDefaultLayout() {
"You should set a layout with layout(...) instead of using this.");
}

@NonNull
public EpoxyModelGroup shouldSaveViewState(boolean shouldSaveViewState) {
onMutation();
this.shouldSaveViewState = shouldSaveViewState;
return this;
}

@Override
public boolean shouldSaveViewState() {
// By default state is saved if any of the models have saved state enabled.
// Override this if you need custom behavior.
return shouldSaveViewState;
if (shouldSaveViewState != null) {
return shouldSaveViewState;
} else {
return shouldSaveViewStateDefault;
}
}

/**
Expand Down
28 changes: 28 additions & 0 deletions epoxy-adapter/src/main/java/com/airbnb/epoxy/GroupModel.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.airbnb.epoxy

/**
* An [EpoxyModelGroup] usable in a DSL manner via the [group] extension.
* <p>
* Example:
* ```
* group {
* id("photos")
* layout(R.layout.photo_grid)
*
* // add your models here, example:
* for (photo in photos) {
* imageView {
* id(photo.id)
* url(photo.url)
* }
* }
* }
* ```
*/
@EpoxyModelClass
abstract class GroupModel : EpoxyModelGroup(), ModelCollector {

override fun add(model: EpoxyModel<*>) {
super.addModel(model)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,20 @@ class Memoizer(
}
}
}

private val implementsModelCollectorMap = mutableMapOf<Name, Boolean>()
fun implementsModelCollector(classElement: TypeElement): Boolean {
return synchronized(typeMap) {
implementsModelCollectorMap.getOrPut(classElement.qualifiedName) {
classElement.interfaces.any {
it.toString() == ClassNames.MODEL_COLLECTOR.toString()
} || classElement.superClassElement(types)?.let { superClassElement ->
// Also check the class hierarchy
implementsModelCollector(superClassElement)
} ?: false
}
}
}
}

private val viewModelAnnotations = listOf(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ class ModelBuilderInterfaceWriter(
addTypeVariables(modelInfo.typeVariables)
addMethods(interfaceMethods)

if (modelInfo.memoizer.implementsModelCollector(modelInfo.superClassElement)) {
// If the model implements "ModelCollector" we want the builder too
addSuperinterface(ClassNames.MODEL_COLLECTOR)
}

addOriginatingElement(modelInfo.superClassElement)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,12 @@ public int value() {
return value;
}

@Override
public EpoxyModelGroupWithAnnotations_ shouldSaveViewState(boolean shouldSaveViewState) {
super.shouldSaveViewState(shouldSaveViewState);
return this;
}

@Override
public EpoxyModelGroupWithAnnotations_ id(long id) {
super.id(id);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.airbnb.epoxy.EpoxyRecyclerView
import com.airbnb.epoxy.EpoxyVisibilityTracker
import com.airbnb.epoxy.group
import com.airbnb.epoxy.kotlinsample.helpers.carouselNoSnapBuilder
import com.airbnb.epoxy.kotlinsample.models.ItemDataClass
import com.airbnb.epoxy.kotlinsample.models.ItemViewBindingDataClass
import com.airbnb.epoxy.kotlinsample.models.carouselItemCustomView
import com.airbnb.epoxy.kotlinsample.models.coloredSquareView
import com.airbnb.epoxy.kotlinsample.models.itemCustomView
import com.airbnb.epoxy.kotlinsample.models.itemEpoxyHolder
import com.airbnb.epoxy.kotlinsample.models.itemViewBindingEpoxyHolder
Expand All @@ -32,6 +34,26 @@ class MainActivity : AppCompatActivity() {

recyclerView.withModels {

group {
id("epoxyModelGroupDsl")
layout(R.layout.vertical_linear_group)

coloredSquareView {
id("coloredSquareView 1")
color(Color.DKGRAY)
}

coloredSquareView {
id("coloredSquareView 2")
color(Color.GRAY)
}

coloredSquareView {
id("coloredSquareView 3")
color(Color.LTGRAY)
}
}

for (i in 0 until 100) {
dataBindingItem {
id("data binding $i")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.airbnb.epoxy.kotlinsample.models

import android.content.Context
import android.graphics.Color
import android.util.AttributeSet
import android.view.View
import androidx.annotation.ColorInt
import com.airbnb.epoxy.ModelProp
import com.airbnb.epoxy.ModelView
import com.airbnb.epoxy.kotlinsample.R

@ModelView(defaultLayout = R.layout.colored_square_view)
class ColoredSquareView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {

@JvmOverloads
@ModelProp
fun color(@ColorInt color: Int = Color.RED) {
setBackgroundColor(color)
}
}
4 changes: 4 additions & 0 deletions kotlinsample/src/main/res/layout/colored_square_view.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<com.airbnb.epoxy.kotlinsample.models.ColoredSquareView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="56dp"
android:layout_height="56dp" />
5 changes: 5 additions & 0 deletions kotlinsample/src/main/res/layout/vertical_linear_group.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" />

0 comments on commit 1f14d64

Please sign in to comment.