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

Generate model factory methods #450

Merged
merged 14 commits into from
Jun 27, 2018
Merged

Generate model factory methods #450

merged 14 commits into from
Jun 27, 2018

Conversation

ngsilverman
Copy link
Contributor

@ngsilverman ngsilverman commented Jun 15, 2018

  • Creates a new module, epoxy-modelfactory
    • Houses the new ModelProperties interface
    • Depends on Paris to enable ModelProperties to set styles
    • Contains the relevant tests
      • I tried adding a dependency on epoxy-processortest to reuse ProcessorTestUtils but that caused mysterious Lint issues so I copied a single method from it over instead.
  • If this new module is present, for all models that have an empty constructor, no property group, and at least one property of a supported type, a static from(ModelProperties properties) method is generated.
  public static MyViewModel_ from(ModelProperties properties) {
    MyViewModel_ model = new MyViewModel_();
    model.id(properties.getString("id"));
    // This is repeated for each attribute of a supported type
    if (properties.has("title")) {
      model.title(properties.getString("title"));
    }
    ...
    // If it's styleable
    Style style = properties.getStyle();
    if (style != null) {
      model.style(style);
    }
    return model;
  }

TODO

  • Fix Lint errors
  • Is the new module's package name setup correct?

(I rebased and reformatted GeneratedModelWriter but nothing changed. Maybe it hadn't been properly formatted in the first place?)

@@ -998,7 +1089,7 @@ internal class GeneratedModelWriter(
if (attr is ViewAttributeInfo && attr.generateStringOverloads) {
methods.addAll(StringOverloadWriter(modelInfo, attr, configManager).buildMethods())
} else {
if (attr.isViewClickListener) {
if (attr.isViewClickListener || attr.isViewLongClickListener) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

^ this is a change (though not a functional one, I modified isViewClickListener so I could use it elsewhere).

@@ -1295,6 +1412,106 @@ internal class GeneratedModelWriter(
return method
}

/**
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And here's the main addition besides tests.


javaCompileOptions {
annotationProcessorOptions {
includeCompileClasspath false
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes it so Gradle doesn't complain that there are undeclared annotation processors in the classpath.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

leave a code comment about it?

Copy link
Contributor Author

@ngsilverman ngsilverman Jun 16, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That seems a bit redundant, the config itself is pretty explicit: don't include annotation processors that are in the compile classpath, which this module does because it's testing them with the google testing compile lib.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well you still left a github comment explaining it, so it isn't too explicit ;)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only because it doesn't seem directly related to this PR. 😛

@ngsilverman ngsilverman force-pushed the nsilverman-modelfactory branch from 6e0e8fb to f101814 Compare June 15, 2018 22:39
@ngsilverman ngsilverman force-pushed the nsilverman-modelfactory branch from f101814 to d4ee671 Compare June 15, 2018 22:41
double getDouble(@NonNull String propertyName);

@NonNull
Drawable getDrawable(@NonNull String propertyName);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could support getDrawableRes by looking for @DrawableRes annotation

import java.util.List;

public interface ModelProperties {

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dedicated getId() method?


import java.util.List;

public interface ModelProperties {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can use kotlin for this module


internal object ProcessorTestUtils {
@JvmOverloads
@JvmStatic
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't need the jvm helpers

}

val supportedAttributeInfo = modelInfo.attributeInfo.filter { attributeInfo ->
listOf(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

might be easier to read to have this list declared outside the loop

attributeInfo.isDrawable -> "getDrawable"
attributeInfo.isInt -> "getInt"
attributeInfo.isViewClickListener -> "getOnClickListener"
else -> throw IllegalStateException()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Put a message in the exception

}

// Groups aren't supported because we wouldn't know which type to choose from
if (modelInfo.attributeGroups.any { it.attributes.size > 1 }) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

check this before supported attributes?

}

// If none of the properties are of a supported type the method isn't generated
if (supportedAttributeInfo.isEmpty()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should it be an error if any attributes are not supported?


javaCompileOptions {
annotationProcessorOptions {
includeCompileClasspath false
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

leave a code comment about it?

@ngsilverman
Copy link
Contributor Author

ngsilverman commented Jun 16, 2018

@elihart addressed all the feedback. 😄

Copy link
Contributor

@elihart elihart left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great! ship it 🚀


interface ModelProperties {

fun getId(): String
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why fun over val?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With everything else being a function it seemed inconsistent/out of place. 🤷‍♂️


javaCompileOptions {
annotationProcessorOptions {
includeCompileClasspath false
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well you still left a github comment explaining it, so it isn't too explicit ;)

Copy link
Contributor

@elihart elihart left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ngsilverman great, updates look nice. thanks!

boolean isEpoxyModelList() {
return Utils.isType(
getTypeMirror(),
"java.util.List<? extends com.airbnb.epoxy.EpoxyModel<?>>"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about kotlin support?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@elihart after doing some testing I just realized that we don't need to check for Kotlin types at all (for lists, strings or charsequences alike). Guess they get converted to Java types for interop.

}

boolean isStringList() {
return Utils.isType(getTypeMirror(), "java.util.List<java.lang.String>")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe a good way to DRY up the two list checks is to have a isList(type) method that checks for java/kotlin lists and also checks the generic type as a parameter

boolean isEpoxyModelList() {
return Utils.isType(
getTypeMirror(),
"java.util.List<? extends com.airbnb.epoxy.EpoxyModel<?>>"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be nice to use a ClassName constant or something, not sure if that is possible with the wildcard

|| isStringType(type);
}

static boolean isStringAttributeDataType(TypeMirror type) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

better to have a generic isType(TypeMirror , ClassName) method?

) {
// The epoxy-modelfactory module must be present to enable this functionality
val hasModelFactoryDependency =
getTypeMirror(EPOXY_MODEL_PROPERTIES.reflectionName(), elements) != null
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

opportunity for an elements.isTypeLoaded(ClassName) extension


// Models that don't have an empty constructor are not supported because there would be no
// clear way to create new instances
val hasEmptyConstructor = modelInfo.constructors.isEmpty()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could this be a method on the ModelInfo class?

@ngsilverman
Copy link
Contributor Author

@elihart Added a Kotlin view test and followed your suggestions. Let me know if this is good to go. Do you want me to handle the release?

@elihart
Copy link
Contributor

elihart commented Jun 27, 2018

@ngsilverman yeah, seems good to go. if you can do the release that'd be great

@ngsilverman ngsilverman merged commit ed916a0 into master Jun 27, 2018
@ngsilverman ngsilverman deleted the nsilverman-modelfactory branch June 27, 2018 22:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants