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

Speeding up development with AutoValue and SQLD...

Speeding up development with AutoValue and SQLDelight

Andrii Rakhimov

March 27, 2017
Tweet

More Decks by Andrii Rakhimov

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. How does it works? @AutoValue public abstract class User {

    @Nullable public abstract String username(); public abstract String email(); }
  3. How does it works? @Retention(RetentionPolicy.SOURCE) @Target({ ElementType.TYPE}) public @interface AutoValue

    { @Retention(RetentionPolicy.SOURCE) @Target({ElementType.TYPE}) public @interface Builder { } }
  4. Generated implementation abstract class AutoValue_User extends User { private final

    String email; private final String username; AutoValue_User(String email, @Nullable String username) { if (email == null) { throw new NullPointerException("Null email"); } this.username = username; } ...getters, equals, hashcode, toString, etc.
  5. Instantiation @AutoValue public abstract class User { 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(); } } AS plugin to generate builders https://github.com/afcastano/AutoValuePlugin
  6. Mutable 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(); }
  7. 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.readString(), in.readInt() == 0 ? in.readString() : null ); } @Override public AutoValue_User[] newArray(int size) { return new AutoValue_User[size]; } ...
  8. 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); } }
  9. 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(); }
  10. 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;
  11. All Extensions • JSON(Gson/Jackson) • Parcelable • Cursor • Firebase

    • Redact(Omitting selected fields from toString())
  12. Pros/Cons • DRY • Immutable(Type-safe, null-safe and thread-safe) • Maintainable(180->20,

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

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

    may break • Mutable fields, arrays, lists
  15. The problem static final String CREATE_CATEGORIES_TABLE = "create table "

    + Category.TABLE + "(" + COLUMN_ID + " integer primary key autoincrement, " + Category.COL_NAME + " text, " + Category.COL_COLOR + " integer, " + Category.COL_CREATE_BY_USER + " integer DEFAULT 0 " + ");"; cursor.getString(cursor.getColumnIndexOrThrow(columnName)); ContentValues values = new ContentValues(); values.put(COL_NAME, name);
  16. The problem private static String LIST_QUERY(String selection) { String query

    = "SELECT *, COUNT(i." + ListItem.COL_STATUS + ") " + List.COL_SIZE + ", SUM(i." + ListItem.COL_STATUS + ") " + List.COL_BOUGHT_COUNT + " FROM " + List.TABLE + " s" + " LEFT OUTER JOIN " + ListItem.TABLE + " i" + " ON s." + List.COL_ID + " = i." + ListItem.COL_PARENT_LIST_ID; if (selection == null) { return query + " GROUP BY s." + List.COL_ID; } return query + " WHERE " + selection + " GROUP BY s." + List.COL_ID; }
  17. Features that we want: • RxJava • Immutability • Type

    Safety • Compiler Errors • SQLite features
  18. Features that we want: • RxJava - SQLBrite • Immutability

    • Type Safety • Compiler Errors • SQLite features
  19. Features that we want: • RxJava - SQLBrite • Immutability

    - AutoValue • Type Safety • Compiler Errors • SQLite features
  20. Features that we want: • RxJava - SQLBrite • Immutability

    - AutoValue • Type Safety - SQLDelight • Compiler Errors - SQLDelight • SQLite features - SQLDelight
  21. CREATE TABLE images ( _id INTEGER NOT NULL PRIMARY KEY

    AUTOINCREMENT, ad_id INTEGER NOT NULL, url TEXT, size TEXT, image_id TEXT ); saveImage: INSERT INTO images(ad_id, url, size, image_id) VALUES (?, ?, ?, ?); getAllImages: SELECT * FROM images; SQL statements
  22. Java model @AutoValue public abstract class Image implements ImagesModel {

    public static final Factory<Image> FACTORY = new Factory<>(new Creator<Image>() { @Override public Image create(long _id, long ad_id, @Nullable String url, @Nullable String size, @Nullable String image_id) { return new AutoValue_Image(_id, ad_id, url, size, image_id); } }); public static final RowMapper<Image> GET_ALL_IMAGES_MAPPER = FACTORY.getAllImagesMapper(); public static final RowMapper<Image> GET_IMAGES_BY_ID_MAPPER = FACTORY.getImagesByIdMapper(); }
  23. Cursor mapper final class Mapper<T extends ImagesModel> implements RowMapper<T> {

    private final Factory<T> imagesModelFactory; public Mapper(Factory<T> imagesModelFactory) { this.imagesModelFactory = imagesModelFactory; } @Override public T map(@NonNull Cursor cursor) { return imagesModelFactory.creator.create( cursor.getLong(0), cursor.getLong(1), cursor.isNull(2) ? null : cursor.getString(2), cursor.isNull(3) ? null : cursor.getString(3), cursor.isNull(4) ? null : cursor.getString(4) ); } } cursor.getString(cursor.getColumnIndexOrThrow(columnName));
  24. Example of insertion final class SaveImage extends SqlDelightCompiledStatement.Insert { public

    SaveImage(SQLiteDatabase database) { super("images", database.compileStatement("" + "INSERT INTO images(ad_id, url)\n" + "VALUES (?, ?)")); } public void bind(long ad_id, @Nullable String url) { program.bindLong(1, ad_id); if (url == null) { program.bindNull(2); } else { program.bindString(2, url); } } } saveImage: INSERT INTO images(ad_id, url) VALUES (?, ?);
  25. Example of insertion saveImage: INSERT INTO images(ad_id, url) VALUES (?,

    ?); Image.SaveImage saveImage = new ImagesModel.SaveImage(db); saveImage.bind(10, "url"); saveImage.program.executeInsert();
  26. Example of query getImagesById: SELECT * FROM images WHERE ad_id

    = ? ORDER BY _id ASC; public SqlDelightStatement getAdsByStatus(@NonNull Ad.Status status) { List<String> args = new ArrayList<String>(); int currentIndex = 1; StringBuilder query = new StringBuilder(); query.append("SELECT *\n" + "FROM ads\n" + "WHERE status = "); query.append('?').append(currentIndex++); args.add((String) statusAdapter.encode(status)); return new SqlDelightStatement(query.toString(), args.toArray(new String[args.size()]), Collections.<String>singleton("ads")); }
  27. Example of query getImagesById: SELECT * FROM images WHERE ad_id

    = ? ORDER BY _id ASC; SqlDelightStatement query = Image.FACTORY.getImagesById(10); Cursor cursor = db.rawQuery(query.statement, query.args); Ad.FACTORY.getAdsByStatusMapper().map(cursor);
  28. Sets of values also can be passed as an argument

    getImagesById: SELECT * FROM images WHERE ad_id IN ? or WHERE ad_id IN :param_name ORDER BY _id ASC; SqlDelightStatement query = Image.FACTORY.getImagesById(new long[]{10, 20}); Cursor cursor = db.rawQuery(query.statement, query.args);
  29. Query with join selectAll: SELECT * FROM ads JOIN images

    ON ads.ad_id = images.ad_id; interface SelectAllModel<T1 extends AdModel, T2 extends ImagesModel> { @NonNull T1 ads(); @NonNull T2 images(); }
  30. Query with join public static final RowMapper<AdWithImages> SELECT_ALL_MAPPER = FACTORY.selectAllMapper(

    new SelectAllCreator<Ad, Image, AdWithImages>() { @Override public AdWithImages create(@NonNull Ad ads, @NonNull Image images) { return new AutoValue_Ad_AdWithImages(ads, images); } }, Image.FACTORY); @AutoValue public static abstract class AdWithImages implements SelectAllModel<Ad, Image> { }
  31. Support imports like Java in .sq files import com.justplay1.sqldelight.models.Ad.Status; import

    java.util.Calendar; CREATE TABLE ads ( _id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, ad_id INTEGER NOT NULL UNIQUE ON CONFLICT IGNORE, title TEXT, description TEXT, price TEXT, status TEXT AS Status NOT NULL, create_time INTEGER AS Calendar NOT NULL, is_favorite INTEGER AS Boolean NOT NULL DEFAULT 0 );
  32. Support imports like Java in .sq files public final class

    DateAdapter implements ColumnAdapter<Calendar, Long> { @Override public Long encode(@NonNull Calendar date) { return date.getTimeInMillis(); } @Override @NonNull public Calendar decode(Long data) { Calendar calendar = Calendar.getInstance(); calendar.setTimeInMillis(data); return calendar; } }
  33. Support imports like Java in .sq files @AutoValue public abstract

    class Ad implements AdModel { public enum Status { ACTIVE, DEACTIVATED } private static final DateAdapter DATE_ADAPTER = new DateAdapter(); private static final EnumColumnAdapter<Status> STATUS_ADAPTER = EnumColumnAdapter.create(Status.class); public static final Factory<Ad> FACTORY = new Factory<>(new Creator<Ad>() { @Override public Ad create(long _id, long ad_id, @Nullable String title, @NonNull Status status, @NonNull Calendar create_time, boolean is_favorite) { return new AutoValue_Ad(_id, ad_id, title, status, create_time, is_favorite); } }, STATUS_ADAPTER, DATE_ADAPTER); }
  34. Support to pass any type into query from java CREATE

    TABLE ads ( status TEXT AS Status NOT NULL ); getAdsByStatus: SELECT * FROM ads WHERE status = ?; SqlDelightStatement query = Ad.FACTORY.getAdsByStatus(Ad.Status.ACTIVE); Cursor cursor = db.rawQuery(query.statement, query.args);
  35. SQLDelight with SQLBrite private BriteDatabase db; private Observable<Ad> getAd() {

    SqlDelightStatement statement = Ad.FACTORY.getAdsByStatus(Ad.Status.ACTIVE); return db.createQuery(statement.tables, statement.statement, statement.args) .mapToOne(Ad.ADS_BY_STATUS_MAPPER::map); }
  36. • Syntax highlighting • Refactoring/Find usages • Code autocompletion •

    Generate Model files after edits • Right click to copy as valid SQLite • Compiler errors in IDE click through to file Intellij IDEA plugin
  37. Conclusion • RxJava, Immutability, Type Safety, Compiler Errors, SQLite features

    • Shared language • Tight coupling with views (MVVM) • Easier code review • Confidence writing SQL
  38. 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 SQLDelight - https://github.com/square/sqldelight SQLBrite - https://github.com/square/sqlbrite Rakhimov Andrii - https://www.facebook.com/profile.php?id=100000447429343 | [email protected] Mkhytar Mkhoian - https://www.facebook.com/mkhytar.mkhoian | [email protected]