Skip to content

Commit

Permalink
Carousel building with Kotlin DSL - Step 1 (#967)
Browse files Browse the repository at this point in the history
* apply commit eeff265

* fix wrong merge

* Move the builder in sample app waiting for a more robust api

* Actually let's do it for the carousel no snap

* Use new DSL in sample app

* ktlint

* ktlint

* Simplify, BaseEpoxyController class -> ModelCollector interface

* ktlint

Co-authored-by: Eli Hart <eli.hart@airbnb.com>
Co-authored-by: Emmanuel Boudrant <eboudrant@netflix.com>
  • Loading branch information
3 people authored May 13, 2020
1 parent cb9673f commit 7c4ac9c
Show file tree
Hide file tree
Showing 6 changed files with 71 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
* treated as immutable and never modified again. This is necessary for adapter updates to be
* accurate.
*/
public abstract class EpoxyController implements StickyHeaderCallbacks {
public abstract class EpoxyController implements ModelCollector, StickyHeaderCallbacks {

/**
* We check that the adapter is not connected to multiple recyclerviews, but when a fragment has
Expand Down Expand Up @@ -127,8 +127,8 @@ public EpoxyController(Handler modelBuildingHandler, Handler diffingHandler) {

@Retention(RetentionPolicy.SOURCE)
@IntDef({RequestedModelBuildType.NONE,
RequestedModelBuildType.NEXT_FRAME,
RequestedModelBuildType.DELAYED})
RequestedModelBuildType.NEXT_FRAME,
RequestedModelBuildType.DELAYED})
private @interface RequestedModelBuildType {
int NONE = 0;
/** A request has been made to build models immediately. It is posted. */
Expand Down Expand Up @@ -463,7 +463,7 @@ private void assertNotBuildingModels() {
* Add the model to this controller. Can only be called from inside {@link
* EpoxyController#buildModels()}.
*/
protected void add(@NonNull EpoxyModel<?> model) {
public void add(@NonNull EpoxyModel<?> model) {
model.addTo(this);
}

Expand All @@ -475,7 +475,7 @@ protected void add(@NonNull EpoxyModel<?>... modelsToAdd) {
modelsBeingBuilt.ensureCapacity(modelsBeingBuilt.size() + modelsToAdd.length);

for (EpoxyModel<?> model : modelsToAdd) {
model.addTo(this);
add(model);
}
}

Expand All @@ -487,7 +487,7 @@ protected void add(@NonNull List<? extends EpoxyModel<?>> modelsToAdd) {
modelsBeingBuilt.ensureCapacity(modelsBeingBuilt.size() + modelsToAdd.size());

for (EpoxyModel<?> model : modelsToAdd) {
model.addTo(this);
add(model);
}
}

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

/**
* Interface used to collect models. Used by [EpoxyController]. It is also convenient to build DSL
* helpers for carousel: @link https://github.com/airbnb/epoxy/issues/847.
*/
interface ModelCollector {

fun add(model: EpoxyModel<*>)
}
2 changes: 2 additions & 0 deletions epoxy-processor/src/main/java/com/airbnb/epoxy/ClassNames.kt
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ object ClassNames {
@JvmField
val EPOXY_CONTROLLER = get(PKG_EPOXY, "EpoxyController")!!
@JvmField
val MODEL_COLLECTOR = get(PKG_EPOXY, "ModelCollector")!!
@JvmField
val EPOXY_STYLE_BUILDER_CALLBACK = get(PKG_EPOXY, "StyleBuilderCallback")!!
@JvmField
val EPOXY_CONTROLLER_HELPER = get(PKG_EPOXY, "ControllerHelper")!!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ internal class KotlinModelBuilderExtensionWriter(
)

FunSpec.builder(getMethodName(model)).run {
receiver(ClassNames.EPOXY_CONTROLLER.toKPoet())
receiver(ClassNames.MODEL_COLLECTOR.toKPoet())
val params = constructor?.params ?: listOf()
addParameters(params.toKParams())

Expand All @@ -117,13 +117,14 @@ internal class KotlinModelBuilderExtensionWriter(
addModifiers(KModifier.INLINE)
if (constructorIsNotPublic) addModifiers(KModifier.INTERNAL)

addStatement("add(")
beginControlFlow(
"$tick%T$tick(${params.joinToString(", ") { it.name }}).apply",
modelClass
)
addStatement("modelInitializer()")
endControlFlow()
addStatement(".addTo(this)")
addStatement(")")
return build()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.airbnb.epoxy.EpoxyRecyclerView
import com.airbnb.epoxy.EpoxyVisibilityTracker
import com.airbnb.epoxy.kotlinsample.models.CarouselItemCustomViewModel_
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.itemCustomView
import com.airbnb.epoxy.kotlinsample.models.itemEpoxyHolder
import com.airbnb.epoxy.kotlinsample.models.itemViewBindingEpoxyHolder
import com.airbnb.epoxy.kotlinsample.views.carouselNoSnap

class MainActivity : AppCompatActivity() {
private lateinit var recyclerView: EpoxyRecyclerView
Expand Down Expand Up @@ -71,18 +71,15 @@ class MainActivity : AppCompatActivity() {
}
}

carouselNoSnap {
carouselNoSnapBuilder {
id("carousel $i")
models(mutableListOf<CarouselItemCustomViewModel_>().apply {
val lastPage = 10
for (j in 0 until lastPage) {
add(
CarouselItemCustomViewModel_()
.id("carousel $i-$j")
.title("Page $j / $lastPage")
)
val lastPage = 10
for (j in 0 until lastPage) {
carouselItemCustomView {
id("carousel $i-$j")
title("Page $j / $lastPage")
}
})
}
}

// Since data classes do not use code generation, there's no extension generated here
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.airbnb.epoxy.kotlinsample.helpers

import com.airbnb.epoxy.EpoxyModel
import com.airbnb.epoxy.ModelCollector
import com.airbnb.epoxy.kotlinsample.views.CarouselNoSnapModelBuilder
import com.airbnb.epoxy.kotlinsample.views.CarouselNoSnapModel_

/**
* Example that illustrate how to add a builder for nested list (ex: carousel) that allow building
* it using DSL :
*
* carouselBuilder {
* id(...)
* for (...) {
* carouselItemCustomView {
* id(...)
* }
* }
* }
*
* @link https://github.com/airbnb/epoxy/issues/847
*/
fun ModelCollector.carouselNoSnapBuilder(builder: EpoxyCarouselNoSnapBuilder.() -> Unit): CarouselNoSnapModel_ {
val carouselBuilder = EpoxyCarouselNoSnapBuilder().apply { builder() }
add(carouselBuilder.carouselNoSnapModel)
return carouselBuilder.carouselNoSnapModel
}

class EpoxyCarouselNoSnapBuilder(
internal val carouselNoSnapModel: CarouselNoSnapModel_ = CarouselNoSnapModel_()
) : ModelCollector, CarouselNoSnapModelBuilder by carouselNoSnapModel {
private val models = mutableListOf<EpoxyModel<*>>()

override fun add(model: EpoxyModel<*>) {
models.add(model)

// Set models list every time a model is added so that it can run debug validations to
// ensure it is still valid to mutate the carousel model.
carouselNoSnapModel.models(models)
}
}

0 comments on commit 7c4ac9c

Please sign in to comment.