2016年12月14日 星期三

【Android】最簡潔的Model層 - AutoValue 使用介紹【三】

這篇要來說的是 AutoValue 如何搭配【Gson】一塊使用,

由於已經將 Model 內原先的field變更為 abstract method 了,

可想而知在 Gson de/serialize 的過程中會發生問題!

ps.如果不了解 Gson 可以先參考我的另一篇文章

不做任何處理的話會發現在 call Gson 的 fromJson() 時會噴出 Exception,

java.lang.RuntimeException: Failed to invoke public com.yourpackagename.Book() with no args
如果要讓 Gson 成功的序列化,就必須給它一個新的 parse 規則(預設規則不適用在我們的 AutoValue Model 身上),

這邊我們一樣會用到一個 library【auto-value-gson

接下來只需要幾個簡單步驟:
一、在 Model 類別內新增靜態的 typeAdapter() method
二、新增一個抽象類實作 TypeAdapterFactory,並添加一個可產生物件的靜態 method
三、使用 GsonBuilder 來 create Gson,並給予我們自訂義的 TypeAdapterFactory




Gradle:

//auto-value-gson
provided 'com.ryanharter.auto.value:auto-value-gson:0.4.6'
annotationProcessor 'com.ryanharter.auto.value:auto-value-gson:0.4.6'



After:

//步驟一

@AutoValue
public abstract class Book {
    public abstract String name();
    public abstract int price();
    public abstract String author();

    public static Book create(String name, int price, String author) {
        return new AutoValue_Book(name, price, author);
    }

    public static TypeAdapter<Book> typeAdapter(Gson gson) {
        return new AutoValue_Book.GsonTypeAdapter(gson);
    }
}
//步驟二

@GsonTypeAdapterFactory
public abstract class GsonAdapterFactory implements TypeAdapterFactory {
    public static TypeAdapterFactory create() {
        return new AutoValueGson_GsonAdapterFactory();
    }
}
//步驟三

String jsonString = "{\"author\":\"Anson\",\"name\":\"安森瓦舍\",\"price\":87}";
Gson gson = new GsonBuilder().registerTypeAdapterFactory(GsonAdapterFactory.create()).create();
//serialize
Book book = gson.fromJson(jsonString, Book.class);
//deserialize
System.out.println(gson.toJson(book));



這邊特別要注意的是【步驟二】的 return new AutoValueGson_GsonAdapterFactory() 的部分,
如果你在【步驟一】的 model 中沒有 typeAdapter 的靜態方法,那麼在 build project 時,AutoValueGson_GsonAdapterFactory 是不會被創建出來的。所以步驟可別亂掉!




最後,我在github上創建了一個Project來演示AotoValue與Rxjava2Retrofit2搭配使用的範例。




延伸閱讀:

● 最簡潔的Model層 - AutoValue 使用介紹【一】
● 最簡潔的Model層 - AutoValue 使用介紹【二】

2016年12月13日 星期二

【Android】最簡潔的Model層 - AutoValue 使用介紹【二】

一般來說,在Android若要採用較高效能的Parcelable來做資料傳遞,我們需要在Model內實作【writeToParcel、describeContents、CREATOR】,

這實在是一個很繁雜的功夫,也就是前一篇提過的duplicate code,

還好,在我們使用了AutoValue後,就能一併使用另一個plugin(auto-value-parcel),順便解決這個煩死人的實作。

ps.關於Parcelable與Serializable的效能差異可以參考我的另一篇文章



Gradle:

//auto-value-parcel
annotationProcessor 'com.ryanharter.auto.value:auto-value-parcel:0.2.5'



Before:

public class Book implements Parcelable {
    private String name;
    private int price;
    private String author;

    public Book(String name, int price, String author) {
        this.name = name;
        this.price = price;
        this.author = author;
    }

    @Override
    public String toString() {
        return "Book{"
                + "name=" + name
                + ", price=" + price
                + ", author=" + author
                + "}";
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }
    
    public static final Parcelable.Creator<Book> CREATOR = new Parcelable.Creator<Book>() {
        @Override
        public Book createFromParcel(Parcel in) {
            return new Book(
                    in.readString(),
                    in.readInt(),
                    in.readString()
            );
        }
        @Override
        public Book[] newArray(int size) {
            return new Book[size];
        }
    };

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(name);
        dest.writeInt(price);
        dest.writeString(author);
    }
}



After:

@AutoValue
public abstract class Book implements Parcelable {
    public abstract String name();
    public abstract int price();
    public abstract String author();

    public static Book create(String name, int price, String author) {
        return new AutoValue_Book(name, price, author);
    }
}


基本上在implement Parcelable 後,就不需要做事囉!

有沒有發現,我們的Model層越來越簡潔,越來越可愛了♡♡




延伸閱讀:

● 最簡潔的Model層 - AutoValue 使用介紹【一】
● 最簡潔的Model層 - AutoValue 使用介紹【三】

【Android】最簡潔的Model層 - AutoValue 使用介紹【一】

寫程式應該要盡可能的避免duplicate code(重複程式碼),一樣的、沒有價值的東西,一直寫幹嘛?對吧!

在Android開發上,不管你是使用哪一種Architecture(MVC、MVP、MVVM...等等),都一定有Model層,Model是我們在程式開發上最原始、最簡單的東西。

但打開你的Project看看你的Model,會發現重複的method一直在出現【getter、setter、equals、hashCode、toString....】等等等。

現在,我們透過【AutoValue】就可以大幅的簡化它了。

這個由google maintain的github開源項目,在本文章撰寫時最新版本是v1.4.1



Library projects:

Gradle:

//auto-value
provided 'com.google.auto.value:auto-value:1.4.1'
annotationProcessor 'com.google.auto.value:auto-value:1.4.1'



以下使用一個Book類別做示範



Before:

public class Book {
    private String name;
    private int price;
    private String author;

    public Book(String name, int price, String author) {
        this.name = name;
        this.price = price;
        this.author = author;
    }

    @Override
    public String toString() {
        return "Book{"
                + "name=" + name
                + ", price=" + price
                + ", author=" + author
                + "}";
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }
}
Book book = new Book("安森瓦舍", 87, "Anson");
System.out.println("書名:" + book.getName());
System.out.println("價格:" + book.getPrice());
System.out.println("作者:" + book.getAuthor());



After:

@AutoValue
public abstract class Book {
    public abstract String name();
    public abstract int price();
    public abstract String author();
    
    public static Book create(String name, int price, String author) {
        return new AutoValue_Book(name, price, author);
    }
}
Book book = Book.create("安森瓦舍", 87, "Anson");
System.out.println("書名:" + book.name());
System.out.println("價格:" + book.price());
System.out.println("作者:" + book.author());



如果你想使用Builder pattern的話

@AutoValue
public abstract class Book {
    public abstract String name();
    public abstract int price();
    public abstract String author();

    public static Book create(String name, int price, String author) {
        return builder()
                .name(name)
                .price(price)
                .author(author)
                .build();
    }

    public static Builder builder() {
        return new AutoValue_Book.Builder();
    }
    
    @AutoValue.Builder
    public abstract static class Builder {
        public abstract Builder name(String name);

        public abstract Builder price(int price);

        public abstract Builder author(String author);

        public abstract Book build();
    }
}
Book book = Book.create("安森瓦舍", 87, "Anson");
System.out.println("書名:" + book.name());
System.out.println("價格:" + book.price());
System.out.println("作者:" + book.author());



延伸閱讀:

● 最簡潔的Model層 - AutoValue 使用介紹【二】
● 最簡潔的Model層 - AutoValue 使用介紹【三】

2016年1月28日 星期四

【Android】Gson 使用介紹

Gson是Google在2008年就推出的一套Library

目的是提供開發者快速的將JSON字串(String)轉換成物件(Object)

以及將物件(Object)快速的轉換成JSON字串(String)

目前release到v2.8.1




Gradle:

//gson
compile 'com.google.code.gson:gson:2.8.1'



假設我們有這樣一組JSON字串要轉換成Object

[
    {
        "UserName":"Anson",
        "UserAge":20
    },
    {
        "UserName":"Kevin",
        "UserAge":30
    }
]

可以看出是由一個JSONArray包住兩個JSONObject
JSONObject裡面有兩個屬性,分別是UserName 與 UserAge
於是乎我們便定義了這樣的一個 Model 來裝這些資料

public class User {
    private String UserName;
    private int UserAge;

    public User(String userName, int userAge) {
        UserName = userName;
        UserAge = userAge;
    }

    public String getUserName() {
        return UserName;
    }

    public int getUserAge() {
        return UserAge;
    }
}



使用Gson將 JSON字串 轉換成 Array
只需要簡單一行就搞定!

String jsonString = "[{\"UserName\":\"Anson\",\"UserAge\":20},{\"UserName\":\"Kevin\",\"UserAge\":30}]";
User[] userArray = new Gson().fromJson(jsonString, User[].class);

如果想轉成 List 呢?

String jsonString = "[{\"UserName\":\"Anson123123\",\"UserAge\":20},{\"UserName\":\"Kevin\",\"UserAge\":30}]";
java.lang.reflect.Type listType = new TypeToken<Collection<User>>(){}.getType();
List<User> userList = new Gson().fromJson(jsonString , listType);



那如果是物件轉JSON字串呢?
也是很簡單~~

ArrayList<User> userList = new ArrayList();
for (int i=0;i<2;i++){
    User user = new User("Test"+i,i);
    userList.add(user);
}
String jsonString = new Gson().toJson(userList);
System.out.println(jsonString);

輸出結果:

[{"UserName":"Test0","UserAge":0},{"UserName":"Test1","UserAge":1}]



有發現上面的User class中

在第2.3行我們所定義的屬性為 UserNameUserAge

名子跟JSON裡面的屬性一樣

但這不符合JAVA中的命名規範(小寫開頭)

那如果我們將class中的屬性改成userNameuserAge

在進行轉換時就會失敗~因為大小寫與JSON字串不一致

於是我們可以加上Gson提供的一個很方便的功能

@SerializedName("對應的JSON屬性名稱")

我們可以將 model 改成這個樣子...

public class User {
    @SerializedName("UserName")
    private String name;
    
    @SerializedName("UserAge")
    private int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }
    
    public int getAge() {
        return age;
    }
}

這樣子就等於告訴Gson,在轉換JSON字串時需要將 Model 內的

nameUserName
ageUserAge

以及在 JSON字串 轉 物件 時,將

UserNamename
UserAgeage






來試試複雜一點的例子吧:

{
    "Id":123456,
    "Name":"張三",
    "Gender":true,
    "Phones":[
        {"Type":"Landline","Number":"02-2800-0000"},
        {"Type":"Mobile","Number":"0900-000-000"}
    ],
    "Hobbies":[
        "釣魚","睡覺","打籃球"
    ]
}

這組 JSON 裡面可以看出,這是在描述一個人
他叫做張三,性別男(假設男為true,女為false),電話有兩支,一支市話一支手機
興趣有三個,分別是釣魚.睡覺.打籃球
來定義 Model 吧:

public class Guest {
    @SerializedName("Id")
    private int id;

    @SerializedName("Name")
    private String name;

    @SerializedName("Gender")
    private boolean gender;

    @SerializedName("Phones")
    private List<Phone> phoneList;

    @SerializedName("Hobbies")
    private List<String> hobbyList;

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public boolean getGender() {
        return gender;
    }

    public List<Phone> getPhoneList() {
        return phoneList;
    }

    public List<String> getHobbyList() {
        return hobbyList;
    }

    @Override
    public String toString() {
        return new Gson().toJson(this, Guest.class);
    }

    public static class Phone {
        enum Type {
            Landline, Mobile
        }

        @SerializedName("Type")
        private Type type;

        @SerializedName("Number")
        private String number;

        public Type getType() {
            return type;
        }

        public String getNumber() {
            return number;
        }
    }
}

在這個 Guest 這個 class 中
先使用 SerializedName 將原本 JSON字串 的屬性名稱轉換成我們想要的名子
接著在42行定義了一個 inner class 來裝電話的資料(當然不一定要使用內部類)
可以看到43行 列舉(enum) 也是可以在 gson 轉換中直接使用的
在38行我 override 了 toString 方法,目的是印 log 比較方便
定義好了 Model 後,接著的資料轉換就輕鬆多了!

String jsonString = "{\"Id\":123456,\"Name\":\"張三\",\"Gender\":true,\"Phones\":[{\"Type\":\"Landline\",\"Number\":\"02-2800-0000\"},{\"Type\":\"Mobile\",\"Number\":\"0900-000-000\"}],\"Hobbies\":[\"釣魚\",\"睡覺\",\"打籃球\"]}";
Guest guest = new Gson().fromJson(jsonString, Guest.class);
System.out.println(guest.toString());

也是一行輕鬆搞定

輸出結果:

{"Gender":true,"Hobbies":["釣魚","睡覺","打籃球"],"Id":123456,"Name":"張三","Phones":[{"Number":"02-2800-0000","Type":"Landline"},{"Number":"0900-000-000","Type":"Mobile"}]}



與Gson類似的Library還可以參考FasterXML jackson