Im migrating from using Volley to Retrofit, I already have gson class that I used before for converting JSONObject reponse to a object that implements gson annotations. When I'm trying to make http get request using retrofit but then my app crashes with this error :
Unable to start activity ComponentInfo{com.lightbulb.pawesome/com.example.sample.retrofit.SampleActivity}: java.lang.IllegalArgumentException: Unable to create converter for class com.lightbulb.pawesome.model.Pet
for method GitHubService.getResponse
Im following the guide in retrofit site and I come up with these implementations :
This is my activity where I am trying to execute the retro http request:
public class SampleActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sample);
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("**sample base url here**")
.build();
GitHubService service = retrofit.create(GitHubService.class);
Call<Pet> callPet = service.getResponse("41", "40");
callPet.enqueue(new Callback<Pet>() {
#Override
public void onResponse(Response<Pet> response) {
Log.i("Response", response.toString());
}
#Override
public void onFailure(Throwable t) {
Log.i("Failure", t.toString());
}
});
try{
callPet.execute();
} catch (IOException e){
e.printStackTrace();
}
}
}
My interface which turned to be my API
public interface GitHubService {
#GET("/ **sample here** /{petId}/{otherPet}")
Call<Pet> getResponse(#Path("petId") String userId, #Path("otherPet") String otherPet);
}
And finally the Pet class which should be the reponse:
public class Pet implements Parcelable {
public static final String ACTIVE = "1";
public static final String NOT_ACTIVE = "0";
#SerializedName("is_active")
#Expose
private String isActive;
#SerializedName("pet_id")
#Expose
private String petId;
#Expose
private String name;
#Expose
private String gender;
#Expose
private String age;
#Expose
private String breed;
#SerializedName("profile_picture")
#Expose
private String profilePicture;
#SerializedName("confirmation_status")
#Expose
private String confirmationStatus;
/**
*
* #return
* The confirmationStatus
*/
public String getConfirmationStatus() {
return confirmationStatus;
}
/**
*
* #param confirmationStatus
* The confirmation_status
*/
public void setConfirmationStatus(String confirmationStatus) {
this.confirmationStatus = confirmationStatus;
}
/**
*
* #return
* The isActive
*/
public String getIsActive() {
return isActive;
}
/**
*
* #param isActive
* The is_active
*/
public void setIsActive(String isActive) {
this.isActive = isActive;
}
/**
*
* #return
* The petId
*/
public String getPetId() {
return petId;
}
/**
*
* #param petId
* The pet_id
*/
public void setPetId(String petId) {
this.petId = petId;
}
/**
*
* #return
* The name
*/
public String getName() {
return name;
}
/**
*
* #param name
* The name
*/
public void setName(String name) {
this.name = name;
}
/**
*
* #return
* The gender
*/
public String getGender() {
return gender;
}
/**
*
* #param gender
* The gender
*/
public void setGender(String gender) {
this.gender = gender;
}
/**
*
* #return
* The age
*/
public String getAge() {
return age;
}
/**
*
* #param age
* The age
*/
public void setAge(String age) {
this.age = age;
}
/**
*
* #return
* The breed
*/
public String getBreed() {
return breed;
}
/**
*
* #param breed
* The breed
*/
public void setBreed(String breed) {
this.breed = breed;
}
/**
*
* #return
* The profilePicture
*/
public String getProfilePicture() {
return profilePicture;
}
/**
*
* #param profilePicture
* The profile_picture
*/
public void setProfilePicture(String profilePicture) {
this.profilePicture = profilePicture;
}
protected Pet(Parcel in) {
isActive = in.readString();
petId = in.readString();
name = in.readString();
gender = in.readString();
age = in.readString();
breed = in.readString();
profilePicture = in.readString();
}
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(isActive);
dest.writeString(petId);
dest.writeString(name);
dest.writeString(gender);
dest.writeString(age);
dest.writeString(breed);
dest.writeString(profilePicture);
}
#SuppressWarnings("unused")
public static final Parcelable.Creator<Pet> CREATOR = new Parcelable.Creator<Pet>() {
#Override
public Pet createFromParcel(Parcel in) {
return new Pet(in);
}
#Override
public Pet[] newArray(int size) {
return new Pet[size];
}
};
}
If anyone ever comes across this in the future because you are trying to define your own custom converter factory and are getting this error, it can also be caused by having multiple variables in a class with a misspelled or the same serialized name. IE:
public class foo {
#SerializedName("name")
String firstName;
#SerializedName("name")
String lastName;
}
Having serialized names defined twice (likely by mistake) will also throw this exact same error.
Update: Keep in mind that this logic also holds true via inheritance. If you extend to a parent class with an object that has the same Serialized name as you do in the sub-class, it will cause this same problem.
Prior to 2.0.0, the default converter was a gson converter, but in 2.0.0 and later the default converter is ResponseBody. From the docs:
By default, Retrofit can only deserialize HTTP bodies into OkHttp's
ResponseBody type and it can only accept its RequestBody type for
#Body.
In 2.0.0+, you need to explicitly specify you want a Gson converter:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("**sample base url here**")
.addConverterFactory(GsonConverterFactory.create())
.build();
You will also need to add the following dependency to your gradle file:
compile 'com.squareup.retrofit2:converter-gson:2.1.0'
Use the same version for the converter as you do for your retrofit. The above matches this retrofit dependency:
compile ('com.squareup.retrofit2:retrofit:2.1.0')
Also, note as of writing this, the retrofit docs are not completely updated, which is why that example got you into trouble. From the docs:
Note: This site is still in the process of being expanded for the new 2.0 APIs.
just make sure that you are not using the same serialize name twice
#SerializedName("name") val name: String
#SerializedName("name") val firstName: String
just remove one of them
Based on top comment I updated my imports
implementation 'com.squareup.retrofit2:retrofit:2.1.0'
implementation 'com.squareup.retrofit2:converter-gson:2.1.0'
I've used http://www.jsonschema2pojo.org/ in order to create pojo's from Spotify json results and making sure to specify Gson format.
These days there are Android Studio plugins which can create the pojo's or Kotlin data models for you. One great option for mac is Quicktype.
https://itunes.apple.com/us/app/paste-json-as-code-quicktype/id1330801220
In my case, I had a TextView object inside my modal class and GSON did not know how to serialize it. Marking it as 'transient' solved the issue.
#Silmarilos's post helped me solve this. In my case, it was that I used "id" as a serialized name, like this:
#SerializedName("id")
var node_id: String? = null
and I changed it to
#SerializedName("node_id")
var node_id: String? = null
All working now. I forgot that 'id' is a default attribute.
In my case, it was due to trying to take a List being returned by my service into an ArrayList. So what I had was:
#Json(name = "items")
private ArrayList<ItemModel> items;
when I should've had
#Json(name = "items")
private List<ItemModel> items;
Hope this helps someone!
In my case I was using the Moshi library with Retrofit 2.0, i.e
// Moshi
implementation 'com.squareup.moshi:moshi-kotlin:1.9.3'
// Retrofit with Moshi Converter
implementation 'com.squareup.retrofit2:converter-moshi:2.9.0'
I forgot to pass in the custom Moshi json converter adapter factory object to the moshi converter factory constructor.
private val moshi = Moshi.Builder() // adapter
.add(KotlinJsonAdapterFactory())
.build()
private val retrofit = Retrofit.Builder()
.addConverterFactory(MoshiConverterFactory.create()) // <- missing moshi json adapter insance
.baseUrl(BASE_URL)
.build()
Fix: .addConverterFactory(MoshiConverterFactory.create(moshi))
This may help someone
In my case mistakenly I wrote SerializedName like this
#SerializedName("name","time")
String name,time;
It should be
#SerializedName("name")
String name;
#SerializedName("time")
String time;
In my case, the problem was that my SUPERCLASS model had this field defined in it. Very stupid, I know....
In build.gradle changing
minifyEnabled true
to
minifyEnabled false
has solved my problem.
Hey i was going through the same issue today took me a whole day to find a solution but this is the solution i found finally.
Am using Dagger in my code and i needed to implement the Gson converter in my retrofit instance.
so this was my code before
#Provides
#Singleton
Retrofit providesRetrofit(Application application,OkHttpClient client) {
String SERVER_URL=URL;
Retrofit.Builder builder = new Retrofit.Builder();
builder.baseUrl(SERVER_URL);
return builder
.client(client)
.build();
}
this was what i ended up with
#Provides
#Singleton
Retrofit providesRetrofit(Application application,OkHttpClient client, Gson gson) {
String SERVER_URL=URL;
Retrofit.Builder builder = new Retrofit.Builder();
builder.baseUrl(SERVER_URL);
return builder
.client(client)
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
}
notice how there is no converter in the first example and the addition if you haven't instantiated Gson you add it like this
#Provides
#Singleton
Gson provideGson() {
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES);
return gsonBuilder.create();
}
and ensure you have included it in the method call for retrofit.
once again hope this helps some one like me.
In my case using kotlinx.serialization, the same exception was raised by retrofit,
it was due to the missing #Serializable annotation.
#Serializable
data class MyClass(
val id: String
)
In my case I was missing the Serialization attribute.
I had to add #kotlinx.serialization.Serializable before every data class:
#kotlinx.serialization.Serializable
data class RadioSearchPodcastDto(
val playables: List<Playable>,
val searchTag: SearchTag,
val totalCount: Int
)
Retrofit interface:
interface PodcastRadioApi {
#GET("/podcasts/search")
suspend fun getPodcastBySearch(#Query("query") query: String,
#Query("count") count: Int,
#Query("offset") offset: Int,
#Query("partner") partner: String): RadioSearchPodcastDto
}
With every, I mean the main class and all sub-classes (Playable, SearchTag, ...)
In my cas i had to change ConverterFactory from moshi to Gson
and it woked will.
.addConverterFactory(GsonConverterFactory.create())
in my case, I am using Moshi with Retrofit and my mistake was :
I did not define a body for object that include in Response class service.
for example:
#JsonSerializable
data class Balance(
#field:Json(name = "balance") var balance: Double,
#field:Json(name = "currency") var currency: Currency
and the Currency class was emty. so I complete it and the problem fixed!
Related
Im migrating from using Volley to Retrofit, I already have gson class that I used before for converting JSONObject reponse to a object that implements gson annotations. When I'm trying to make http get request using retrofit but then my app crashes with this error :
Unable to start activity ComponentInfo{com.lightbulb.pawesome/com.example.sample.retrofit.SampleActivity}: java.lang.IllegalArgumentException: Unable to create converter for class com.lightbulb.pawesome.model.Pet
for method GitHubService.getResponse
Im following the guide in retrofit site and I come up with these implementations :
This is my activity where I am trying to execute the retro http request:
public class SampleActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sample);
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("**sample base url here**")
.build();
GitHubService service = retrofit.create(GitHubService.class);
Call<Pet> callPet = service.getResponse("41", "40");
callPet.enqueue(new Callback<Pet>() {
#Override
public void onResponse(Response<Pet> response) {
Log.i("Response", response.toString());
}
#Override
public void onFailure(Throwable t) {
Log.i("Failure", t.toString());
}
});
try{
callPet.execute();
} catch (IOException e){
e.printStackTrace();
}
}
}
My interface which turned to be my API
public interface GitHubService {
#GET("/ **sample here** /{petId}/{otherPet}")
Call<Pet> getResponse(#Path("petId") String userId, #Path("otherPet") String otherPet);
}
And finally the Pet class which should be the reponse:
public class Pet implements Parcelable {
public static final String ACTIVE = "1";
public static final String NOT_ACTIVE = "0";
#SerializedName("is_active")
#Expose
private String isActive;
#SerializedName("pet_id")
#Expose
private String petId;
#Expose
private String name;
#Expose
private String gender;
#Expose
private String age;
#Expose
private String breed;
#SerializedName("profile_picture")
#Expose
private String profilePicture;
#SerializedName("confirmation_status")
#Expose
private String confirmationStatus;
/**
*
* #return
* The confirmationStatus
*/
public String getConfirmationStatus() {
return confirmationStatus;
}
/**
*
* #param confirmationStatus
* The confirmation_status
*/
public void setConfirmationStatus(String confirmationStatus) {
this.confirmationStatus = confirmationStatus;
}
/**
*
* #return
* The isActive
*/
public String getIsActive() {
return isActive;
}
/**
*
* #param isActive
* The is_active
*/
public void setIsActive(String isActive) {
this.isActive = isActive;
}
/**
*
* #return
* The petId
*/
public String getPetId() {
return petId;
}
/**
*
* #param petId
* The pet_id
*/
public void setPetId(String petId) {
this.petId = petId;
}
/**
*
* #return
* The name
*/
public String getName() {
return name;
}
/**
*
* #param name
* The name
*/
public void setName(String name) {
this.name = name;
}
/**
*
* #return
* The gender
*/
public String getGender() {
return gender;
}
/**
*
* #param gender
* The gender
*/
public void setGender(String gender) {
this.gender = gender;
}
/**
*
* #return
* The age
*/
public String getAge() {
return age;
}
/**
*
* #param age
* The age
*/
public void setAge(String age) {
this.age = age;
}
/**
*
* #return
* The breed
*/
public String getBreed() {
return breed;
}
/**
*
* #param breed
* The breed
*/
public void setBreed(String breed) {
this.breed = breed;
}
/**
*
* #return
* The profilePicture
*/
public String getProfilePicture() {
return profilePicture;
}
/**
*
* #param profilePicture
* The profile_picture
*/
public void setProfilePicture(String profilePicture) {
this.profilePicture = profilePicture;
}
protected Pet(Parcel in) {
isActive = in.readString();
petId = in.readString();
name = in.readString();
gender = in.readString();
age = in.readString();
breed = in.readString();
profilePicture = in.readString();
}
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(isActive);
dest.writeString(petId);
dest.writeString(name);
dest.writeString(gender);
dest.writeString(age);
dest.writeString(breed);
dest.writeString(profilePicture);
}
#SuppressWarnings("unused")
public static final Parcelable.Creator<Pet> CREATOR = new Parcelable.Creator<Pet>() {
#Override
public Pet createFromParcel(Parcel in) {
return new Pet(in);
}
#Override
public Pet[] newArray(int size) {
return new Pet[size];
}
};
}
If anyone ever comes across this in the future because you are trying to define your own custom converter factory and are getting this error, it can also be caused by having multiple variables in a class with a misspelled or the same serialized name. IE:
public class foo {
#SerializedName("name")
String firstName;
#SerializedName("name")
String lastName;
}
Having serialized names defined twice (likely by mistake) will also throw this exact same error.
Update: Keep in mind that this logic also holds true via inheritance. If you extend to a parent class with an object that has the same Serialized name as you do in the sub-class, it will cause this same problem.
Prior to 2.0.0, the default converter was a gson converter, but in 2.0.0 and later the default converter is ResponseBody. From the docs:
By default, Retrofit can only deserialize HTTP bodies into OkHttp's
ResponseBody type and it can only accept its RequestBody type for
#Body.
In 2.0.0+, you need to explicitly specify you want a Gson converter:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("**sample base url here**")
.addConverterFactory(GsonConverterFactory.create())
.build();
You will also need to add the following dependency to your gradle file:
compile 'com.squareup.retrofit2:converter-gson:2.1.0'
Use the same version for the converter as you do for your retrofit. The above matches this retrofit dependency:
compile ('com.squareup.retrofit2:retrofit:2.1.0')
Also, note as of writing this, the retrofit docs are not completely updated, which is why that example got you into trouble. From the docs:
Note: This site is still in the process of being expanded for the new 2.0 APIs.
just make sure that you are not using the same serialize name twice
#SerializedName("name") val name: String
#SerializedName("name") val firstName: String
just remove one of them
Based on top comment I updated my imports
implementation 'com.squareup.retrofit2:retrofit:2.1.0'
implementation 'com.squareup.retrofit2:converter-gson:2.1.0'
I've used http://www.jsonschema2pojo.org/ in order to create pojo's from Spotify json results and making sure to specify Gson format.
These days there are Android Studio plugins which can create the pojo's or Kotlin data models for you. One great option for mac is Quicktype.
https://itunes.apple.com/us/app/paste-json-as-code-quicktype/id1330801220
In my case, I had a TextView object inside my modal class and GSON did not know how to serialize it. Marking it as 'transient' solved the issue.
#Silmarilos's post helped me solve this. In my case, it was that I used "id" as a serialized name, like this:
#SerializedName("id")
var node_id: String? = null
and I changed it to
#SerializedName("node_id")
var node_id: String? = null
All working now. I forgot that 'id' is a default attribute.
In my case, it was due to trying to take a List being returned by my service into an ArrayList. So what I had was:
#Json(name = "items")
private ArrayList<ItemModel> items;
when I should've had
#Json(name = "items")
private List<ItemModel> items;
Hope this helps someone!
In build.gradle changing
minifyEnabled true
to
minifyEnabled false
has solved my problem.
In my case I was using the Moshi library with Retrofit 2.0, i.e
// Moshi
implementation 'com.squareup.moshi:moshi-kotlin:1.9.3'
// Retrofit with Moshi Converter
implementation 'com.squareup.retrofit2:converter-moshi:2.9.0'
I forgot to pass in the custom Moshi json converter adapter factory object to the moshi converter factory constructor.
private val moshi = Moshi.Builder() // adapter
.add(KotlinJsonAdapterFactory())
.build()
private val retrofit = Retrofit.Builder()
.addConverterFactory(MoshiConverterFactory.create()) // <- missing moshi json adapter insance
.baseUrl(BASE_URL)
.build()
Fix: .addConverterFactory(MoshiConverterFactory.create(moshi))
This may help someone
In my case mistakenly I wrote SerializedName like this
#SerializedName("name","time")
String name,time;
It should be
#SerializedName("name")
String name;
#SerializedName("time")
String time;
In my case, the problem was that my SUPERCLASS model had this field defined in it. Very stupid, I know....
Hey i was going through the same issue today took me a whole day to find a solution but this is the solution i found finally.
Am using Dagger in my code and i needed to implement the Gson converter in my retrofit instance.
so this was my code before
#Provides
#Singleton
Retrofit providesRetrofit(Application application,OkHttpClient client) {
String SERVER_URL=URL;
Retrofit.Builder builder = new Retrofit.Builder();
builder.baseUrl(SERVER_URL);
return builder
.client(client)
.build();
}
this was what i ended up with
#Provides
#Singleton
Retrofit providesRetrofit(Application application,OkHttpClient client, Gson gson) {
String SERVER_URL=URL;
Retrofit.Builder builder = new Retrofit.Builder();
builder.baseUrl(SERVER_URL);
return builder
.client(client)
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
}
notice how there is no converter in the first example and the addition if you haven't instantiated Gson you add it like this
#Provides
#Singleton
Gson provideGson() {
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES);
return gsonBuilder.create();
}
and ensure you have included it in the method call for retrofit.
once again hope this helps some one like me.
In my case using kotlinx.serialization, the same exception was raised by retrofit,
it was due to the missing #Serializable annotation.
#Serializable
data class MyClass(
val id: String
)
In my case I was missing the Serialization attribute.
I had to add #kotlinx.serialization.Serializable before every data class:
#kotlinx.serialization.Serializable
data class RadioSearchPodcastDto(
val playables: List<Playable>,
val searchTag: SearchTag,
val totalCount: Int
)
Retrofit interface:
interface PodcastRadioApi {
#GET("/podcasts/search")
suspend fun getPodcastBySearch(#Query("query") query: String,
#Query("count") count: Int,
#Query("offset") offset: Int,
#Query("partner") partner: String): RadioSearchPodcastDto
}
With every, I mean the main class and all sub-classes (Playable, SearchTag, ...)
In my cas i had to change ConverterFactory from moshi to Gson
and it woked will.
.addConverterFactory(GsonConverterFactory.create())
in my case, I am using Moshi with Retrofit and my mistake was :
I did not define a body for object that include in Response class service.
for example:
#JsonSerializable
data class Balance(
#field:Json(name = "balance") var balance: Double,
#field:Json(name = "currency") var currency: Currency
and the Currency class was emty. so I complete it and the problem fixed!
I don't even know if this is a valid question but I am having a hard time converting the API result to POJO since some key are dynamic.
{
"data": [{
"something_edit": true
},
{
"test_null": false
}
],
"success": true
}
As you can see the key inside data are dynamic. I tried using jsonschema2pojo or other converter but it is declaring a named variable which is not a good result. BTW I am using retrofit and GSON library
EDIT:
So here is the flow, so the keys are the ones I asked on the API. So for Example I asked something_edit1, something_edit2 and something_edit3. The data result will be.
{
"data": [{
"something_edit1": true
}, {
"something_edit2": false
},
{
"something_edit3": false
}
],
"success": true
}
You can use Json Object or Generics for your condition.
Using Json Object you can check, if key is exist in your json.
if(yourJsonObject.hasOwnProperty('key_name')){
// do your work here
}
Using Generic you have to check, if your Pojo have instance of the
Pojo.
if(YourMainPOJO instanceOf YourChildPojo){
// do your work here
}
Try to view only Generic part in this link.
It's hard to determine or you have to declare all the possible fields in your POJO or write your own json parser extending the Gson Parser or use a JsonElement which can be converted into json array, object and primitive, based on that result you can convert back to some specific pojo.
/**
* this will convert the whole json into map which you can use to determine the json elements
*
* #param json
*/
private void getModelFromJson(JsonObject json) {
Gson gson = new Gson();
Map<String, JsonElement> jsonElementMap = gson.fromJson(json.toString(), new TypeToken<Map<String, JsonElement>>() {
}.getType());
for (Map.Entry<String, JsonElement> jsonElementEntry : jsonElementMap.entrySet()) {
if (jsonElementEntry.getValue().isJsonPrimitive()) {
//json primitives are data types, do something
//get json boolean
//you can here also check if json element has some json object or json array or primitives based on that
//you can convert this to something else after comparison
if (true) {
InterestModelResponse response = gson.fromJson(jsonElementEntry.getValue().getAsJsonObject().toString(), InterestModelResponse.class);
//use your dynamic converted model
}
} else {
//do something else
}
}
}
2 Years ago we did a project in which we had to handle notifications data with different type of objects in same array we handle that while using retrofit
this is our retrofit Creator class
class Creator {
public static FullTeamService newFullTeamService() {
final HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
final OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(interceptor)
.build();
final Retrofit retrofit = new Retrofit.Builder()
.baseUrl(FullTeamService.HOST)
.client(client)
.addConverterFactory(GsonConverterFactory.create(GsonUtils.get()))
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
return retrofit.create(FullTeamService.class);
}
}
and GsonUtils.java is:
public class GsonUtils {
private static final Gson sGson = new GsonBuilder()
.setDateFormat("yyyy-MM-dd'T'HH:mm:ss")
.registerTypeAdapter(NotificationObject.class, new NotificationDeserializer())
.create();
private GsonUtils() {}
public static Gson get() {
return sGson;
}
}
NotificationObject is something like:
public class NotificationObject {
#SerializedName("ID")
#Expose
private long ID;
#SerializedName("type")
#Expose
private Type type;
#SerializedName("DataObject")
#Expose
private NotificationDataObject dataObject;
public void setDataObject(NotificationDataObject newsFields) {
dataObject = newsFields;
}
#SuppressWarnings("unchecked")
public <T> T getDataObject() {
return (T) dataObject;
}
public enum Type {
#SerializedName("0")
CHAT_MESSAGE,
#SerializedName("10")
GAME_APPLICATION,
#SerializedName("20")
GAME_APPLICATION_RESPONSE,
#SerializedName("30")
GAME_INVITE....
}
}
NotificationDataObject as new class is like:
public class NotificationDataObject {}
and finally NotificationDeserializer is like:
public class NotificationDeserializer implements JsonDeserializer<NotificationObject> {
#Override
public NotificationObject deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
final JsonObject itemBean = json.getAsJsonObject();
final NotificationObject object = GsonUtils.getSimpleGson().fromJson(itemBean, NotificationObject.class);
switch (object.getType()) {
case CHAT_MESSAGE:
break;
case GAME_APPLICATION:
object.setDataObject(GsonUtils.get().fromJson(itemBean.get("DataObject").getAsJsonObject(),
GameApplicationNotification.class));
break;
case GAME_APPLICATION_RESPONSE:
object.setDataObject(GsonUtils.get().fromJson(itemBean.get("DataObject").getAsJsonObject(),
GameApplicationResponseNotification.class));
break;
case GAME_INVITE:
object.setDataObject(GsonUtils.get().fromJson(itemBean.get("DataObject").getAsJsonObject(),
GameInviteNotification.class));
break;
}
return object;
}
}
Happy coding ...!
any query will be appreciated...
Below I prepared some of the code I use when I execute query to server.
Response from the server returns list with books in json format. One of the fields params contains list in json array.
How could I force Moshi to treat this field as a raw string?
Here is an explanation of my code.
This is a pseudo-class from where I call instance of Retrofit.
public final class RestApi {
private static final String API_ENDPOINT_KEY = "...";
private final MyApi myApi;
private RestApi() {
OkHttpClient.Builder builder = new OkHttpClient.Builder();
OkHttpClient client = builder.build();
final Retrofit retrofit = (new Retrofit.Builder())
.baseUrl(API_ENDPOINT_KEY)
.client(client)
.addConverterFactory(MoshiConverterFactory.create())
.build();
myApi = retrofit.create(MyApi.class);
}
public static RestApi getInstance() {
return RestApiGazeta.InstanceHolder.INSTASNCE;
}
private static final class InstanceHolder {
private static final RestApi INSTASNCE = new RestApi();
}
}
This is the Book class with prepared fields for Moshi.
#Generated("com.robohorse.robopojogenerator")
public class Book{
#Json(name = "pages")
private int pages;
#Json(name = "params")
private Params params;
}
This is the query for Retrofit for querying list of books.
#GET("android/getBooks.json")
Call<List<Book>> getBooks(
#Query("section") String section
);
What I have tried was I changed return type in Book class type Params to String but I was returning exception about Expected a string but was BEGIN_ARRAY at path $[0].params.
Okay, so I've read Moshi documentation and it seems to be easy to convert any field to json string. I don't know how to parse it on the fly but here is the solution how to do it after You get ready (in my case) Book object.
Moshi moshi = new Moshi.Builder().build();
JsonAdapter<Params> jsonAdapter = moshi.adapter(Params.class);
String json = jsonAdapter.toJson(body.getParams());
That's it.
The JSON data returned from the server can either return an object, or if that object is null, it returns an empty string ("").
My problem is that my DTO expects an object, but it sees a string and crashes.
PersonDTO
data class PersonDto(
#SerializedName("firstName") val first: String,
#SerializedName("lastName") val last: String,
#SerializedName("favorites") val favorites: FavoriteDto,
)
FavoriteDto
class FavoriteDto(
#SerializedName("color") val color: String,
#SerializedName("number") val number: Int
)
Different responses from server
"person" : {
"firstName": "Steve",
"lastName" : "Johnson",
"favorites" : {
"color": "Purple",
"number": 25
}
}
...
"person" : {
"firstName": "Steve",
"lastName" : "Johnson",
"favorites" : ""
}
I've heard that I might need a custom GSON deserializer, but I've never done anything with GSON other than the out of the box stuff - so I was hoping for a nudge in the right direction.
Thanks!
Easiest hack is that you can add extra fields in the class with the same serialised name but with a String data type. Like this -
data class PersonDto(
#SerializedName("firstName") val first: String,
#SerializedName("lastName") val last: String,
#SerializedName("favorites") val favorites: FavoriteDto,
#SerializedName("favorites") val favoritesStr: String,
)
As there is nothing in Gson as "Required" field, you'll just get a null in your deserialized object if something is missing in the JSON. So, if there is an empty string the FavoriteDto object will be null and not null otherwise.
EDIT
I'm adding some Java code that I have written earlier. This might help:
public class PersonDto {
private FavoriteDto favorites;
private String favoritesStr;
public FavoriteDto getResponseDataObject() {
return favorites;
}
public void setResponseDataObject(FavoriteDto favorites) {
this.favorites = favorites;
}
public String getResponseDataString() {
return favoritesStr;
}
public void setResponseDataString(String favoritesStr) {
this.favoritesStr = favoritesStr;
}
Defining the Deserializar:
public static class ArrayObjectDualityDeserializer implements JsonDeserializer<PersonDto> {
public PersonDto deserialize(JsonElement json, Type typeOfT,
JsonDeserializationContext context) throws JsonParseException {
PersonDto response = new PersonDto();
JsonObject object = json.getAsJsonObject();
if(object.get("favorites").isJsonArray()) {
} else if(object.get("favorites").isJsonObject()) {
try {
FavoriteDto dtoObject = gson.fromJson(object.get("favorites"), FavoriteDto.class);
response.setResponseDataObject(dtoObject);
} catch (JsonSyntaxException e) {
DebugLogger.e("Error " + e);
}
} else if (object.get("favorites").isJsonNull()) {
} else {
response.setResponseDataString(object.get("favorites").getAsString());
}
}
}
And:
public static Gson gson = new GsonBuilder()
.registerTypeAdapter(PersonDto.class, new ArrayObjectDualityDeserializer())
.create();
Lastly:
private static Retrofit retrofit = null;
private static OkHttpClient.Builder httpClient = null;
private static OkHttpClient session_client = null;
httpClient = new OkHttpClient.Builder();
httpClient.addInterceptor(new SessionOkHttpInterceptor());
session_client = httpClient.build();
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.client(session_client)
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
The answer of #Subrato M. is partially correct. In order to work, you should remove #SerializedName("favorites") annotation from both fields in order to work. In this case, Gson won't throw an error when deserialize multiple json fields named favorites because you don't have any field annotated with that name (also don't name fileds the same as expected field name beucase Gson will try to deserialize). Use the custom deserializer to check if is an object or a string.
Nowadays isn't necessary the #Subrato M's hack, you don't get the right json cause you are missing the data keyword before class FavoriteDto and that is why doesn't exists the get and set methods
I am currently looking into implementing the Retrofit API (after using Volley) into my app and I have some questions that I cannot seem to find answers to anywhere else so I will ask here.
How do I go about downloading images using Retrofit API? I am asking this because Volley has the ImageLoader class and NetworkedImageView etc. and was wondering if Retrofit has something similar?
I read that using the RequestIntercepter, it can add a header to every request. How is this different from just adding a static (or dynamic) header (#Header) in the interface's abstract method
How does Retrofit deal with nested JSON objects? I read it uses GSON to convert the JSON into java objects but the POJO class must have the same field names.
Thank you for reading
For your first doubt:
Retrofit no have feature to manager image as Volley and UIL has. The same company that developer Retrofit too have a nice lib to manager images called Picasso.
For your second doubt:
Headers that need to be added to every request can be specified using
a RequestInterceptor. The following code creates a RequestInterceptor
that will add a User-Agent header to every request.
for instance:
I developed a client to Parse.com and to all request I need set my keys in the header: see here Android-Retrofit-Example
public class RestClient {
private static RestClient mRestClient = null;
private static RestAdapter restAdapter;
private static RequestInterceptor requestInterceptor;
public static RestClient getInstance(){
if(mRestClient == null ){
mRestClient = new RestClient();
setup();
}
return mRestClient;
}
private static void setup(){
requestInterceptor = new RequestInterceptor() {
#Override
public void intercept(RequestFacade request) {
request.addHeader("X-Parse-Application-Id", "");
request.addHeader("X-Parse-REST-API-Key", "");
}
};
restAdapter = new RestAdapter.Builder()
.setEndpoint(" https://api.parse.com/1/")
.setLogLevel(RestAdapter.LogLevel.FULL)
.setRequestInterceptor(requestInterceptor)
.build();
}
public RestAdapter getRestAdapter(){
return restAdapter;
}
}
The last :
JSON CONVERSION
Retrofit uses Gson by default to convert HTTP bodies to and from JSON.
If you want to specify behavior that is different from Gson's defaults
(e.g. naming policies, date formats, custom types), provide a new Gson
instance with your desired behavior when building a RestAdapter. Refer
to the Gson documentation for more details on customization.
To get this :
{
results: [3]
{
createdAt: "2015-03-07T20:43:44.107Z"
objectId: "osCJ8PI65r"
updatedAt: "2015-03-08T00:45:37.539Z"
username: "Test 2"
},
{
createdAt: "2015-03-07T21:42:38.591Z"
objectId: "tkIi6Ll1Os"
updatedAt: "2015-03-07T21:42:38.591Z"
username: "Test 2"
},
{
createdAt: "2015-03-08T01:13:21.188Z"
objectId: "Cz0HqiYpwl"
updatedAt: "2015-03-08T04:21:18.069Z"
username: "Test 3"
}
}
Pojo :
public class User {
private String objectId;
private String username;
private String createdAt;
private String updatedAt;
//gerate getters and setters
}
public class WrappeUser {
#SerializedName(value="results")
List<User> results;
public List<User> getResults() {
return results;
}
public void setResults(List<User> results) {
this.results = results;
}
}