diff --git a/epoxy-modelfactory/.gitignore b/epoxy-modelfactory/.gitignore
new file mode 100644
index 0000000000..796b96d1c4
--- /dev/null
+++ b/epoxy-modelfactory/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/epoxy-modelfactory/build.gradle b/epoxy-modelfactory/build.gradle
new file mode 100644
index 0000000000..d7d3d282e5
--- /dev/null
+++ b/epoxy-modelfactory/build.gradle
@@ -0,0 +1,59 @@
+import org.gradle.internal.jvm.Jvm
+
+apply plugin: 'com.android.library'
+apply from: 'build.workaround-missing-resource.gradle'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-kapt'
+
+android {
+ compileSdkVersion rootProject.COMPILE_SDK_VERSION
+
+ defaultConfig {
+ minSdkVersion rootProject.MIN_SDK_VERSION
+ targetSdkVersion rootProject.TARGET_SDK_VERSION
+ }
+}
+
+configurations.all { strategy ->
+ strategy.resolutionStrategy.force rootProject.deps.junit, rootProject.deps.robolectric,
+ rootProject.deps.mockito
+}
+
+dependencies {
+ compile project(':epoxy-adapter')
+ compile rootProject.deps.paris
+
+ testCompile project(':epoxy-adapter')
+ // Need to include the processor directly since we create an instance of it in code for testing
+ testCompile project(':epoxy-processor')
+ testCompile project(':epoxy-processortest')
+ testCompile rootProject.deps.googleTestingCompile
+ testCompile rootProject.deps.junit
+ testCompile rootProject.deps.kotlin
+ // Need to include the processor directly since we create an instance of it in code for testing
+ testCompile rootProject.deps.parisProcessor
+ testCompile rootProject.deps.robolectric
+ testCompile files(getRuntimeJar())
+ testCompile files(Jvm.current().getToolsJar())
+
+ kaptTest project(':epoxy-processor')
+}
+
+// Javadoc isn't working well with Kotlin :(
+tasks.withType(Javadoc).all { enabled = false }
+
+static def getRuntimeJar() {
+ try {
+ final File javaBase = new File(System.getProperty("java.home")).getCanonicalFile()
+ File runtimeJar = new File(javaBase, "lib/rt.jar")
+ if (runtimeJar.exists()) {
+ return runtimeJar
+ }
+ runtimeJar = new File(javaBase, "jre/lib/rt.jar")
+ return runtimeJar.exists() ? runtimeJar : null
+ } catch (IOException e) {
+ throw new RuntimeException(e)
+ }
+}
+
+apply from: rootProject.file('gradle/gradle-maven-push.gradle')
diff --git a/epoxy-modelfactory/build.workaround-missing-resource.gradle b/epoxy-modelfactory/build.workaround-missing-resource.gradle
new file mode 100644
index 0000000000..7637db440d
--- /dev/null
+++ b/epoxy-modelfactory/build.workaround-missing-resource.gradle
@@ -0,0 +1,23 @@
+// Source - https://github.com/nenick/AndroidStudioAndRobolectric/blob/master/app/build.workaround-missing-resource.gradle
+// Workaround for missing test resources when running unit tests within android studio.
+// This copies the test resources next to the test classes for each variant.
+// Tracked at https://github.com/nenick/AndroidStudioAndRobolectric/issues/7
+// Original solution comes from https://code.google.com/p/android/issues/detail?id=136013#c10
+// See also https://code.google.com/p/android/issues/detail?id=64887
+gradle.projectsEvaluated {
+ // Base path which is recognized by android studio.
+ def testClassesPath = "${buildDir}/intermediates/classes/test/"
+ // Copy must be done for each variant.
+ def variants = android.libraryVariants.collect()
+
+ variants.each { variant ->
+ def variationName = variant.name.capitalize()
+ def variationPath = variant.buildType.name
+
+ // Specific copy task for each variant
+ def copyTestResourcesTask = project.tasks.create("copyTest${variationName}Resources", Copy)
+ copyTestResourcesTask.from("${projectDir}/src/test/resources")
+ copyTestResourcesTask.into("${testClassesPath}/${variationPath}")
+ copyTestResourcesTask.execute()
+ }
+}
\ No newline at end of file
diff --git a/epoxy-modelfactory/gradle.properties b/epoxy-modelfactory/gradle.properties
new file mode 100644
index 0000000000..834da184d1
--- /dev/null
+++ b/epoxy-modelfactory/gradle.properties
@@ -0,0 +1,3 @@
+POM_NAME=Epoxy Model Factory
+POM_ARTIFACT_ID=epoxy-modelfactory
+POM_PACKAGING=jar
\ No newline at end of file
diff --git a/epoxy-modelfactory/src/main/AndroidManifest.xml b/epoxy-modelfactory/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..884085bc7f
--- /dev/null
+++ b/epoxy-modelfactory/src/main/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
diff --git a/epoxy-modelfactory/src/main/java/com/airbnb/epoxy/ModelProperties.java b/epoxy-modelfactory/src/main/java/com/airbnb/epoxy/ModelProperties.java
new file mode 100644
index 0000000000..1905033b9c
--- /dev/null
+++ b/epoxy-modelfactory/src/main/java/com/airbnb/epoxy/ModelProperties.java
@@ -0,0 +1,45 @@
+package com.airbnb.epoxy;
+
+import android.support.annotation.DrawableRes;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.view.View.OnClickListener;
+
+import com.airbnb.paris.styles.Style;
+
+import java.util.List;
+
+public interface ModelProperties {
+
+ @NonNull
+ String getId();
+
+ boolean has(@NonNull String propertyName);
+
+ boolean getBoolean(@NonNull String propertyName);
+
+ double getDouble(@NonNull String propertyName);
+
+ @DrawableRes
+ int getDrawableRes(@NonNull String propertyName);
+
+ @NonNull
+ List extends EpoxyModel>> getEpoxyModelList(@NonNull String propertyName);
+
+ int getInt(@NonNull String propertyName);
+
+ @NonNull
+ OnClickListener getOnClickListener(@NonNull String propertyName);
+
+ @NonNull
+ String getString(@NonNull String propertyName);
+
+ @NonNull
+ List getStringList(@NonNull String propertyName);
+
+ /**
+ * @return Null to apply the default style.
+ */
+ @Nullable
+ Style getStyle();
+}
diff --git a/epoxy-modelfactory/src/main/res/values/strings.xml b/epoxy-modelfactory/src/main/res/values/strings.xml
new file mode 100644
index 0000000000..5b7aab467d
--- /dev/null
+++ b/epoxy-modelfactory/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ Epoxy Model Factory
+
diff --git a/epoxy-modelfactory/src/test/java/com/airbnb/epoxy/FromModelPropertiesKotlinTest.kt b/epoxy-modelfactory/src/test/java/com/airbnb/epoxy/FromModelPropertiesKotlinTest.kt
new file mode 100644
index 0000000000..a7345d033c
--- /dev/null
+++ b/epoxy-modelfactory/src/test/java/com/airbnb/epoxy/FromModelPropertiesKotlinTest.kt
@@ -0,0 +1,122 @@
+package com.airbnb.epoxy
+
+import android.view.View
+import com.airbnb.epoxymodelfactory.R
+import com.airbnb.paris.styles.Style
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Test
+
+/**
+ * Asserts that using from(ModelProperties) to create a model applies the property values correctly
+ */
+class FromModelPropertiesKotlinTest {
+
+ @Test
+ fun getId() {
+ val model = TestModelPropertiesKotlinViewModel_.from(TestModelProperties(id = "100"))
+ assertFalse(model.hasDefaultId())
+ }
+
+ @Test
+ fun getBoolean() {
+ val model = TestModelPropertiesKotlinViewModel_.from(TestModelProperties(booleanValue = true))
+ assertEquals(true, model.booleanValue())
+ }
+
+ @Test
+ fun getDouble() {
+ val model = TestModelPropertiesKotlinViewModel_.from(TestModelProperties(doubleValue = 42.0))
+ assertEquals(42.0, model.doubleValue(), 0.0)
+ }
+
+ @Test
+ fun getDrawableRes() {
+ val drawableRes = R.drawable.abc_ic_star_black_48dp
+ val model =
+ TestModelPropertiesKotlinViewModel_.from(TestModelProperties(drawableRes = drawableRes))
+ assertEquals(drawableRes, model.drawableRes())
+ }
+
+ @Test
+ fun getEpoxyModelList() {
+ val epoxyModelList = emptyList>()
+ val model =
+ TestModelPropertiesKotlinViewModel_.from(TestModelProperties(epoxyModelList = epoxyModelList))
+ assertEquals(epoxyModelList, model.epoxyModelList())
+ }
+
+ @Test
+ fun getInt() {
+ val model = TestModelPropertiesKotlinViewModel_.from(TestModelProperties(intValue = 51))
+ assertEquals(51, model.intValue())
+ }
+
+ @Test
+ fun getOnClickListener() {
+ val clickListener = View.OnClickListener { }
+ val model =
+ TestModelPropertiesKotlinViewModel_.from(TestModelProperties(onClickListener = clickListener))
+ assertEquals(clickListener, model.onClickListener())
+ }
+
+ @Test
+ fun getString() {
+ val model =
+ TestModelPropertiesKotlinViewModel_.from(TestModelProperties(stringValue = "ModelFactory"))
+ assertEquals("ModelFactory", model.stringValue())
+ }
+
+ @Test
+ fun getStringList() {
+ val stringList = listOf("Model", "Factory")
+ val model = TestModelPropertiesKotlinViewModel_.from(TestModelProperties(stringList = stringList))
+ assertEquals(stringList, model.stringList())
+ }
+
+ class TestModelProperties(
+ private val id: String = "",
+ private val booleanValue: Boolean? = null,
+ private val doubleValue: Double? = null,
+ private val drawableRes: Int? = null,
+ private val epoxyModelList: List>? = null,
+ private val intValue: Int? = null,
+ private val onClickListener: View.OnClickListener? = null,
+ private val stringValue: String? = null,
+ private val stringList: List? = null,
+ private val styleValue: Style? = null
+ ) : ModelProperties {
+ override fun getId() = id
+
+ override fun has(propertyName: String): Boolean {
+ return mapOf(
+ "booleanValue" to booleanValue,
+ "doubleValue" to doubleValue,
+ "drawableRes" to drawableRes,
+ "epoxyModelList" to epoxyModelList,
+ "intValue" to intValue,
+ "onClickListener" to onClickListener,
+ "stringList" to stringList,
+ "stringValue" to stringValue
+ )[propertyName] != null
+ }
+
+ override fun getBoolean(propertyName: String) = booleanValue!!
+
+ override fun getDouble(propertyName: String) = doubleValue!!
+
+ override fun getDrawableRes(propertyName: String) = drawableRes!!
+
+ override fun getEpoxyModelList(propertyName: String) = epoxyModelList!!
+
+ override fun getInt(propertyName: String) = intValue!!
+
+ override fun getOnClickListener(propertyName: String) = onClickListener!!
+
+ override fun getString(propertyName: String) = stringValue!!
+
+ override fun getStringList(propertyName: String) = stringList!!
+
+ override fun getStyle() = styleValue
+ }
+}
diff --git a/epoxy-modelfactory/src/test/java/com/airbnb/epoxy/FromModelPropertiesTest.kt b/epoxy-modelfactory/src/test/java/com/airbnb/epoxy/FromModelPropertiesTest.kt
new file mode 100644
index 0000000000..0e86d107c0
--- /dev/null
+++ b/epoxy-modelfactory/src/test/java/com/airbnb/epoxy/FromModelPropertiesTest.kt
@@ -0,0 +1,122 @@
+package com.airbnb.epoxy
+
+import android.view.View
+import com.airbnb.epoxymodelfactory.R
+import com.airbnb.paris.styles.Style
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Test
+
+/**
+ * Asserts that using from(ModelProperties) to create a model applies the property values correctly
+ */
+class FromModelPropertiesTest {
+
+ @Test
+ fun getId() {
+ val model = TestModelPropertiesViewModel_.from(TestModelProperties(id = "100"))
+ assertFalse(model.hasDefaultId())
+ }
+
+ @Test
+ fun getBoolean() {
+ val model = TestModelPropertiesViewModel_.from(TestModelProperties(booleanValue = true))
+ assertEquals(true, model.booleanValue())
+ }
+
+ @Test
+ fun getDouble() {
+ val model = TestModelPropertiesViewModel_.from(TestModelProperties(doubleValue = 42.0))
+ assertEquals(42.0, model.doubleValue(), 0.0)
+ }
+
+ @Test
+ fun getDrawableRes() {
+ val drawableRes = R.drawable.abc_ic_star_black_48dp
+ val model =
+ TestModelPropertiesViewModel_.from(TestModelProperties(drawableRes = drawableRes))
+ assertEquals(drawableRes, model.drawableRes())
+ }
+
+ @Test
+ fun getEpoxyModelList() {
+ val epoxyModelList = emptyList>()
+ val model =
+ TestModelPropertiesViewModel_.from(TestModelProperties(epoxyModelList = epoxyModelList))
+ assertEquals(epoxyModelList, model.epoxyModelList())
+ }
+
+ @Test
+ fun getInt() {
+ val model = TestModelPropertiesViewModel_.from(TestModelProperties(intValue = 51))
+ assertEquals(51, model.intValue())
+ }
+
+ @Test
+ fun getOnClickListener() {
+ val clickListener = View.OnClickListener { }
+ val model =
+ TestModelPropertiesViewModel_.from(TestModelProperties(onClickListener = clickListener))
+ assertEquals(clickListener, model.onClickListener())
+ }
+
+ @Test
+ fun getString() {
+ val model =
+ TestModelPropertiesViewModel_.from(TestModelProperties(stringValue = "ModelFactory"))
+ assertEquals("ModelFactory", model.stringValue())
+ }
+
+ @Test
+ fun getStringList() {
+ val stringList = listOf("Model", "Factory")
+ val model = TestModelPropertiesViewModel_.from(TestModelProperties(stringList = stringList))
+ assertEquals(stringList, model.stringList())
+ }
+
+ class TestModelProperties(
+ private val id: String = "",
+ private val booleanValue: Boolean? = null,
+ private val doubleValue: Double? = null,
+ private val drawableRes: Int? = null,
+ private val epoxyModelList: List>? = null,
+ private val intValue: Int? = null,
+ private val onClickListener: View.OnClickListener? = null,
+ private val stringValue: String? = null,
+ private val stringList: List? = null,
+ private val styleValue: Style? = null
+ ) : ModelProperties {
+ override fun getId() = id
+
+ override fun has(propertyName: String): Boolean {
+ return mapOf(
+ "booleanValue" to booleanValue,
+ "doubleValue" to doubleValue,
+ "drawableRes" to drawableRes,
+ "epoxyModelList" to epoxyModelList,
+ "intValue" to intValue,
+ "onClickListener" to onClickListener,
+ "stringList" to stringList,
+ "stringValue" to stringValue
+ )[propertyName] != null
+ }
+
+ override fun getBoolean(propertyName: String) = booleanValue!!
+
+ override fun getDouble(propertyName: String) = doubleValue!!
+
+ override fun getDrawableRes(propertyName: String) = drawableRes!!
+
+ override fun getEpoxyModelList(propertyName: String) = epoxyModelList!!
+
+ override fun getInt(propertyName: String) = intValue!!
+
+ override fun getOnClickListener(propertyName: String) = onClickListener!!
+
+ override fun getString(propertyName: String) = stringValue!!
+
+ override fun getStringList(propertyName: String) = stringList!!
+
+ override fun getStyle() = styleValue
+ }
+}
diff --git a/epoxy-modelfactory/src/test/java/com/airbnb/epoxy/ModelFactoryViewProcessorTest.kt b/epoxy-modelfactory/src/test/java/com/airbnb/epoxy/ModelFactoryViewProcessorTest.kt
new file mode 100644
index 0000000000..b7a52b4d5b
--- /dev/null
+++ b/epoxy-modelfactory/src/test/java/com/airbnb/epoxy/ModelFactoryViewProcessorTest.kt
@@ -0,0 +1,104 @@
+package com.airbnb.epoxy
+
+import com.airbnb.epoxy.ProcessorTestUtils.assertGeneration
+import com.google.testing.compile.JavaFileObjects
+import org.junit.Test
+import javax.tools.JavaFileObject
+
+class ModelFactoryViewProcessorTest {
+
+ @Test
+ fun baseModel() {
+ assertGeneration(
+ "BasicModelWithAttribute.java",
+ "BasicModelWithAttribute_.java"
+ )
+ }
+
+ @Test
+ fun baseModelWithFinalAttribute() {
+ assertGeneration(
+ "BasicModelWithFinalAttribute.java",
+ "BasicModelWithFinalAttribute_.java"
+ )
+ }
+
+ @Test
+ fun baseModelView() {
+ assertGeneration(
+ "BaseModelView.java",
+ "BaseModelViewModel_.java"
+ )
+ }
+
+ @Test
+ fun groupPropSingleSupportedAttribute() {
+ assertGeneration(
+ "GroupPropSingleSupportedAttributeModelView.java",
+ "GroupPropSingleSupportedAttributeModelViewModel_.java"
+ )
+ }
+
+ @Test
+ fun groupPropMultipleSupportedAttribute() {
+ // Shouldn't generate a from method, groups with multiple supported attribute types aren't
+ // supported
+ assertGeneration(
+ "GroupPropMultipleSupportedAttributeModelView.java",
+ "GroupPropMultipleSupportedAttributeModelViewModel_.java"
+ )
+ }
+
+ @Test
+ fun callbackPropModelView() {
+ assertGeneration(
+ "CallbackPropModelView.java",
+ "CallbackPropModelViewModel_.java"
+ )
+ }
+
+ @Test
+ fun textPropModelView() {
+ assertGeneration(
+ "TextPropModelView.java",
+ "TextPropModelViewModel_.java"
+ )
+ }
+
+ @Test
+ fun allTypesModelView() {
+ assertGeneration(
+ "AllTypesModelView.java",
+ "AllTypesModelViewModel_.java"
+ )
+ }
+
+ @Test
+ fun listSubtypeModelView() {
+ // Subtypes of List are not supported so no from method should be generated here
+ assertGeneration(
+ "ListSubtypeModelView.java",
+ "ListSubtypeModelViewModel_.java"
+ )
+ }
+
+ @Test
+ fun styleableModelView() {
+ // If the view is styleable then the generated "from" method supports setting a style
+
+ val configClass: JavaFileObject = JavaFileObjects
+ .forSourceLines("com.airbnb.epoxy.package-info",
+ "@ParisConfig(rClass = R.class)\n"
+ + "package com.airbnb.epoxy;\n"
+ + "\n"
+ + "import com.airbnb.paris.annotations.ParisConfig;\n"
+ + "import com.airbnb.epoxymodelfactory.R;\n")
+
+ assertGeneration(
+ "StyleableModelView.java",
+ "StyleableModelViewModel_.java",
+ useParis = true,
+ helperObjects = listOf(configClass)
+ )
+ }
+}
diff --git a/epoxy-modelfactory/src/test/java/com/airbnb/epoxy/ProcessorTestUtils.kt b/epoxy-modelfactory/src/test/java/com/airbnb/epoxy/ProcessorTestUtils.kt
new file mode 100644
index 0000000000..7e0d0f186b
--- /dev/null
+++ b/epoxy-modelfactory/src/test/java/com/airbnb/epoxy/ProcessorTestUtils.kt
@@ -0,0 +1,35 @@
+package com.airbnb.epoxy
+
+import com.airbnb.paris.processor.ParisProcessor
+import com.google.common.truth.Truth.assert_
+import com.google.testing.compile.JavaFileObjects
+import com.google.testing.compile.JavaSourcesSubjectFactory.javaSources
+import javax.annotation.processing.Processor
+import javax.tools.JavaFileObject
+
+internal object ProcessorTestUtils {
+
+ fun assertGeneration(
+ inputFile: String,
+ generatedFile: String,
+ useParis: Boolean = false,
+ helperObjects: List = emptyList()
+ ) {
+ val model = JavaFileObjects
+ .forResource(inputFile)
+
+ val generatedModel = JavaFileObjects.forResource(generatedFile)
+
+ val processors = mutableListOf().apply {
+ add(EpoxyProcessor())
+ if (useParis) add(ParisProcessor())
+ }
+
+ assert_().about(javaSources())
+ .that(helperObjects + listOf(model))
+ .processedWith(processors)
+ .compilesWithoutError()
+ .and()
+ .generatesSources(generatedModel)
+ }
+}
diff --git a/epoxy-modelfactory/src/test/java/com/airbnb/epoxy/TestModelPropertiesKotlinView.kt b/epoxy-modelfactory/src/test/java/com/airbnb/epoxy/TestModelPropertiesKotlinView.kt
new file mode 100644
index 0000000000..1a27f84702
--- /dev/null
+++ b/epoxy-modelfactory/src/test/java/com/airbnb/epoxy/TestModelPropertiesKotlinView.kt
@@ -0,0 +1,52 @@
+package com.airbnb.epoxy
+
+import android.content.Context
+import android.support.annotation.DrawableRes
+import android.view.View
+import android.widget.FrameLayout
+
+import com.airbnb.epoxy.ModelView.Size
+
+@ModelView(autoLayout = Size.MATCH_WIDTH_WRAP_HEIGHT)
+class TestModelPropertiesKotlinView(context: Context) : FrameLayout(context) {
+
+ @ModelProp
+ fun setBooleanValue(value: Boolean) {
+
+ }
+
+ @ModelProp
+ fun setDoubleValue(value: Double) {
+
+ }
+
+ @ModelProp
+ fun setDrawableRes(@DrawableRes value: Int) {
+
+ }
+
+ @ModelProp
+ fun setEpoxyModelList(value: List>) {
+
+ }
+
+ @ModelProp
+ fun setIntValue(value: Int) {
+
+ }
+
+ @CallbackProp
+ override fun setOnClickListener(value: View.OnClickListener?) {
+
+ }
+
+ @ModelProp
+ fun setStringValue(value: CharSequence) {
+
+ }
+
+ @ModelProp
+ fun setStringList(value: List) {
+
+ }
+}
diff --git a/epoxy-modelfactory/src/test/java/com/airbnb/epoxy/TestModelPropertiesView.java b/epoxy-modelfactory/src/test/java/com/airbnb/epoxy/TestModelPropertiesView.java
new file mode 100644
index 0000000000..bd7b051983
--- /dev/null
+++ b/epoxy-modelfactory/src/test/java/com/airbnb/epoxy/TestModelPropertiesView.java
@@ -0,0 +1,58 @@
+package com.airbnb.epoxy;
+
+import android.content.Context;
+import android.support.annotation.DrawableRes;
+import android.support.annotation.Nullable;
+import android.widget.FrameLayout;
+
+import com.airbnb.epoxy.ModelView.Size;
+
+import java.util.List;
+
+@ModelView(autoLayout = Size.MATCH_WIDTH_WRAP_HEIGHT)
+public class TestModelPropertiesView extends FrameLayout {
+
+ public TestModelPropertiesView(Context context) {
+ super(context);
+ }
+
+ @ModelProp
+ public void setBooleanValue(boolean value) {
+
+ }
+
+ @ModelProp
+ public void setDoubleValue(double value) {
+
+ }
+
+ @ModelProp
+ public void setDrawableRes(@DrawableRes int value) {
+
+ }
+
+ @ModelProp
+ public void setEpoxyModelList(List extends EpoxyModel>> value) {
+
+ }
+
+ @ModelProp
+ public void setIntValue(int value) {
+
+ }
+
+ @CallbackProp
+ public void setOnClickListener(@Nullable OnClickListener value) {
+
+ }
+
+ @ModelProp
+ public void setStringValue(CharSequence value) {
+
+ }
+
+ @ModelProp
+ public void setStringList(List value) {
+
+ }
+}
\ No newline at end of file
diff --git a/epoxy-modelfactory/src/test/java/com/airbnb/epoxy/package-info.java b/epoxy-modelfactory/src/test/java/com/airbnb/epoxy/package-info.java
new file mode 100644
index 0000000000..af3865f78f
--- /dev/null
+++ b/epoxy-modelfactory/src/test/java/com/airbnb/epoxy/package-info.java
@@ -0,0 +1,5 @@
+@ParisConfig(rClass = R.class)
+package com.airbnb.epoxy;
+
+import com.airbnb.epoxymodelfactory.R;
+import com.airbnb.paris.annotations.ParisConfig;
diff --git a/epoxy-modelfactory/src/test/resources/AllTypesModelView.java b/epoxy-modelfactory/src/test/resources/AllTypesModelView.java
new file mode 100644
index 0000000000..38d92fcc89
--- /dev/null
+++ b/epoxy-modelfactory/src/test/resources/AllTypesModelView.java
@@ -0,0 +1,63 @@
+package com.airbnb.epoxy;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.DrawableRes;
+import android.widget.FrameLayout;
+
+import com.airbnb.epoxy.ModelProp.Option;
+
+import java.util.List;
+
+@ModelView(defaultLayout = 1)
+public class AllTypesModelView extends FrameLayout {
+
+ public AllTypesModelView(Context context) {
+ super(context);
+ }
+
+ @ModelProp
+ public void setBooleanValue(boolean value) {
+
+ }
+
+ @ModelProp
+ public void setCharSequenceValue(CharSequence value) {
+
+ }
+
+ @ModelProp
+ public void setDoubleValue(double value) {
+
+ }
+
+ @ModelProp
+ public void setDrawableRes(@DrawableRes int value) {
+
+ }
+
+ @ModelProp
+ public void setEpoxyModelList(List extends EpoxyModel>> value) {
+
+ }
+
+ @ModelProp
+ public void setIntValue(int value) {
+
+ }
+
+ @ModelProp({ Option.DoNotHash })
+ public void setOnClickListener(OnClickListener value) {
+
+ }
+
+ @ModelProp
+ public void setStringValue(String value) {
+
+ }
+
+ @ModelProp
+ public void setStringList(List value) {
+
+ }
+}
diff --git a/epoxy-modelfactory/src/test/resources/AllTypesModelViewModel_.java b/epoxy-modelfactory/src/test/resources/AllTypesModelViewModel_.java
new file mode 100644
index 0000000000..1310ae5f32
--- /dev/null
+++ b/epoxy-modelfactory/src/test/resources/AllTypesModelViewModel_.java
@@ -0,0 +1,587 @@
+package com.airbnb.epoxy;
+
+import android.support.annotation.DrawableRes;
+import android.support.annotation.LayoutRes;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.view.View;
+import java.lang.CharSequence;
+import java.lang.IllegalArgumentException;
+import java.lang.IllegalStateException;
+import java.lang.Number;
+import java.lang.Object;
+import java.lang.Override;
+import java.lang.String;
+import java.util.BitSet;
+import java.util.List;
+
+/**
+ * Generated file. Do not modify! */
+public class AllTypesModelViewModel_ extends EpoxyModel implements GeneratedModel, AllTypesModelViewModelBuilder {
+ private final BitSet assignedAttributes_epoxyGeneratedModel = new BitSet(9);
+
+ private OnModelBoundListener onModelBoundListener_epoxyGeneratedModel;
+
+ private OnModelUnboundListener onModelUnboundListener_epoxyGeneratedModel;
+
+ /**
+ * Bitset index: 0 */
+ private boolean booleanValue_Boolean = false;
+
+ /**
+ * Bitset index: 1 */
+ @NonNull
+ private CharSequence charSequenceValue_CharSequence;
+
+ /**
+ * Bitset index: 2 */
+ private double doubleValue_Double = 0.0d;
+
+ /**
+ * Bitset index: 3 */
+ @DrawableRes
+ private int drawableRes_Int = 0;
+
+ /**
+ * Bitset index: 4 */
+ @NonNull
+ private List extends EpoxyModel>> epoxyModelList_List;
+
+ /**
+ * Bitset index: 5 */
+ private int intValue_Int = 0;
+
+ /**
+ * Bitset index: 6 */
+ @NonNull
+ private View.OnClickListener onClickListener_OnClickListener;
+
+ /**
+ * Bitset index: 7 */
+ @NonNull
+ private String stringValue_String;
+
+ /**
+ * Bitset index: 8 */
+ @NonNull
+ private List stringList_List;
+
+ @Override
+ public void addTo(EpoxyController controller) {
+ super.addTo(controller);
+ addWithDebugValidation(controller);
+ if (!assignedAttributes_epoxyGeneratedModel.get(7)) {
+ throw new IllegalStateException("A value is required for setStringValue");
+ }
+ if (!assignedAttributes_epoxyGeneratedModel.get(6)) {
+ throw new IllegalStateException("A value is required for setOnClickListener");
+ }
+ if (!assignedAttributes_epoxyGeneratedModel.get(4)) {
+ throw new IllegalStateException("A value is required for setEpoxyModelList");
+ }
+ if (!assignedAttributes_epoxyGeneratedModel.get(1)) {
+ throw new IllegalStateException("A value is required for setCharSequenceValue");
+ }
+ if (!assignedAttributes_epoxyGeneratedModel.get(8)) {
+ throw new IllegalStateException("A value is required for setStringList");
+ }
+ }
+
+ @Override
+ public void handlePreBind(final EpoxyViewHolder holder, final AllTypesModelView object,
+ final int position) {
+ validateStateHasNotChangedSinceAdded("The model was changed between being added to the controller and being bound.", position);
+ }
+
+ @Override
+ public void bind(final AllTypesModelView object) {
+ super.bind(object);
+ object.setStringValue(stringValue_String);
+ object.setOnClickListener(onClickListener_OnClickListener);
+ object.setIntValue(intValue_Int);
+ object.setBooleanValue(booleanValue_Boolean);
+ object.setDrawableRes(drawableRes_Int);
+ object.setDoubleValue(doubleValue_Double);
+ object.setEpoxyModelList(epoxyModelList_List);
+ object.setCharSequenceValue(charSequenceValue_CharSequence);
+ object.setStringList(stringList_List);
+ }
+
+ @Override
+ public void bind(final AllTypesModelView object, EpoxyModel previousModel) {
+ if (!(previousModel instanceof AllTypesModelViewModel_)) {
+ bind(object);
+ return;
+ }
+ AllTypesModelViewModel_ that = (AllTypesModelViewModel_) previousModel;
+ super.bind(object);
+
+ if ((stringValue_String != null ? !stringValue_String.equals(that.stringValue_String) : that.stringValue_String != null)) {
+ object.setStringValue(stringValue_String);
+ }
+
+ if (((onClickListener_OnClickListener == null) != (that.onClickListener_OnClickListener == null))) {
+ object.setOnClickListener(onClickListener_OnClickListener);
+ }
+
+ if ((intValue_Int != that.intValue_Int)) {
+ object.setIntValue(intValue_Int);
+ }
+
+ if ((booleanValue_Boolean != that.booleanValue_Boolean)) {
+ object.setBooleanValue(booleanValue_Boolean);
+ }
+
+ if ((drawableRes_Int != that.drawableRes_Int)) {
+ object.setDrawableRes(drawableRes_Int);
+ }
+
+ if ((Double.compare(that.doubleValue_Double, doubleValue_Double) != 0)) {
+ object.setDoubleValue(doubleValue_Double);
+ }
+
+ if ((epoxyModelList_List != null ? !epoxyModelList_List.equals(that.epoxyModelList_List) : that.epoxyModelList_List != null)) {
+ object.setEpoxyModelList(epoxyModelList_List);
+ }
+
+ if ((charSequenceValue_CharSequence != null ? !charSequenceValue_CharSequence.equals(that.charSequenceValue_CharSequence) : that.charSequenceValue_CharSequence != null)) {
+ object.setCharSequenceValue(charSequenceValue_CharSequence);
+ }
+
+ if ((stringList_List != null ? !stringList_List.equals(that.stringList_List) : that.stringList_List != null)) {
+ object.setStringList(stringList_List);
+ }
+ }
+
+ @Override
+ public void handlePostBind(final AllTypesModelView object, int position) {
+ if (onModelBoundListener_epoxyGeneratedModel != null) {
+ onModelBoundListener_epoxyGeneratedModel.onModelBound(this, object, position);
+ }
+ validateStateHasNotChangedSinceAdded("The model was changed during the bind call.", position);
+ }
+
+ /**
+ * Register a listener that will be called when this model is bound to a view.
+ *
+ * The listener will contribute to this model's hashCode state per the {@link
+ * com.airbnb.epoxy.EpoxyAttribute.Option#DoNotHash} rules.
+ *
+ * You may clear the listener by setting a null value, or by calling {@link #reset()} */
+ public AllTypesModelViewModel_ onBind(OnModelBoundListener listener) {
+ onMutation();
+ this.onModelBoundListener_epoxyGeneratedModel = listener;
+ return this;
+ }
+
+ @Override
+ public void unbind(AllTypesModelView object) {
+ super.unbind(object);
+ if (onModelUnboundListener_epoxyGeneratedModel != null) {
+ onModelUnboundListener_epoxyGeneratedModel.onModelUnbound(this, object);
+ }
+ }
+
+ /**
+ * Register a listener that will be called when this model is unbound from a view.
+ *
+ * The listener will contribute to this model's hashCode state per the {@link
+ * com.airbnb.epoxy.EpoxyAttribute.Option#DoNotHash} rules.
+ *
+ * You may clear the listener by setting a null value, or by calling {@link #reset()} */
+ public AllTypesModelViewModel_ onUnbind(OnModelUnboundListener listener) {
+ onMutation();
+ this.onModelUnboundListener_epoxyGeneratedModel = listener;
+ return this;
+ }
+
+ /**
+ * Optional: Default value is false
+ *
+ * @see AllTypesModelView#setBooleanValue(boolean)
+ */
+ public AllTypesModelViewModel_ booleanValue(boolean booleanValue) {
+ assignedAttributes_epoxyGeneratedModel.set(0);
+ onMutation();
+ this.booleanValue_Boolean = booleanValue;
+ return this;
+ }
+
+ public boolean booleanValue() {
+ return booleanValue_Boolean;
+ }
+
+ /**
+ * Required.
+ *
+ * @see AllTypesModelView#setCharSequenceValue(CharSequence)
+ */
+ public AllTypesModelViewModel_ charSequenceValue(@NonNull CharSequence charSequenceValue) {
+ if (charSequenceValue == null) {
+ throw new IllegalArgumentException("charSequenceValue cannot be null");
+ }
+ assignedAttributes_epoxyGeneratedModel.set(1);
+ onMutation();
+ this.charSequenceValue_CharSequence = charSequenceValue;
+ return this;
+ }
+
+ @NonNull
+ public CharSequence charSequenceValue() {
+ return charSequenceValue_CharSequence;
+ }
+
+ /**
+ * Optional: Default value is 0.0d
+ *
+ * @see AllTypesModelView#setDoubleValue(double)
+ */
+ public AllTypesModelViewModel_ doubleValue(double doubleValue) {
+ assignedAttributes_epoxyGeneratedModel.set(2);
+ onMutation();
+ this.doubleValue_Double = doubleValue;
+ return this;
+ }
+
+ public double doubleValue() {
+ return doubleValue_Double;
+ }
+
+ /**
+ * Optional: Default value is 0
+ *
+ * @see AllTypesModelView#setDrawableRes(int)
+ */
+ public AllTypesModelViewModel_ drawableRes(@DrawableRes int drawableRes) {
+ assignedAttributes_epoxyGeneratedModel.set(3);
+ onMutation();
+ this.drawableRes_Int = drawableRes;
+ return this;
+ }
+
+ @DrawableRes
+ public int drawableRes() {
+ return drawableRes_Int;
+ }
+
+ /**
+ * Required.
+ *
+ * @see AllTypesModelView#setEpoxyModelList(List extends EpoxyModel>>)
+ */
+ public AllTypesModelViewModel_ epoxyModelList(@NonNull List extends EpoxyModel>> epoxyModelList) {
+ if (epoxyModelList == null) {
+ throw new IllegalArgumentException("epoxyModelList cannot be null");
+ }
+ assignedAttributes_epoxyGeneratedModel.set(4);
+ onMutation();
+ this.epoxyModelList_List = epoxyModelList;
+ return this;
+ }
+
+ @NonNull
+ public List extends EpoxyModel>> epoxyModelList() {
+ return epoxyModelList_List;
+ }
+
+ /**
+ * Optional: Default value is 0
+ *
+ * @see AllTypesModelView#setIntValue(int)
+ */
+ public AllTypesModelViewModel_ intValue(int intValue) {
+ assignedAttributes_epoxyGeneratedModel.set(5);
+ onMutation();
+ this.intValue_Int = intValue;
+ return this;
+ }
+
+ public int intValue() {
+ return intValue_Int;
+ }
+
+ /**
+ * Set a click listener that will provide the parent view, model, and adapter position of the clicked view. This will clear the normal View.OnClickListener if one has been set */
+ @NonNull
+ public AllTypesModelViewModel_ onClickListener(final OnModelClickListener onClickListener) {
+ assignedAttributes_epoxyGeneratedModel.set(6);
+ onMutation();
+ if (onClickListener == null) {
+ this.onClickListener_OnClickListener = null;
+ }
+ else {
+ this.onClickListener_OnClickListener = new WrappedEpoxyModelClickListener(onClickListener);
+ }
+ return this;
+ }
+
+ /**
+ * Required.
+ *
+ * @see AllTypesModelView#setOnClickListener(View.OnClickListener)
+ */
+ public AllTypesModelViewModel_ onClickListener(@NonNull View.OnClickListener onClickListener) {
+ if (onClickListener == null) {
+ throw new IllegalArgumentException("onClickListener cannot be null");
+ }
+ assignedAttributes_epoxyGeneratedModel.set(6);
+ onMutation();
+ this.onClickListener_OnClickListener = onClickListener;
+ return this;
+ }
+
+ @NonNull
+ public View.OnClickListener onClickListener() {
+ return onClickListener_OnClickListener;
+ }
+
+ /**
+ * Required.
+ *
+ * @see AllTypesModelView#setStringValue(String)
+ */
+ public AllTypesModelViewModel_ stringValue(@NonNull String stringValue) {
+ if (stringValue == null) {
+ throw new IllegalArgumentException("stringValue cannot be null");
+ }
+ assignedAttributes_epoxyGeneratedModel.set(7);
+ onMutation();
+ this.stringValue_String = stringValue;
+ return this;
+ }
+
+ @NonNull
+ public String stringValue() {
+ return stringValue_String;
+ }
+
+ /**
+ * Required.
+ *
+ * @see AllTypesModelView#setStringList(List)
+ */
+ public AllTypesModelViewModel_ stringList(@NonNull List stringList) {
+ if (stringList == null) {
+ throw new IllegalArgumentException("stringList cannot be null");
+ }
+ assignedAttributes_epoxyGeneratedModel.set(8);
+ onMutation();
+ this.stringList_List = stringList;
+ return this;
+ }
+
+ @NonNull
+ public List stringList() {
+ return stringList_List;
+ }
+
+ @Override
+ public AllTypesModelViewModel_ id(long id) {
+ super.id(id);
+ return this;
+ }
+
+ @Override
+ public AllTypesModelViewModel_ id(@NonNull Number... arg0) {
+ super.id(arg0);
+ return this;
+ }
+
+ @Override
+ public AllTypesModelViewModel_ id(long id1, long id2) {
+ super.id(id1, id2);
+ return this;
+ }
+
+ @Override
+ public AllTypesModelViewModel_ id(@NonNull CharSequence arg0) {
+ super.id(arg0);
+ return this;
+ }
+
+ @Override
+ public AllTypesModelViewModel_ id(@NonNull CharSequence arg0, @NonNull CharSequence... arg1) {
+ super.id(arg0, arg1);
+ return this;
+ }
+
+ @Override
+ public AllTypesModelViewModel_ id(@NonNull CharSequence arg0, long arg1) {
+ super.id(arg0, arg1);
+ return this;
+ }
+
+ @Override
+ public AllTypesModelViewModel_ layout(@LayoutRes int arg0) {
+ super.layout(arg0);
+ return this;
+ }
+
+ @Override
+ public AllTypesModelViewModel_ spanSizeOverride(@Nullable EpoxyModel.SpanSizeOverrideCallback arg0) {
+ super.spanSizeOverride(arg0);
+ return this;
+ }
+
+ @Override
+ public AllTypesModelViewModel_ show() {
+ super.show();
+ return this;
+ }
+
+ @Override
+ public AllTypesModelViewModel_ show(boolean show) {
+ super.show(show);
+ return this;
+ }
+
+ @Override
+ public AllTypesModelViewModel_ hide() {
+ super.hide();
+ return this;
+ }
+
+ @Override
+ @LayoutRes
+ protected int getDefaultLayout() {
+ return 1;
+ }
+
+ @Override
+ public AllTypesModelViewModel_ reset() {
+ onModelBoundListener_epoxyGeneratedModel = null;
+ onModelUnboundListener_epoxyGeneratedModel = null;
+ assignedAttributes_epoxyGeneratedModel.clear();
+ this.booleanValue_Boolean = false;
+ this.charSequenceValue_CharSequence = null;
+ this.doubleValue_Double = 0.0d;
+ this.drawableRes_Int = 0;
+ this.epoxyModelList_List = null;
+ this.intValue_Int = 0;
+ this.onClickListener_OnClickListener = null;
+ this.stringValue_String = null;
+ this.stringList_List = null;
+ super.reset();
+ return this;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (!(o instanceof AllTypesModelViewModel_)) {
+ return false;
+ }
+ if (!super.equals(o)) {
+ return false;
+ }
+ AllTypesModelViewModel_ that = (AllTypesModelViewModel_) o;
+ if (((onModelBoundListener_epoxyGeneratedModel == null) != (that.onModelBoundListener_epoxyGeneratedModel == null))) {
+ return false;
+ }
+ if (((onModelUnboundListener_epoxyGeneratedModel == null) != (that.onModelUnboundListener_epoxyGeneratedModel == null))) {
+ return false;
+ }
+ if ((booleanValue_Boolean != that.booleanValue_Boolean)) {
+ return false;
+ }
+ if ((charSequenceValue_CharSequence != null ? !charSequenceValue_CharSequence.equals(that.charSequenceValue_CharSequence) : that.charSequenceValue_CharSequence != null)) {
+ return false;
+ }
+ if ((Double.compare(that.doubleValue_Double, doubleValue_Double) != 0)) {
+ return false;
+ }
+ if ((drawableRes_Int != that.drawableRes_Int)) {
+ return false;
+ }
+ if ((epoxyModelList_List != null ? !epoxyModelList_List.equals(that.epoxyModelList_List) : that.epoxyModelList_List != null)) {
+ return false;
+ }
+ if ((intValue_Int != that.intValue_Int)) {
+ return false;
+ }
+ if (((onClickListener_OnClickListener == null) != (that.onClickListener_OnClickListener == null))) {
+ return false;
+ }
+ if ((stringValue_String != null ? !stringValue_String.equals(that.stringValue_String) : that.stringValue_String != null)) {
+ return false;
+ }
+ if ((stringList_List != null ? !stringList_List.equals(that.stringList_List) : that.stringList_List != null)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = super.hashCode();
+ result = 31 * result + (onModelBoundListener_epoxyGeneratedModel != null ? 1 : 0);
+ result = 31 * result + (onModelUnboundListener_epoxyGeneratedModel != null ? 1 : 0);
+ long temp;
+ result = 31 * result + (booleanValue_Boolean ? 1 : 0);
+ result = 31 * result + (charSequenceValue_CharSequence != null ? charSequenceValue_CharSequence.hashCode() : 0);
+ temp = Double.doubleToLongBits(doubleValue_Double);
+ result = 31 * result + (int) (temp ^ (temp >>> 32));
+ result = 31 * result + drawableRes_Int;
+ result = 31 * result + (epoxyModelList_List != null ? epoxyModelList_List.hashCode() : 0);
+ result = 31 * result + intValue_Int;
+ result = 31 * result + (onClickListener_OnClickListener != null ? 1 : 0);
+ result = 31 * result + (stringValue_String != null ? stringValue_String.hashCode() : 0);
+ result = 31 * result + (stringList_List != null ? stringList_List.hashCode() : 0);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "AllTypesModelViewModel_{" +
+ "booleanValue_Boolean=" + booleanValue_Boolean +
+ ", charSequenceValue_CharSequence=" + charSequenceValue_CharSequence +
+ ", doubleValue_Double=" + doubleValue_Double +
+ ", drawableRes_Int=" + drawableRes_Int +
+ ", epoxyModelList_List=" + epoxyModelList_List +
+ ", intValue_Int=" + intValue_Int +
+ ", onClickListener_OnClickListener=" + onClickListener_OnClickListener +
+ ", stringValue_String=" + stringValue_String +
+ ", stringList_List=" + stringList_List +
+ "}" + super.toString();
+ }
+
+ public static AllTypesModelViewModel_ from(ModelProperties properties) {
+ AllTypesModelViewModel_ model = new AllTypesModelViewModel_();
+ model.id(properties.getId());
+ if (properties.has("stringValue")) {
+ model.stringValue(properties.getString("stringValue"));
+ }
+ if (properties.has("onClickListener")) {
+ model.onClickListener(properties.getOnClickListener("onClickListener"));
+ }
+ if (properties.has("intValue")) {
+ model.intValue(properties.getInt("intValue"));
+ }
+ if (properties.has("booleanValue")) {
+ model.booleanValue(properties.getBoolean("booleanValue"));
+ }
+ if (properties.has("drawableRes")) {
+ model.drawableRes(properties.getDrawableRes("drawableRes"));
+ }
+ if (properties.has("doubleValue")) {
+ model.doubleValue(properties.getDouble("doubleValue"));
+ }
+ if (properties.has("epoxyModelList")) {
+ model.epoxyModelList(properties.getEpoxyModelList("epoxyModelList"));
+ }
+ if (properties.has("charSequenceValue")) {
+ model.charSequenceValue(properties.getString("charSequenceValue"));
+ }
+ if (properties.has("stringList")) {
+ model.stringList(properties.getStringList("stringList"));
+ }
+ return model;
+ }
+
+ @Override
+ public int getSpanSize(int totalSpanCount, int position, int itemCount) {
+ return totalSpanCount;
+ }
+}
\ No newline at end of file
diff --git a/epoxy-modelfactory/src/test/resources/BaseModelView.java b/epoxy-modelfactory/src/test/resources/BaseModelView.java
new file mode 100644
index 0000000000..995c16864a
--- /dev/null
+++ b/epoxy-modelfactory/src/test/resources/BaseModelView.java
@@ -0,0 +1,17 @@
+package com.airbnb.epoxy;
+
+import android.content.Context;
+import android.widget.FrameLayout;
+
+@ModelView(defaultLayout = 1)
+public class BaseModelView extends FrameLayout {
+
+ public BaseModelView(Context context) {
+ super(context);
+ }
+
+ @ModelProp
+ public void setTitle(String title) {
+
+ }
+}
\ No newline at end of file
diff --git a/epoxy-modelfactory/src/test/resources/BaseModelViewModel_.java b/epoxy-modelfactory/src/test/resources/BaseModelViewModel_.java
new file mode 100644
index 0000000000..da523d5dbe
--- /dev/null
+++ b/epoxy-modelfactory/src/test/resources/BaseModelViewModel_.java
@@ -0,0 +1,261 @@
+package com.airbnb.epoxy;
+
+import android.support.annotation.LayoutRes;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import java.lang.CharSequence;
+import java.lang.IllegalArgumentException;
+import java.lang.IllegalStateException;
+import java.lang.Number;
+import java.lang.Object;
+import java.lang.Override;
+import java.lang.String;
+import java.util.BitSet;
+
+/**
+ * Generated file. Do not modify! */
+public class BaseModelViewModel_ extends EpoxyModel implements GeneratedModel, BaseModelViewModelBuilder {
+ private final BitSet assignedAttributes_epoxyGeneratedModel = new BitSet(1);
+
+ private OnModelBoundListener onModelBoundListener_epoxyGeneratedModel;
+
+ private OnModelUnboundListener onModelUnboundListener_epoxyGeneratedModel;
+
+ /**
+ * Bitset index: 0 */
+ @NonNull
+ private String title_String;
+
+ @Override
+ public void addTo(EpoxyController controller) {
+ super.addTo(controller);
+ addWithDebugValidation(controller);
+ if (!assignedAttributes_epoxyGeneratedModel.get(0)) {
+ throw new IllegalStateException("A value is required for setTitle");
+ }
+ }
+
+ @Override
+ public void handlePreBind(final EpoxyViewHolder holder, final BaseModelView object,
+ final int position) {
+ validateStateHasNotChangedSinceAdded("The model was changed between being added to the controller and being bound.", position);
+ }
+
+ @Override
+ public void bind(final BaseModelView object) {
+ super.bind(object);
+ object.setTitle(title_String);
+ }
+
+ @Override
+ public void bind(final BaseModelView object, EpoxyModel previousModel) {
+ if (!(previousModel instanceof BaseModelViewModel_)) {
+ bind(object);
+ return;
+ }
+ BaseModelViewModel_ that = (BaseModelViewModel_) previousModel;
+ super.bind(object);
+
+ if ((title_String != null ? !title_String.equals(that.title_String) : that.title_String != null)) {
+ object.setTitle(title_String);
+ }
+ }
+
+ @Override
+ public void handlePostBind(final BaseModelView object, int position) {
+ if (onModelBoundListener_epoxyGeneratedModel != null) {
+ onModelBoundListener_epoxyGeneratedModel.onModelBound(this, object, position);
+ }
+ validateStateHasNotChangedSinceAdded("The model was changed during the bind call.", position);
+ }
+
+ /**
+ * Register a listener that will be called when this model is bound to a view.
+ *
+ * The listener will contribute to this model's hashCode state per the {@link
+ * com.airbnb.epoxy.EpoxyAttribute.Option#DoNotHash} rules.
+ *
+ * You may clear the listener by setting a null value, or by calling {@link #reset()} */
+ public BaseModelViewModel_ onBind(OnModelBoundListener listener) {
+ onMutation();
+ this.onModelBoundListener_epoxyGeneratedModel = listener;
+ return this;
+ }
+
+ @Override
+ public void unbind(BaseModelView object) {
+ super.unbind(object);
+ if (onModelUnboundListener_epoxyGeneratedModel != null) {
+ onModelUnboundListener_epoxyGeneratedModel.onModelUnbound(this, object);
+ }
+ }
+
+ /**
+ * Register a listener that will be called when this model is unbound from a view.
+ *
+ * The listener will contribute to this model's hashCode state per the {@link
+ * com.airbnb.epoxy.EpoxyAttribute.Option#DoNotHash} rules.
+ *
+ * You may clear the listener by setting a null value, or by calling {@link #reset()} */
+ public BaseModelViewModel_ onUnbind(OnModelUnboundListener listener) {
+ onMutation();
+ this.onModelUnboundListener_epoxyGeneratedModel = listener;
+ return this;
+ }
+
+ /**
+ * Required.
+ *
+ * @see BaseModelView#setTitle(String)
+ */
+ public BaseModelViewModel_ title(@NonNull String title) {
+ if (title == null) {
+ throw new IllegalArgumentException("title cannot be null");
+ }
+ assignedAttributes_epoxyGeneratedModel.set(0);
+ onMutation();
+ this.title_String = title;
+ return this;
+ }
+
+ @NonNull
+ public String title() {
+ return title_String;
+ }
+
+ @Override
+ public BaseModelViewModel_ id(long id) {
+ super.id(id);
+ return this;
+ }
+
+ @Override
+ public BaseModelViewModel_ id(@NonNull Number... arg0) {
+ super.id(arg0);
+ return this;
+ }
+
+ @Override
+ public BaseModelViewModel_ id(long id1, long id2) {
+ super.id(id1, id2);
+ return this;
+ }
+
+ @Override
+ public BaseModelViewModel_ id(@NonNull CharSequence arg0) {
+ super.id(arg0);
+ return this;
+ }
+
+ @Override
+ public BaseModelViewModel_ id(@NonNull CharSequence arg0, @NonNull CharSequence... arg1) {
+ super.id(arg0, arg1);
+ return this;
+ }
+
+ @Override
+ public BaseModelViewModel_ id(@NonNull CharSequence arg0, long arg1) {
+ super.id(arg0, arg1);
+ return this;
+ }
+
+ @Override
+ public BaseModelViewModel_ layout(@LayoutRes int arg0) {
+ super.layout(arg0);
+ return this;
+ }
+
+ @Override
+ public BaseModelViewModel_ spanSizeOverride(@Nullable EpoxyModel.SpanSizeOverrideCallback arg0) {
+ super.spanSizeOverride(arg0);
+ return this;
+ }
+
+ @Override
+ public BaseModelViewModel_ show() {
+ super.show();
+ return this;
+ }
+
+ @Override
+ public BaseModelViewModel_ show(boolean show) {
+ super.show(show);
+ return this;
+ }
+
+ @Override
+ public BaseModelViewModel_ hide() {
+ super.hide();
+ return this;
+ }
+
+ @Override
+ @LayoutRes
+ protected int getDefaultLayout() {
+ return 1;
+ }
+
+ @Override
+ public BaseModelViewModel_ reset() {
+ onModelBoundListener_epoxyGeneratedModel = null;
+ onModelUnboundListener_epoxyGeneratedModel = null;
+ assignedAttributes_epoxyGeneratedModel.clear();
+ this.title_String = null;
+ super.reset();
+ return this;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (!(o instanceof BaseModelViewModel_)) {
+ return false;
+ }
+ if (!super.equals(o)) {
+ return false;
+ }
+ BaseModelViewModel_ that = (BaseModelViewModel_) o;
+ if (((onModelBoundListener_epoxyGeneratedModel == null) != (that.onModelBoundListener_epoxyGeneratedModel == null))) {
+ return false;
+ }
+ if (((onModelUnboundListener_epoxyGeneratedModel == null) != (that.onModelUnboundListener_epoxyGeneratedModel == null))) {
+ return false;
+ }
+ if ((title_String != null ? !title_String.equals(that.title_String) : that.title_String != null)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = super.hashCode();
+ result = 31 * result + (onModelBoundListener_epoxyGeneratedModel != null ? 1 : 0);
+ result = 31 * result + (onModelUnboundListener_epoxyGeneratedModel != null ? 1 : 0);
+ result = 31 * result + (title_String != null ? title_String.hashCode() : 0);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "BaseModelViewModel_{" +
+ "title_String=" + title_String +
+ "}" + super.toString();
+ }
+
+ public static BaseModelViewModel_ from(ModelProperties properties) {
+ BaseModelViewModel_ model = new BaseModelViewModel_();
+ model.id(properties.getId());
+ if (properties.has("title")) {
+ model.title(properties.getString("title"));
+ }
+ return model;
+ }
+
+ @Override
+ public int getSpanSize(int totalSpanCount, int position, int itemCount) {
+ return totalSpanCount;
+ }
+}
\ No newline at end of file
diff --git a/epoxy-modelfactory/src/test/resources/BasicModelWithAttribute.java b/epoxy-modelfactory/src/test/resources/BasicModelWithAttribute.java
new file mode 100755
index 0000000000..4b753efc29
--- /dev/null
+++ b/epoxy-modelfactory/src/test/resources/BasicModelWithAttribute.java
@@ -0,0 +1,10 @@
+package com.airbnb.epoxy;
+
+public class BasicModelWithAttribute extends EpoxyModel