From 8b9389b20a42792fe93101b00f2001f5dc88b57e Mon Sep 17 00:00:00 2001 From: Araz Abishov Date: Mon, 17 Oct 2016 23:56:02 +0200 Subject: [PATCH] Replaced Jackson by combination of GSON and AutoValue extension. (#205) * Added dependencies on gson and autovalue-gson libraries. * Implemented GsonTypeAdapterFactory which spits out Item.GsonTypeAdapter generated by autovalue. Added test which ensures that Item type is registered within factory. * Replaced EqualsVerifier in Item unit tests, which was failing against AutoValue_Item (it was trying to instantiate super type ($AutoValue_Type) with null arguments, as the result .equals() and .hascode() methods were throwing NPE. Suppressing warnings did not help. * Replaced GsonTypeAdapterFactory with generated implementation from autovalue-gson. Removed redundant tests. --- app/build.gradle | 11 +++++--- .../api/entities/ItemTest.java | 7 +++--- .../qualitymatters/ApplicationComponent.java | 6 ++--- .../qualitymatters/ApplicationModule.java | 18 ++++++++++--- .../qualitymatters/api/ApiModule.java | 8 +++--- .../qualitymatters/api/entities/Item.java | 24 +++++++++--------- .../other/EntityTypeAdapterFactory.java | 12 +++++++++ .../qualitymatters/api/entities/ItemTest.java | 25 ++++++++++++------- dependencies.gradle | 10 +++++--- 9 files changed, 77 insertions(+), 44 deletions(-) create mode 100644 app/src/main/java/com/artemzin/qualitymatters/other/EntityTypeAdapterFactory.java diff --git a/app/build.gradle b/app/build.gradle index c458087..63139e3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -8,8 +8,8 @@ apply plugin: 'com.github.ben-manes.versions' paperwork { set = [ - gitSha: gitSha(), - buildDate: buildTime("dd-MM-yyyy HH:00:00 z", "UTC"), + gitSha : gitSha(), + buildDate: buildTime("dd-MM-yyyy HH:00:00 z", "UTC"), ] } @@ -119,14 +119,17 @@ dependencies { compile libraries.okHttp compile libraries.retrofit - compile libraries.retrofitJacksonConverter + compile libraries.retrofitGsonConverter compile libraries.retrofitRxJavaAdapter - compile libraries.jacksonDataBind + compile libraries.gson // Do not compile AutoValue dependencies to the app. annotationProcessor libraries.autoValue + annotationProcessor libraries.autoValueGson + // Make AutoValue annotation visible to the compiler. provided libraries.autoValue + provided libraries.autoValueGson compile libraries.supportAnnotations compile libraries.supportAppCompat diff --git a/app/src/integrationTests/java/com/artemzin/qualitymatters/integration_tests/api/entities/ItemTest.java b/app/src/integrationTests/java/com/artemzin/qualitymatters/integration_tests/api/entities/ItemTest.java index 18dac43..5b53a9c 100644 --- a/app/src/integrationTests/java/com/artemzin/qualitymatters/integration_tests/api/entities/ItemTest.java +++ b/app/src/integrationTests/java/com/artemzin/qualitymatters/integration_tests/api/entities/ItemTest.java @@ -2,7 +2,7 @@ import com.artemzin.qualitymatters.QualityMattersIntegrationRobolectricTestRunner; import com.artemzin.qualitymatters.api.entities.Item; -import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.gson.Gson; import org.junit.Test; import org.junit.runner.RunWith; @@ -19,9 +19,9 @@ public class ItemTest { // 2. Be sure that @JsonIgnore and similar annotations do not affect expected behavior (cc @karlicos). @Test public void fromJson() throws IOException { - ObjectMapper objectMapper = QualityMattersIntegrationRobolectricTestRunner.qualityMattersApp().applicationComponent().objectMapper(); + Gson gson = QualityMattersIntegrationRobolectricTestRunner.qualityMattersApp().applicationComponent().gson(); - Item item = objectMapper.readValue("{ " + + Item item = gson.fromJson("{ " + "\"id\": \"test_id\", " + "\"image_preview_url\": \"some_url\"," + "\"title\": \"Test title\", " + @@ -34,5 +34,4 @@ public void fromJson() throws IOException { assertThat(item.title()).isEqualTo("Test title"); assertThat(item.shortDescription()).isEqualTo("Test short description"); } - } diff --git a/app/src/main/java/com/artemzin/qualitymatters/ApplicationComponent.java b/app/src/main/java/com/artemzin/qualitymatters/ApplicationComponent.java index 5d98bf6..9121399 100644 --- a/app/src/main/java/com/artemzin/qualitymatters/ApplicationComponent.java +++ b/app/src/main/java/com/artemzin/qualitymatters/ApplicationComponent.java @@ -18,7 +18,7 @@ import com.artemzin.qualitymatters.performance.AsyncJobsObserver; import com.artemzin.qualitymatters.ui.activities.MainActivity; import com.artemzin.qualitymatters.ui.fragments.ItemsFragment; -import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.gson.Gson; import javax.inject.Singleton; @@ -36,9 +36,9 @@ }) public interface ApplicationComponent { - // Provide ObjectMapper from the real app to the tests without need in injection to the test. + // Provide Gson from the real app to the tests without need in injection to the test. @NonNull - ObjectMapper objectMapper(); + Gson gson(); // Provide QualityMattersRestApi from the real app to the tests without need in injection to the test. @NonNull diff --git a/app/src/main/java/com/artemzin/qualitymatters/ApplicationModule.java b/app/src/main/java/com/artemzin/qualitymatters/ApplicationModule.java index c092922..dea98b7 100644 --- a/app/src/main/java/com/artemzin/qualitymatters/ApplicationModule.java +++ b/app/src/main/java/com/artemzin/qualitymatters/ApplicationModule.java @@ -5,9 +5,12 @@ import android.os.Looper; import android.support.annotation.NonNull; -import com.artemzin.qualitymatters.models.QualityMattersImageLoader; import com.artemzin.qualitymatters.models.PicassoImageLoader; -import com.fasterxml.jackson.databind.ObjectMapper; +import com.artemzin.qualitymatters.models.QualityMattersImageLoader; +import com.artemzin.qualitymatters.other.EntityTypeAdapterFactory; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.TypeAdapterFactory; import com.jakewharton.picasso.OkHttp3Downloader; import com.squareup.picasso.Picasso; @@ -36,8 +39,15 @@ public Application provideQualityMattersApp() { } @Provides @NonNull @Singleton - public ObjectMapper provideObjectMapper() { - return new ObjectMapper(); + public TypeAdapterFactory provideTypeAdapterFactory() { + return EntityTypeAdapterFactory.create(); + } + + @Provides @NonNull @Singleton + public Gson provideGson(TypeAdapterFactory typeAdapterFactory) { + return new GsonBuilder() + .registerTypeAdapterFactory(typeAdapterFactory) + .create(); } @Provides @NonNull @Named(MAIN_THREAD_HANDLER) @Singleton diff --git a/app/src/main/java/com/artemzin/qualitymatters/api/ApiModule.java b/app/src/main/java/com/artemzin/qualitymatters/api/ApiModule.java index 8bc324c..f47e0fd 100644 --- a/app/src/main/java/com/artemzin/qualitymatters/api/ApiModule.java +++ b/app/src/main/java/com/artemzin/qualitymatters/api/ApiModule.java @@ -3,7 +3,7 @@ import android.support.annotation.NonNull; import com.artemzin.qualitymatters.BuildConfig; -import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.gson.Gson; import javax.inject.Singleton; @@ -12,7 +12,7 @@ import okhttp3.OkHttpClient; import retrofit2.Retrofit; import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory; -import retrofit2.converter.jackson.JacksonConverterFactory; +import retrofit2.converter.gson.GsonConverterFactory; @Module public class ApiModule { @@ -30,11 +30,11 @@ public ChangeableBaseUrl provideChangeableBaseUrl() { } @Provides @NonNull @Singleton - public QualityMattersRestApi provideQualityMattersApi(@NonNull OkHttpClient okHttpClient, @NonNull ObjectMapper objectMapper, @NonNull ChangeableBaseUrl changeableBaseUrl) { + public QualityMattersRestApi provideQualityMattersApi(@NonNull OkHttpClient okHttpClient, @NonNull Gson gson, @NonNull ChangeableBaseUrl changeableBaseUrl) { return new Retrofit.Builder() .baseUrl(changeableBaseUrl) .client(okHttpClient) - .addConverterFactory(JacksonConverterFactory.create(objectMapper)) + .addConverterFactory(GsonConverterFactory.create(gson)) .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) .validateEagerly(BuildConfig.DEBUG) // Fail early: check Retrofit configuration at creation time in Debug build. .build() diff --git a/app/src/main/java/com/artemzin/qualitymatters/api/entities/Item.java b/app/src/main/java/com/artemzin/qualitymatters/api/entities/Item.java index 381dfe7..890dca8 100644 --- a/app/src/main/java/com/artemzin/qualitymatters/api/entities/Item.java +++ b/app/src/main/java/com/artemzin/qualitymatters/api/entities/Item.java @@ -2,16 +2,15 @@ import android.support.annotation.NonNull; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.google.auto.value.AutoValue; +import com.google.gson.Gson; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.SerializedName; // This class is immutable, it has correctly implemented hashCode and equals. // Thanks to AutoValue https://github.com/google/auto/tree/master/value. @AutoValue -@JsonDeserialize(builder = AutoValue_Item.Builder.class) public abstract class Item { - private static final String JSON_PROPERTY_ID = "id"; private static final String JSON_PROPERTY_IMAGE_PREVIEW_URL = "image_preview_url"; private static final String JSON_PROPERTY_TITLE = "title"; @@ -23,38 +22,39 @@ public static Builder builder() { } @NonNull - @JsonProperty(JSON_PROPERTY_ID) + public static TypeAdapter typeAdapter(Gson gson) { + return new AutoValue_Item.GsonTypeAdapter(gson); + } + + @NonNull + @SerializedName(JSON_PROPERTY_ID) public abstract String id(); @NonNull - @JsonProperty(JSON_PROPERTY_IMAGE_PREVIEW_URL) + @SerializedName(JSON_PROPERTY_IMAGE_PREVIEW_URL) public abstract String imagePreviewUrl(); @NonNull - @JsonProperty(JSON_PROPERTY_TITLE) + @SerializedName(JSON_PROPERTY_TITLE) public abstract String title(); @NonNull - @JsonProperty(JSON_PROPERTY_SHORT_DESCRIPTION) + @SerializedName(JSON_PROPERTY_SHORT_DESCRIPTION) public abstract String shortDescription(); @AutoValue.Builder public static abstract class Builder { @NonNull - @JsonProperty(JSON_PROPERTY_ID) public abstract Builder id(@NonNull String id); @NonNull - @JsonProperty(JSON_PROPERTY_IMAGE_PREVIEW_URL) public abstract Builder imagePreviewUrl(@NonNull String imagePreviewUrl); @NonNull - @JsonProperty(JSON_PROPERTY_TITLE) public abstract Builder title(@NonNull String title); @NonNull - @JsonProperty(JSON_PROPERTY_SHORT_DESCRIPTION) public abstract Builder shortDescription(@NonNull String shortDescription); @NonNull diff --git a/app/src/main/java/com/artemzin/qualitymatters/other/EntityTypeAdapterFactory.java b/app/src/main/java/com/artemzin/qualitymatters/other/EntityTypeAdapterFactory.java new file mode 100644 index 0000000..e922bb3 --- /dev/null +++ b/app/src/main/java/com/artemzin/qualitymatters/other/EntityTypeAdapterFactory.java @@ -0,0 +1,12 @@ +package com.artemzin.qualitymatters.other; + +import com.google.gson.TypeAdapterFactory; +import com.ryanharter.auto.value.gson.GsonTypeAdapterFactory; + +@GsonTypeAdapterFactory +public abstract class EntityTypeAdapterFactory implements TypeAdapterFactory { + + public static TypeAdapterFactory create() { + return new AutoValueGson_EntityTypeAdapterFactory(); + } +} diff --git a/app/src/unitTests/java/com/artemzin/qualitymatters/api/entities/ItemTest.java b/app/src/unitTests/java/com/artemzin/qualitymatters/api/entities/ItemTest.java index 52aa507..1c9aa9d 100644 --- a/app/src/unitTests/java/com/artemzin/qualitymatters/api/entities/ItemTest.java +++ b/app/src/unitTests/java/com/artemzin/qualitymatters/api/entities/ItemTest.java @@ -2,18 +2,25 @@ import org.junit.Test; -import nl.jqno.equalsverifier.EqualsVerifier; -import nl.jqno.equalsverifier.Warning; +import static org.assertj.core.api.Java6Assertions.assertThat; public class ItemTest { @Test - public void hashCode_equals_shouldWorkCorrectly() { - EqualsVerifier - .forExamples( - Item.builder().id("id1").imagePreviewUrl("i1").title("Title1").shortDescription("s1").build(), - Item.builder().id("id2").imagePreviewUrl("i2").title("Title2").shortDescription("s2").build()) - .suppress(Warning.NULL_FIELDS) // AutoValue checks nullability, EqualsVerifier does not expect that by default. - .verify(); + public void equals_shouldWorkCorrectly() { + Item itemOne = Item.builder().id("id1").imagePreviewUrl("i1").title("Title1").shortDescription("s1").build(); + Item itemTwo = Item.builder().id("id2").imagePreviewUrl("i2").title("Title2").shortDescription("s2").build(); + + assertThat(itemOne).isNotEqualTo(itemTwo); + } + + @Test + public void hashcode_shouldWorkCorrectly() { + Item itemOne = Item.builder().id("id1").imagePreviewUrl("i1").title("Title1").shortDescription("s1").build(); + Item itemOneCopy = Item.builder().id("id1").imagePreviewUrl("i1").title("Title1").shortDescription("s1").build(); + Item itemTwo = Item.builder().id("id2").imagePreviewUrl("i2").title("Title2").shortDescription("s2").build(); + + assertThat(itemOne.hashCode()).isEqualTo(itemOneCopy.hashCode()); + assertThat(itemOne.hashCode()).isNotEqualTo(itemTwo.hashCode()); } } \ No newline at end of file diff --git a/dependencies.gradle b/dependencies.gradle index bef63b9..e2d463a 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -17,7 +17,7 @@ ext.versions = [ errorProneCoreVersion : '2.0.9', dexcountPlugin : '0.6.1', versionsGradlePlugin : '0.13.0', - buildScan : '1.1.1', + buildScan : '1.1.1', dagger : '2.6', @@ -26,8 +26,9 @@ ext.versions = [ supportLibs : '23.1.1', okHttp : '3.2.0', retrofit : '2.0.0-beta4', - jackson : '2.8.1', + gson : '2.7', autoValue : '1.2', + autoValueGson : '0.4.2', butterKnife : '8.2.1', picasso : '2.5.2', picassoOkHttpDownloader : '1.0.2', @@ -72,11 +73,12 @@ ext.libraries = [ okHttp : "com.squareup.okhttp3:okhttp:$versions.okHttp", okHttpLoggingInterceptor: "com.squareup.okhttp3:logging-interceptor:$versions.okHttp", retrofit : "com.squareup.retrofit2:retrofit:$versions.retrofit", - retrofitJacksonConverter: "com.squareup.retrofit2:converter-jackson:$versions.retrofit", + retrofitGsonConverter : "com.squareup.retrofit2:converter-gson:$versions.retrofit", retrofitRxJavaAdapter : "com.squareup.retrofit2:adapter-rxjava:$versions.retrofit", - jacksonDataBind : "com.fasterxml.jackson.core:jackson-databind:$versions.jackson", + gson : "com.google.code.gson:gson:$versions.gson", autoValue : "com.google.auto.value:auto-value:$versions.autoValue", + autoValueGson : "com.ryanharter.auto.value:auto-value-gson:$versions.autoValueGson", supportAnnotations : "com.android.support:support-annotations:$versions.supportLibs", supportAppCompat : "com.android.support:appcompat-v7:$versions.supportLibs",