Brandon
Brandon

Reputation: 1421

Android Release APK Not Parsing JSON From Web Service with SSL Using Retrofit2

I have created a REST web service using SSL, i.e., https://api.bmcstudios.org

The endpoint that I am trying to hit is:

GET https://api.bmcstudios.org/member

The expected response is

{
    "members": [
        {
            "memberId": "5c4e448d46e0fb0001656a46",
            "email": "[email protected]",
            "displayName": "username",
            "firstName": "First",
            "lastName": "Last",
            "address": {
                "street_1": "Street Address 1",
                "street_2": "",
                "city": "City",
                "state": "ST",
                "zipCode": 55555,
                "country": "US"
            },
            "phone": "123-456-7890",
            "roles": [
                {
                    "id": "5c47fe53473d4ad05bdf99f5",
                    "role": "ROLE_MEMBER"
                }
            ]
        }
    ]
}

I receive this response when I use Postman, and when I run the Android emulator Pixel 2 XL API 28.

When I deploy my build via the Android App Bundle in the latest version of Android Studio 3.3 to the google play store, via the Internal Test Track as well as Production Track I am able to reach my web service. But the response that I am returned is empty!

Here are what my logs show as the response:

I/System.out: appWebservice.getMemberDetails(): {"members":[{},{},{}]}

The structure is present, but the Member object within the array does not show up. Here is my Java code below:

Retrofit2 Interface

public interface AppWebservice {
    String API_URL = "https://api.bmcstudios.org/";

    Gson gson = new GsonBuilder()
                        .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
                        .create();

    Retrofit RETROFIT = new Retrofit.Builder()
                                .baseUrl(API_URL)
                                .addConverterFactory(GsonConverterFactory.create(gson))
                                .build();

    // MEMBER
    @GET("member")
    Call<MemberResponse> getMemberDetails(
            @HeaderMap Map<String, String> headers);
}

Member Response Object

@Getter
@Setter
@NoArgsConstructor
public class MemberResponse implements Serializable {
    @SerializedName("members")
    @Expose
    private List<Member> members;

    @Getter
    @Setter
    @NoArgsConstructor
    public class Member implements Serializable {
        @SerializedName("memberId")
        @Expose
        private String memberId;
        @SerializedName("email")
        @Expose
        private String email;
        @SerializedName("displayName")
        @Expose
        private String displayName;
        @SerializedName("firstName")
        @Expose
        private String firstName;
        @SerializedName("lastName")
        @Expose
        private String lastName;
        @SerializedName("picture")
        @Expose
        private String picture;
        @SerializedName("address")
        @Expose
        private Address address;
        @SerializedName("phone")
        @Expose
        private String phone;
        @SerializedName("roles")
        @Expose
        private List<Role> roles;

        @Getter
        @Setter
        @NoArgsConstructor
        public class Address implements Serializable {
            @SerializedName("street_1")
            @Expose
            private String street1;
            @SerializedName("street_2")
            @Expose
            private String street2;
            @SerializedName("city")
            @Expose
            private String city;
            @SerializedName("state")
            @Expose
            private String state;
            @SerializedName("zipCode")
            @Expose
            private Integer zipCode;
            @SerializedName("country")
            @Expose
            private String country;
        }

        @Getter
        @Setter
        @NoArgsConstructor
        public class Role implements Serializable {
            @SerializedName("id")
            @Expose
            private String id;
            @SerializedName("role")
            @Expose
            private String role;
        }
    }
}

Making the service call within the app:

public void getMemberDetails() {
        if (getIdToken().getValue() == null) {
            updateIdToken();
        } else {
            Map<String, String> headers = new HashMap<>();
            headers.put("Content-type", "application/json");
            headers.put(FIREBASE_AUTH_HEADER, getIdToken().getValue());

            appWebservice.getMemberDetails(headers)
                    .enqueue(new Callback<MemberResponse>() {
                        @Override
                        public void onResponse(
                                @NonNull Call<MemberResponse> call,
                                @NonNull retrofit2.Response<MemberResponse> response) {
                            if (response.body() != null) {
                                System.out.println("appWebservice.getMemberDetails(): " + new Gson().toJson(response.body()));

                                for (MemberResponse.Member member : response.body().getMembers()) {
                                    MemberEntity memberEntity =
                                            toEntity(new Gson().toJson(member));

                                    if (isNotBlank(memberEntity.getMemberId())) {
                                        repository.storeMemberDetails(memberEntity);
                                    }
                                }
                            }
                        }

                        @Override
                        public void onFailure(
                                @NonNull Call<MemberResponse> call, @NonNull Throwable t) {
                            System.err.println("appWebservice.getMemberDetails(): " + t.getMessage());
                        }
                    });
        }
    }

I am not reaching the onFailure call because the service call is successful. I receive a json response, yet it doesn't seem to be parsing correctly, i.e., the data is missing from the objects within the array.

This issue only occurs when I deploy my .apk through the google play store.

Please help me resolve this issue! Thank you

Upvotes: 2

Views: 1483

Answers (2)

wilson whippet
wilson whippet

Reputation: 31

This issue was answered here Gson Not mapping data in Production Mode APK Android

add this to your proguard rules

# Gson uses generic type information stored in a class file when working with fields. Proguard
# removes such information by default, so configure it to keep all of it.
-keepattributes Signature

# Gson specific classes
-keep class sun.misc.Unsafe { *; }
#-keep class com.google.gson.stream.** { *; }

# Application classes that will be serialized/deserialized over Gson
-keep class com.example.YourPackage.** { *; }

Upvotes: 1

Brandon
Brandon

Reputation: 1421

I was able to solve this problem using the Gson object called JsonElement

https://google.github.io/gson/apidocs/com/google/gson/JsonElement.html

I swapped out the MemberResponse object for the JsonElement object and then manually parsed my data via JsonObject and JsonArray. It worked out perfectly.

Here are the updated files.

public interface AppWebservice {
    String API_URL = "https://api.bmcstudios.org/";

    Gson gson = new GsonBuilder()
                        .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
                        .create();

    Retrofit RETROFIT = new Retrofit.Builder()
                                .baseUrl(API_URL)
                                .addConverterFactory(GsonConverterFactory.create(gson))
                                .build();

    // MEMBER
    @GET("member")
    Call<JsonElement> getMemberDetails(
            @HeaderMap Map<String, String> headers);
}

public void getMemberDetails() {
    if (getIdToken().getValue() == null) {
        updateIdToken();
    } else {
        Map<String, String> headers = new HashMap<>();
        headers.put("Content-type", "application/json");
        headers.put(FIREBASE_AUTH_HEADER, getIdToken().getValue());

        appWebservice.getMemberDetails(headers)
                .enqueue(new Callback<JsonElement>() {
                    @Override
                    public void onResponse(
                            @NonNull Call<JsonElement> call,
                            @NonNull retrofit2.Response<JsonElement> response) {
                        if (response.body() != null) {
                            System.out.println("appWebservice.putMemberDetails(): " + new Gson().toJson(response.body()));

                            JsonObject jsonObject = response.body().getAsJsonObject();
                            if (jsonObject.has("members")) {
                                JsonArray members = jsonObject.getAsJsonArray("members");
                                for (JsonElement memberElement : members) {
                                    MemberEntity memberEntity =
                                            toEntity(new Gson().toJson(memberElement));

                                    if (isNotBlank(memberEntity.getMemberId())) {
                                        repository.storeMemberDetails(memberEntity);

                                        member.postValue(Member.toMember(memberEntity));
                                    }
                                }
                            }
                        }
                    }

                    @Override
                    public void onFailure(
                            @NonNull Call<JsonElement> call, @NonNull Throwable t) {
                        System.err.println("appWebservice.getMemberDetails(): " + t.getMessage());
                    }
                });
    }
}

Upvotes: 0

Related Questions