Skip to content

Commit

Permalink
New view annotation for hook after view bound (#242)
Browse files Browse the repository at this point in the history
  • Loading branch information
elihart authored Jul 14, 2017
1 parent 526322f commit db8b76c
Show file tree
Hide file tree
Showing 10 changed files with 419 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.airbnb.epoxy;

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

/**
* This can be used to annotate methods inside classes with a {@link com.airbnb.epoxy.ModelView}
* annotation. Methods with this annotation will be called after a view instance is bound to a
* model and all model props have been set. This is useful if you need to wait until multiple props
* are set before doing certain initialization.
* <p>
* Methods with this annotation will be called after both the initial bind when the view comes on
* screen, and after partial binds when an onscreen view is updated.
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface AfterPropsSet {
}

Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* This can be used to annotate methods inside classes with a {@link com.airbnb.epoxy.ModelView}
* annotation. Methods with this annotation will be called when the view is recycled by the
* RecyclerView.
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface OnViewRecycled {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ boolean addToBindWithDiffMethod(Builder methodBuilder, ParameterSpec boundObject
ParameterSpec previousModelParam) {
return false;
}

public void addToHandlePostBindMethod(Builder postBindBuilder, ParameterSpec boundObjectParam) {

}
}

GeneratedModelWriter(Filer filer, Types typeUtils, ErrorLogger errorLogger,
Expand Down Expand Up @@ -429,6 +433,8 @@ private Iterable<MethodSpec> generateBindMethods(GeneratedModelInfo classInfo) {
addHashCodeValidationIfNecessary(postBindBuilder,
"The model was changed during the bind call.");

builderHooks.addToHandlePostBindMethod(postBindBuilder, boundObjectParam);

methods.add(postBindBuilder
.build());

Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
package com.airbnb.epoxy

import com.airbnb.epoxy.ClassNames.EPOXY_LITHO_MODEL
import com.squareup.javapoet.ClassName
import com.squareup.javapoet.ParameterizedTypeName

import javax.lang.model.element.Element
import javax.lang.model.element.TypeElement
import javax.lang.model.util.Elements
import javax.lang.model.util.Types

import com.airbnb.epoxy.ClassNames.EPOXY_LITHO_MODEL

internal class LithoModelInfo(
typeUtils: Types,
elementUtils: Elements,
layoutSpecClassElement: TypeElement) : GeneratedModelInfo() {
layoutSpecClassElement: TypeElement
) : GeneratedModelInfo() {

val lithoComponentName: ClassName

Expand All @@ -39,7 +38,7 @@ internal class LithoModelInfo(
* package, and with the "Spec" term removed from the name.
*/
fun getLithoComponentName(elementUtils: Elements,
layoutSpecClassElement: TypeElement): ClassName {
layoutSpecClassElement: TypeElement): ClassName {
val packageName = elementUtils.getPackageOf(layoutSpecClassElement).qualifiedName.toString()

// Litho doesn't appear to allow specs as nested classes, so we don't check for nested
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

class ModelViewInfo extends GeneratedModelInfo {
final List<String> resetMethodNames = new ArrayList<>();
final List<String> afterPropsSetMethodNames = new ArrayList<>();
final TypeElement viewElement;
final Types typeUtils;
final Elements elements;
Expand Down Expand Up @@ -144,6 +145,10 @@ void addOnRecycleMethod(ExecutableElement resetMethod) {
resetMethodNames.add(resetMethod.getSimpleName().toString());
}

void addAfterPropsSetMethod(ExecutableElement afterPropsSetMethod) {
afterPropsSetMethodNames.add(afterPropsSetMethod.getSimpleName().toString());
}

LayoutResource getLayoutResource(LayoutResourceProcessor layoutResourceProcessor) {
ModelView annotation = viewElement.getAnnotation(ModelView.class);
int layoutValue = annotation.defaultLayout();
Expand All @@ -166,6 +171,10 @@ List<String> getResetMethodNames() {
return resetMethodNames;
}

List<String> getAfterPropsSetMethodNames() {
return afterPropsSetMethodNames;
}

List<ViewAttributeInfo> getViewAttributes() {
List<ViewAttributeInfo> result = new ArrayList<>(attributeInfo.size());
for (AttributeInfo info : attributeInfo) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ Collection<? extends GeneratedModelInfo> process(RoundEnvironment roundEnv,

processSetterAnnotations(roundEnv);
processResetAnnotations(roundEnv);
processAfterBindAnnotations(roundEnv);

updateViewsForInheritedViewAnnotations();

Expand Down Expand Up @@ -242,14 +243,35 @@ private void processResetAnnotations(RoundEnvironment roundEnv) {
ModelViewInfo info = getModelInfoForMethodElement(recycleMethod);
if (info == null) {
errorLogger.logError("%s annotation can only be used in classes annotated with %s",
ModelProp.class, ModelView.class);
OnViewRecycled.class, ModelView.class);
continue;
}

info.addOnRecycleMethod((ExecutableElement) recycleMethod);
}
}

private void processAfterBindAnnotations(RoundEnvironment roundEnv) {
for (Element afterPropsMethod : roundEnv.getElementsAnnotatedWith(AfterPropsSet.class)) {
if (!validateAfterPropsMethod(afterPropsMethod)) {
continue;
}

ModelViewInfo info = getModelInfoForMethodElement(afterPropsMethod);
if (info == null) {
errorLogger.logError("%s annotation can only be used in classes annotated with %s",
AfterPropsSet.class, ModelView.class);
continue;
}

info.addAfterPropsSetMethod((ExecutableElement) afterPropsMethod);
}
}

private boolean validateAfterPropsMethod(Element resetMethod) {
return validateExecutableElement(resetMethod, AfterPropsSet.class, 0);
}

/** Include props and reset methods from super class views. */
private void updateViewsForInheritedViewAnnotations() {
for (ModelViewInfo view : modelClassMap.values()) {
Expand All @@ -262,6 +284,7 @@ private void updateViewsForInheritedViewAnnotations() {
}

view.resetMethodNames.addAll(otherView.resetMethodNames);
view.afterPropsSetMethodNames.addAll(otherView.afterPropsSetMethodNames);

boolean samePackage =
belongToTheSamePackage(view.viewElement, otherView.viewElement, elements);
Expand Down Expand Up @@ -462,6 +485,13 @@ boolean addToBindWithDiffMethod(Builder methodBuilder, ParameterSpec boundObject
return true;
}

@Override
public void addToHandlePostBindMethod(Builder postBindBuilder,
ParameterSpec boundObjectParam) {

addAfterPropsAddedMethodsToBuilder(postBindBuilder, modelInfo, boundObjectParam);
}

@Override
void addToUnbindMethod(MethodSpec.Builder unbindBuilder, String unbindParamName) {
for (ViewAttributeInfo viewAttribute : modelInfo.getViewAttributes()) {
Expand Down Expand Up @@ -558,6 +588,13 @@ private void addResetMethodsToBuilder(Builder builder, ModelViewInfo modelViewIn
}
}

private void addAfterPropsAddedMethodsToBuilder(Builder methodBuilder, ModelViewInfo modelInfo,
ParameterSpec boundObjectParam) {
for (String methodName : modelInfo.getAfterPropsSetMethodNames()) {
methodBuilder.addStatement(boundObjectParam.name + "." + methodName + "()");
}
}

private ModelViewInfo getModelInfoForMethodElement(Element element) {
Element enclosingElement = element.getEnclosingElement();
if (enclosingElement == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -857,4 +857,23 @@ public void layoutOverloads() {
.and()
.generatesSources(generatedModel);
}

@Test
public void afterBindProps() {
JavaFileObject model = JavaFileObjects
.forResource("TestAfterBindPropsView.java");

JavaFileObject superModel = JavaFileObjects
.forResource("TestAfterBindPropsSuperView.java");

JavaFileObject generatedModel =
JavaFileObjects.forResource("TestAfterBindPropsViewModel_.java");

assert_().about(javaSources())
.that(asList(model, superModel))
.processedWith(new EpoxyProcessor())
.compilesWithoutError()
.and()
.generatesSources(generatedModel);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.airbnb.epoxy;

import android.content.Context;
import android.view.View;

@ModelView(defaultLayout = 1)
public abstract class TestAfterBindPropsSuperView extends View {

public TestAfterBindPropsSuperView(Context context) {
super(context);
}

@ModelProp
public void setFlagSuper(boolean flag) {

}

@AfterPropsSet
public void afterFlagSetSuper() {

}
}
21 changes: 21 additions & 0 deletions epoxy-processortest/src/test/resources/TestAfterBindPropsView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.airbnb.epoxy;

import android.content.Context;

@ModelView(defaultLayout = 1)
public class TestAfterBindPropsView extends TestAfterBindPropsSuperView {

public TestAfterBindPropsView(Context context) {
super(context);
}

@ModelProp
public void setFlag(boolean flag) {

}

@AfterPropsSet
public void afterFlagSet() {

}
}
Loading

0 comments on commit db8b76c

Please sign in to comment.