Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Databinding naming pattern support #319

Merged
merged 2 commits into from
Oct 16, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
* <p>
* The layouts must not specify a custom databinding class name or package via the
* class="com.example.CustomClassName" override in the layout xml.
* <p>
* Alternatively you can use {@link EpoxyDataBindingPattern} to avoid explicitly declaring each
* layout.
*/
@Target(ElementType.PACKAGE)
@Retention(RetentionPolicy.CLASS)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.airbnb.epoxy;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Used to specify a naming pattern for the databinding layouts that you want models generated for.
* Use this instead of {@link EpoxyDataBindingLayouts} to avoid having to explicitly list every
* databinding layout.
* <p>
* The layouts must not specify a custom databinding class name or package via the
* class="com.example.CustomClassName" override in the layout xml.
*/
@Target(ElementType.PACKAGE)
@Retention(RetentionPolicy.CLASS)
public @interface EpoxyDataBindingPattern {
/**
* The R class used in this module (eg "com.example.app.R.class"). This is needed so Epoxy can
* look up layout files.
*/
Class<?> rClass();
/**
* A string prefix that your databinding layouts start with. Epoxy will generate a model for each
* databinding layout whose name starts with this.
* <p>
* For example, if you set this prefix to "view_holder" and you have a "view_holder_header.xml"
* databinding layout, Epoxy will generate a HeaderBindingModel_ class for that layout.
*/
String layoutPrefix();
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
@EpoxyDataBindingPattern(rClass = R.class, layoutPrefix = "view_holder")
@EpoxyDataBindingLayouts({R.layout.model_with_data_binding})
package com.airbnb.epoxy.integrationtest;

import com.airbnb.epoxy.EpoxyDataBindingLayouts;
import com.airbnb.epoxy.EpoxyDataBindingPattern;
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

<data>

<variable
name="stringValue"
type="String" />

<variable
name="clickListener"
type="android.view.View.OnClickListener" />
</data>

<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="40dp"
android:text="@{stringValue}" />

</layout>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?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="match_parent"
android:orientation="vertical">

<!-- This layout does nothing, but it tests that the databinding processor skips none databinding layouts that match the naming pattern. -->

</LinearLayout>
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@

import com.airbnb.epoxy.DataBindingEpoxyModel.DataBindingHolder;
import com.airbnb.epoxy.integrationtest.BuildConfig;
import com.airbnb.epoxy.integrationtest.DatabindingTestBindingModel_;
import com.airbnb.epoxy.integrationtest.ModelWithDataBindingBindingModel_;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;


import java.util.Collections;
import java.util.List;

Expand Down Expand Up @@ -144,4 +144,10 @@ public void typesWithHashCodeAreDiffed() {
verify(observerMock).onItemRangeChanged(eq(0), eq(1), any());
verifyNoMoreInteractions(observerMock);
}

@Test
public void generatesBindingModelFromNamingPattern() {
// Make sure that the model was generated from the annotation naming pattern
new DatabindingTestBindingModel_();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ private ClassNames() {
static final ClassName EPOXY_STRING_ATTRIBUTE_DATA = get(PKG_EPOXY, "StringAttributeData");
static final ClassName EPOXY_CONTROLLER = get(PKG_EPOXY, "EpoxyController");
static final ClassName EPOXY_STYLE_BUILDER_CALLBACK = get(PKG_EPOXY, "StyleBuilderCallback");
static final ClassName EPOXY_CONTROLLER_HELPER = get(PKG_EPOXY, "ControllerHelper");

static final ClassName PARIS_STYLE_UTILS = get(PKG_PARIS, "StyleApplierUtils", "Companion");
static final ClassName PARIS_STYLE = get(PKG_PARIS, "Style");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,17 +109,16 @@ List<Exception> processConfigurations(RoundEnvironment roundEnv) {
continue;
}

TypeMirror rClassType =
getClassParamFromAnnotation(element, PackageModelViewConfig.class, "rClass");
if (rClassType == null) {
ClassName rClassName =
getClassParamFromAnnotation(element, PackageModelViewConfig.class, "rClass", typeUtils);

if (rClassName == null) {
errors.add(buildEpoxyException(
"Unable to get R class details from annotation %s (package: %s)",
PackageModelViewConfig.class.getSimpleName(), packageName));
continue;
}

ClassName rClassName =
ClassName.get((TypeElement) typeUtils.asElement(rClassType));

String rLayoutClassString = rClassName.reflectionName();
if (!rLayoutClassString.endsWith(".R")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.airbnb.epoxy;

import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
Expand Down Expand Up @@ -39,7 +38,6 @@
import static com.airbnb.epoxy.Utils.validateFieldAccessibleViaGeneratedCode;

class ControllerProcessor {
private static final String CONTROLLER_HELPER_INTERFACE = "com.airbnb.epoxy.ControllerHelper";
private Filer filer;
private Elements elementUtils;
private Types typeUtils;
Expand Down Expand Up @@ -237,9 +235,9 @@ private void generateJava(Map<TypeElement, ControllerClassInfo> controllerClassM

private void generateHelperClassForController(ControllerClassInfo controllerInfo)
throws IOException {
ClassName superclass = ClassName.get(elementUtils.getTypeElement(CONTROLLER_HELPER_INTERFACE));
ParameterizedTypeName parameterizeSuperClass =
ParameterizedTypeName.get(superclass, controllerInfo.getControllerClassType());
ParameterizedTypeName
.get(ClassNames.EPOXY_CONTROLLER_HELPER, controllerInfo.getControllerClassType());

TypeSpec.Builder builder = TypeSpec.classBuilder(controllerInfo.getGeneratedClassName())
.addJavadoc("Generated file. Do not modify!")
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package com.airbnb.epoxy

import com.airbnb.epoxy.ClassNames.*
import com.airbnb.epoxy.Utils.*
import com.squareup.javapoet.*
import javax.lang.model.element.*
import javax.lang.model.util.*

internal class DataBindingModelInfo(
private val typeUtils: Types,
private val elementUtils: Elements,
val layoutResource: ResourceValue,
private val moduleName: String,
private val layoutPrefix: String = ""
) : GeneratedModelInfo() {
private val dataBindingClassName: ClassName

val dataBindingClassElement: Element?
get() = getElementByName(dataBindingClassName, elementUtils, typeUtils)

init {

dataBindingClassName = getDataBindingClassNameForResource(layoutResource, moduleName)

superClassElement = Utils.getElementByName(EPOXY_DATA_BINDING_MODEL,
elementUtils, typeUtils) as TypeElement
superClassName = EPOXY_DATA_BINDING_MODEL
generatedClassName = buildGeneratedModelName()
parametrizedClassName = generatedClassName
boundObjectTypeName = EPOXY_DATA_BINDING_HOLDER
shouldGenerateModel = true

collectMethodsReturningClassType(superClassElement, typeUtils)
}

/**
* Look up the DataBinding class generated for this model's layout file and parse the attributes
* for it.
*/
fun parseDataBindingClass() {
// This databinding class won't exist until the second round of annotation processing since
// it is generated in the first round.

val hashCodeValidator = HashCodeValidator(typeUtils, elementUtils)
dataBindingClassElement!!.enclosedElements
.filter { Utils.isSetterMethod(it) }
.forEach {
addAttribute(
DataBindingAttributeInfo(this, it as ExecutableElement,
hashCodeValidator))
}
}

private fun getDataBindingClassNameForResource(
layoutResource: ResourceValue,
moduleName: String
): ClassName {
val modelName = layoutResource.resourceName!!.toUpperCamelCase().plus(BINDING_SUFFIX)

return ClassName.get(moduleName + ".databinding", modelName)
}

private fun buildGeneratedModelName(): ClassName {
val modelName = layoutResource.resourceName!!
.removePrefix(layoutPrefix)
.toUpperCamelCase()
.plus(BINDING_SUFFIX)
.plus(GeneratedModelInfo.GENERATED_MODEL_SUFFIX)

return ClassName.get(moduleName, modelName)
}

companion object {

val BINDING_SUFFIX = "Binding"
}
}
Loading