Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Speeding up development with AutoValue - Andrii Rakhimov

Speeding up development with AutoValue - Andrii Rakhimov

GDG Ternopil

April 29, 2017
Tweet

More Decks by GDG Ternopil

Other Decks in Programming

Transcript

  1. Problems public class User implements Parcelable { public static final

    Creator<User> CREATOR = new Creator<User>() { @Override public User createFromParcel(Parcel in) { return new User(in); } @Override public User[] newArray(int size) { return new User[size]; } }; @SerializedName("username") private String userName; @SerializedName("email") private String email; public User(String userName) { this.userName = userName; } protected User(Parcel in) { userName = in.readString(); email = in.readString(); } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(userName); dest.writeString(email); } @Override public int describeContents() { return 0; } public String getUserName() { return userName; } public String setUserName(String userName) { this.userName = userName; } public String getEmail() { return email; } public String setEmail(String email) { this.email = email; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; User user = (User) o; if (userName != null ? !userName.equals(user.userName) : user.userName != null) return false; return email != null ? !email.equals(user.email) : user.email != null; } @Override public int hashCode() { int result = 31; result = 31 * result + (userName != null ? userName.hashCode() : 0); result = 31 * result + (email != null ? email.hashCode() : 0); return result; } @Override public String toString() { return "User{userName='" + userName + ", email='" + email; }
  2. What we write and get @AutoValue public abstract class User

    { @Nullable public abstract String username(); public abstract String email(); } final class AutoValue_User extends User { private final String username; private final String email; AutoValue_User(@Nullable String username, String email) { this.username = username; if (email == null) { throw new NullPointerException("Null email"); } this.email = email; } @Nullable @Override public String username() { return username; } @Override public String email() { return email; } //toString, equals, hashCode…
  3. How does it works? @Retention(RetentionPolicy.SOURCE) @Target({ ElementType.TYPE}) public @interface AutoValue

    { @Retention(RetentionPolicy.SOURCE) @Target({ElementType.TYPE}) public @interface Builder { } }
  4. How we instantiate @AutoValue public abstract class User { @Nullable

    public abstract String username(); public abstract String email(); public static Builder builder() { return new AutoValue_User.Builder(); } @AutoValue.Builder public abstract static class Builder { public abstract Builder email(String email); public abstract Builder username(String username); public abstract User build(); } } static final class Builder extends User.Builder { private String email; private String username; Builder() { } Builder(User source) { this.email = source.email(); this.username = source.username(); } @Override public User.Builder email(String email) { this.email = email; return this; } @Override public User.Builder username(@Nullable String username) { this.username = username; return this; } @Override public User build() { String missing = ""; if (email == null) { missing += " email"; } if (!missing.isEmpty()) { throw new IllegalStateException("Missing properties:" + missing); } return new AutoValue_User(this.email, this.username); } }
  5. How we instantiate @AutoValue public abstract class User { @Nullable

    public abstract String username(); public abstract String email(); public static User create(String username, String email) { return builder().username(username).email(email).build(); } public static Builder builder() { return new AutoValue_User.Builder(); } @AutoValue.Builder public abstract static class Builder { public abstract Builder email(String email); public abstract Builder username(String username); public abstract User build(); } }
  6. How we instantiate public class ApiClientMock implements ApiClientType { @Override

    public Observable<User> login(LoginBody body) { return Observable.just( User.create("ivan", "[email protected]") ); } }
  7. Don't use constructors, use builders under the hood @AutoValue public

    abstract class User { @Nullable public abstract String username(); public abstract String email(); public static User create(String username, String email) { return new AutoValue_User(username, email); } }
  8. Don't. Avoid mutable property types @AutoValue public abstract class MutableExample

    { public static MutableExample create(MutablePropertyType ouch) { // Replace `.clone` below with the right copying code for this type return new AutoValue_MutableExample(ouch.clone()); } /** * Returns the ouch associated with this object; <b>do not mutate</b> the * returned object. */ public abstract MutablePropertyType ouch(); }
  9. Parcelable final class AutoValue_User extends $AutoValue_User { public static final

    Parcelable.Creator<AutoValue_User> CREATOR = new Parcelable.Creator<AutoValue_User>() { @Override public AutoValue_User createFromParcel(Parcel in) { return new AutoValue_User( in.readInt() == 0 ? in.readString() : null, in.readString() ); } @Override public AutoValue_User[] newArray(int size) { return new AutoValue_User[size]; } }; AutoValue_User(String username, String email) { super(username, email); } @Override public void writeToParcel(Parcel dest, int flags) { if (username() == null) { dest.writeInt(1); } else { dest.writeInt(0); dest.writeString(username()); } dest.writeString(email()); } @Override public int describeContents() { return 0; }
  10. How this works public abstract class AutoValueExtension { //Determines whether

    this Extension applies to the given context. public boolean applicable(Context context) { return false; } //Denotes that the class generated by this Extension must be the final class in the inheritance hierarchy. public boolean mustBeFinal(Context context) { return false; } //Returns a possibly empty set of property names that this Extension intends to implement public Set<String> consumeProperties(Context context) { return Collections.emptySet(); } //Returns a possible empty set of abstract methods that this Extension intends to implement public Set<ExecutableElement> consumeMethods(Context context) { return Collections.emptySet(); } //Returns the generated source code of the class public abstract String generateClass(Context context, String className, String classToExtend, boolean isFinal); }
  11. How this works @Override public boolean applicable(Context context) { TypeMirror

    autoValueClass = context.autoValueClass().asType(); // Disallow manual implementation of the CREATOR instance ... // Disallow manual implementation of writeToParcel ExecutableElement writeToParcel = findWriteToParcel(context); if (writeToParcel != null) { context.processingEnvironment().getMessager().printMessage(Diagnostic.Kind.ERROR, "Manual implementation of Parcelable#writeToParcel(Parcel,int) found when processing " + autoValueClass.toString() + ". Remove this so auto-value-parcel can automatically generate the " + "implementation for you.", writeToParcel); } TypeMirror parcelable = context.processingEnvironment().getElementUtils() .getTypeElement("android.os.Parcelable").asType(); return TypeSimplifier.isClassOfType(context.processingEnvironment().getTypeUtils(), parcelable, autoValueClass); }
  12. How this works @Override public Set<String> consumeProperties(Context context) { ImmutableSet.Builder<String>

    properties = new ImmutableSet.Builder<>(); for (String property : context.properties().keySet()) { switch (property) { case "describeContents": properties.add(property); break; } } return properties.build(); }
  13. How this works @Override public Set<ExecutableElement> consumeMethods(Context context) { ImmutableSet.Builder<ExecutableElement>

    methods = new ImmutableSet.Builder<>(); for (ExecutableElement element : context.abstractMethods()) { switch (element.getSimpleName().toString()) { case "writeToParcel": methods.add(element); break; } } return methods.build(); }
  14. How this works @Override public String generateClass(Context context, String className,

    String classToExtend, boolean isFinal) { … //using JavaPoet to generate class on top of Spec } MethodSpec generateDescribeContents() { return MethodSpec.methodBuilder("describeContents") .addAnnotation(Override.class) .addModifiers(PUBLIC) .returns(int.class) .addStatement("return 0") .build(); }
  15. Gson @AutoValue public abstract class User implements Parcelable { @Nullable

    public abstract String username(); public abstract String email(); public static TypeAdapter<User> typeAdapter(Gson gson) { return new AutoValue_User.GsonTypeAdapter(gson); } }
  16. Gson @GsonTypeAdapterFactory public abstract class AdapterFactory implements TypeAdapterFactory { public

    static AdapterFactory create() { return new AutoValueGson_AdapterFactory(); } } @Provides @Singleton Gson provideGson() { return new GsonBuilder() .registerTypeAdapterFactory(AdapterFactory.create()) .create(); }
  17. Gson abstract class $AutoValue_User extends $$AutoValue_User { ... public static

    final class GsonTypeAdapter extends TypeAdapter<User> { ... @Override public void write(JsonWriter jsonWriter, User object) throws IOException { if (object == null) { jsonWriter.nullValue(); return; } jsonWriter.beginObject(); jsonWriter.name("username"); usernameAdapter.write(jsonWriter, object.username()); jsonWriter.name("email"); emailAdapter.write(jsonWriter, object.email()); jsonWriter.endObject(); } } @Override public User read(JsonReader jsonReader) throws IOException { if (jsonReader.peek() == JsonToken.NULL) { jsonReader.nextNull(); return null; } jsonReader.beginObject(); String email = this.defaultEmail; String username = this.defaultUsername; while (jsonReader.hasNext()) { String _name = jsonReader.nextName(); if (jsonReader.peek() == JsonToken.NULL) { jsonReader.nextNull(); continue; } switch (_name) { case "username": { username = usernameAdapter.read(jsonReader); break; } case "email": { email = emailAdapter.read(jsonReader); break;
  18. All Extensions • JSON(Gson/Jackson) • Parcelable • Cursor • Firebase

    • Redact(Omitting selected fields from toString())
  19. • DRY • Immutable(Type-safe, null-safe and thread-safe) • Maintainable(180->20, no

    need for tests(builder), faster PRs, debuggable) Pros/Cons
  20. Pros/Cons • DRY • Immutable(Type-safe, null-safe and thread-safe) • Maintainable(180->20,

    no need for tests(builder), faster PRs, debuggable) • API-invisible
  21. Pros/Cons • DRY • Immutable(Type-safe, null-safe and thread-safe) • Maintainable(180->20,

    no need for tests(builder), faster PRs, debuggable) • API-invisible • No runtime dependencies
  22. Pros/Cons • DRY • Immutable(Type-safe, null-safe and thread-safe) • Maintainable(180->20,

    no need for tests(builder), faster PRs, debuggable) • API-invisible • No runtime dependencies • Docs
  23. Cons • Require to compile • Fabrics for class instantiation

    may break • Mutable fields, arrays, lists
  24. Links and contacts AutoValue - https://github.com/google/auto/blob/master/value/userguide/index.md AutoValue plugins for AS

    - https://github.com/afcastano/AutoValuePlugin https://github.com/robohorse/RoboPOJOGenerator AutoValue Extensions - https://github.com/blipinsk/awesome-auto-value-extensions @Eliminate("Boilerplate") by Ryan Harter - https://www.youtube.com/watch?v=NBkl_SIHUr8 AutoValue by Jake Wharton - https://www.youtube.com/watch?v=FfBBTHkRC-o