I have a RealmObject like this :
public class Konsumen extends RealmObject {
@SerializedName("id_konsumen") @PrimaryKey private String idKonsumen;
@SerializedName("nama_konsumen") private String namaKonsumen;
private String telepon;
private String alamat;
private String kota;
private String keterangan;
private boolean modified;
private int deleted;
public Konsumen() {}
// Getter and setter...
}
When I tried to send the object to my server using Retrofit, it failed with error like this :
05-15 20:09:32.423 19331-19596/com.yulia.admin D/WEB-SERVICE﹕ ---> HTTP POST http://yulia-radhi.rhcloud.com/api/konsumen
05-15 20:09:32.423 19331-19596/com.yulia.admin D/WEB-SERVICE﹕ Content-Type: application/json; charset=UTF-8
05-15 20:09:32.423 19331-19596/com.yulia.admin D/WEB-SERVICE﹕ Content-Length: 32
05-15 20:09:32.423 19331-19596/com.yulia.admin D/WEB-SERVICE﹕ [{"modified":false,"deleted":0}]
05-15 20:09:32.423 19331-19596/com.yulia.admin D/WEB-SERVICE﹕ ---> END HTTP (32-byte body)
05-15 20:09:33.123 19331-19596/com.yulia.admin D/WEB-SERVICE﹕ <--- HTTP 500 http://yulia-radhi.rhcloud.com/api/konsumen (696ms)
05-15 20:09:33.123 19331-19596/com.yulia.admin D/WEB-SERVICE﹕ : HTTP/1.1 500 Internal Server Error
From this logcat, I found out that the error is happened because GSON unable to convert the object Konsumen.java entirely, and only able to convert field modified and deleted into JSON. Thats why when Retrofit send this JSON to my server, it caused Internal Server Error, because my server need at least the idKonsumen and namaKonsumen.
After looking around, I've found a StackOverflow question with same problem. There it said to convert RealmObject to JSON I have to create my own serializer. So, I create a KonsumenSerializer like this :
public class KonsumenSerializer implements JsonSerializer<Konsumen> {
@Override
public JsonElement serialize(Konsumen src, Type typeOfSrc, JsonSerializationContext context) {
JsonObject object = new JsonObject();
object.addProperty("id_konsumen", src.getIdKonsumen());
object.addProperty("nama_konsumen", src.getNamaKonsumen());
object.addProperty("telepon", src.getTelepon());
object.addProperty("alamat", src.getAlamat());
object.addProperty("kota", src.getKota());
object.addProperty("keterangan", src.getKeterangan());
object.addProperty("deleted", src.getDeleted());
return object;
}
}
After that, I register my serializer in my RestClient :
public class RestClient {
private static final String API_URL = "http://yulia-radhi.rhcloud.com";
public static ApiService getService() {
ExclusionStrategy exclusionStrategy = new ExclusionStrategy() {
@Override
public boolean shouldSkipField(FieldAttributes f) {
return f.getDeclaringClass().equals(RealmObject.class);
}
@Override
public boolean shouldSkipClass(Class<?> clazz) {
return false;
}
};
Gson gson = new GsonBuilder()
.setExclusionStrategies(exclusionStrategy)
.registerTypeAdapter(Konsumen.class, new KonsumenSerializer())
.create();
return new RestAdapter.Builder()
.setEndpoint(API_URL)
.setConverter(new GsonConverter(gson))
.setLogLevel(RestAdapter.LogLevel.FULL)
.setLog(new AndroidLog("WEB-SERVICE"))
.build()
.create(ApiService.class);
}
}
After that, I tried again to send Konsumen data to server using Retrofit, but it still failed with similar error like before.
So, my question is, how can I convert the RealmObject into JSON properly ?
On issue #812, mpost said :
Creating a normal JsonSerializer and registering it as a type adapter is not straight forward. Since you are actually working against the Proxy object, gson tries to match the adapter against the proxy which is not found. So you either register the serializer against the proxy or you use a different library.
If so, how can I do that, because I can't find the proxy class for my Konsumen object.
What I've done right now is creating a helper class that translate my Konsumen object to ordinary POJO like this :
public class KonsumenHelper {
String id_konsumen;
String nama_konsumen;
String telepon;
String alamat;
String kota;
int deleted;
public KonsumenHelper(Konsumen konsumen) {
// Set local variable value from konsumen
}
}
The advantage of this method is I don't have to create a custom serializer, but I don't really like this method because it make two object with same value. It got even worse when I have to send many Konsumen data from local database to server.
Hi guys, here is the update of my problem.
After looking around, I've found out that proxy class for RealmObject is located in io.realm.<ObjectName>RealmProxy. So, for my Konsumen object, the proxy class is located in io.realm.KonsumenRealmProxy.
With this new info, I change my RestClient into like this :
public class RestClient {
private static final String API_URL = "http://yulia-radhi.rhcloud.com";
public static ApiService getService() {
ExclusionStrategy exclusionStrategy = new ExclusionStrategy() {
@Override
public boolean shouldSkipField(FieldAttributes f) {
return f.getDeclaringClass().equals(RealmObject.class);
}
@Override
public boolean shouldSkipClass(Class<?> clazz) {
return false;
}
};
Gson gson = null;
try {
gson = new GsonBuilder()
.setExclusionStrategies(exclusionStrategy)
.registerTypeAdapter(Class.forName("io.realm.KonsumenRealmProxy"), new KonsumenSerializer())
.create();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return new RestAdapter.Builder()
.setEndpoint(API_URL)
.setConverter(new GsonConverter(gson))
.setLogLevel(RestAdapter.LogLevel.FULL)
.setLog(new AndroidLog("WEB-SERVICE"))
.build()
.create(ApiService.class);
}
}
After that, I tried to send my Konsumen to server. It still failed with following error :
05-16 09:49:10.060 12782-12838/com.yulia.admin D/WEB-SERVICE﹕ ---> HTTP POST http://yulia-radhi.rhcloud.com/api/konsumen
05-16 09:49:10.060 12782-12838/com.yulia.admin D/WEB-SERVICE﹕ Content-Type: application/json; charset=UTF-8
05-16 09:49:10.060 12782-12838/com.yulia.admin D/WEB-SERVICE﹕ Content-Length: 185
05-16 09:49:10.090 12782-12838/com.yulia.admin D/WEB-SERVICE﹕ [{"id_konsumen":"0100000002","nama_konsumen":"Setyo Makmur Santoso","telepon":"081353273756","alamat":"Jl Junjung Buih No 132 A","kota":"Palangka Raya","keterangan":"Mhsx","deleted":0}]
05-16 09:49:10.090 12782-12838/com.yulia.admin D/WEB-SERVICE﹕ ---> END HTTP (185-byte body)
05-16 09:49:25.325 12782-12838/com.yulia.admin D/WEB-SERVICE﹕ ---- ERROR http://yulia-radhi.rhcloud.com/api/konsumen
05-16 09:49:25.330 12782-12838/com.yulia.admin D/WEB-SERVICE﹕ java.net.SocketTimeoutException: failed to connect to yulia-radhi.rhcloud.com/54.159.167.45 (port 80) after 15000ms
at libcore.io.IoBridge.connectErrno(IoBridge.java:159)
at libcore.io.IoBridge.connect(IoBridge.java:112)
at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:192)
at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:459)
at java.net.Socket.connect(Socket.java:843)
at com.android.okhttp.internal.Platform.connectSocket(Platform.java:131)
at com.android.okhttp.Connection.connect(Connection.java:101)
at com.android.okhttp.internal.http.HttpEngine.connect(HttpEngine.java:294)
at com.android.okhttp.internal.http.HttpEngine.sendSocketRequest(HttpEngine.java:255)
at com.android.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:206)
at com.android.okhttp.internal.http.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:345)
at com.android.okhttp.internal.http.HttpURLConnectionImpl.connect(HttpURLConnectionImpl.java:89)
at com.android.okhttp.internal.http.HttpURLConnectionImpl.getOutputStream(HttpURLConnectionImpl.java:197)
at retrofit.client.UrlConnectionClient.prepareRequest(UrlConnectionClient.java:68)
at retrofit.client.UrlConnectionClient.execute(UrlConnectionClient.java:37)
at retrofit.RestAdapter$RestHandler.invokeRequest(RestAdapter.java:326)
at retrofit.RestAdapter$RestHandler.invoke(RestAdapter.java:240)
at $Proxy0.upsertKonsumen(Native Method)
at com.yulia.admin.fragment.konsumen.ListKonsumen$SinkronisasiKonsumen.doInBackground(ListKonsumen.java:171)
at com.yulia.admin.fragment.konsumen.ListKonsumen$SinkronisasiKonsumen.doInBackground(ListKonsumen.java:140)
at android.os.AsyncTask$2.call(AsyncTask.java:288)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:231)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
at java.lang.Thread.run(Thread.java:841)
05-16 09:49:25.330 12782-12838/com.yulia.admin D/WEB-SERVICE﹕ ---- END ERROR
From this log, I found that the serializer now able to convert my Konsumen object to JSON successfully (compared to the previous where it only able to convert modifed and deleted field). But somehow or another it crashed because the application is unable to connect to my server after 15 s.
Can somebody explain to me whats wrong with my code ?
Thanks
Nevermind guys, it works. My internet connection was unstable, that's why it failed when connecting to server.
To convert RealmObject into JSON using Gson library, we have to create our own Serializer and register it to the proxy class of our model, NOT to the model itself. So, for my Konsumen model, I have to register my KonsumenSerializer to proxy class io.realm.KonsumenRealmProxy. I hope in near future Realm will support converting RealmObject to JSON internally, so we don't have to create ourr custom serializer.
Model Konsumen :
public class Konsumen extends RealmObject {
@SerializedName("id_konsumen") @PrimaryKey private String idKonsumen;
@SerializedName("nama_konsumen") private String namaKonsumen;
private String telepon;
private String alamat;
private String kota;
private String keterangan;
private boolean modified;
private int deleted;
public Konsumen() {}
// Getter and setter...
}
Serializer for my Konsumen :
public class KonsumenSerializer implements JsonSerializer<Konsumen> {
@Override
public JsonElement serialize(Konsumen src, Type typeOfSrc, JsonSerializationContext context) {
JsonObject object = new JsonObject();
object.addProperty("id_konsumen", src.getIdKonsumen());
object.addProperty("nama_konsumen", src.getNamaKonsumen());
object.addProperty("telepon", src.getTelepon());
object.addProperty("alamat", src.getAlamat());
object.addProperty("kota", src.getKota());
object.addProperty("keterangan", src.getKeterangan());
object.addProperty("deleted", src.getDeleted());
return object;
}
}
And last, my RestClient :
public class RestClient {
private static final String API_URL = "http://yulia-radhi.rhcloud.com";
public static ApiService getService() {
// Create Gson builder
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.setExclusionStrategies(new ExclusionStrategy() {
@Override
public boolean shouldSkipField(FieldAttributes f) {
return f.getDeclaringClass().equals(RealmObject.class);
}
@Override
public boolean shouldSkipClass(Class<?> clazz) {
return false;
}
});
// Register adapter to builder
try {
gsonBuilder.registerTypeAdapter(Class.forName("io.realm.KonsumenRealmProxy"), new KonsumenSerializer());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
// Create gson
Gson gson = gsonBuilder.create();
// Set Gson as converter for Retrofit
return new RestAdapter.Builder()
.setEndpoint(API_URL)
.setConverter(new GsonConverter(gson))
.setLogLevel(RestAdapter.LogLevel.FULL)
.setLog(new AndroidLog("WEB-SERVICE"))
.build()
.create(ApiService.class);
}
}
Hi @RadhiFadlillah
Great to hear you found the solution yourself. You are right that the current way of serializing RealmObjects using GSON is not straight forward, and that the very least we should document this in our docs
This should definitely be in the docs. From reading everything in the realm-java docs I had assumed that gson serialization would just work as long as I set the provided ExclusionStrategy. I've spent the whole afternoon trying to figure out why my model wasn't serializing properly, and finally find out here that it's basically broken (but at least there's a workaround).
+1
@RadhiFadlillah @cmelchior Hi there, I noticed that quite often you want to query realm and convert the results into Json. In these instances, you will need to register type adapters for your models.
The most complete solution is to register type adapters for both your models that extend from RealmObject and the proxy classes generated for them by realm.
Here's an update for the gist referenced in the documentation: https://gist.github.com/Retistic/9afece43e3d01f017f8b
Very interesting. I had the same situation and everything was solved just like your solution. Now suddenly I register a new serializer (the third in row) for a new table (new proxy etc) and it is not discovered somehow (serialize method is never being called on the new serializer but only on the old one). The difference is I have a relation in there which earlier was not a problem. I am wondering if anybody else had such issues.
I'm getting a error when gson tries to serialize a RealmProxy object using this method
java.lang.IllegalStateException: Realm access from incorrect thread. Realm objects can only be accessed on the thread they were created.
I guess that's because the realm object was queried on a background thread, but how should I work around this?
@marcio-granzotto-ckl Without knowing the context it is hard to say, but you have 2 options:
a) Query for the object on the correct thread.
b) Use realm.copyFromRealm() to get a in-memory copy you can move across threads.
For anybody that have this problem (realm + gson + retrofit), there's workaround like this:
Country.java:
public class Country extends RealmObject {
@PrimaryKey
private int id;
private String iso1366_2;
private String name;
@Override
public String toString() {
return name;
}
}
ApiService.java:
public interface ApiService {
@POST("retrofit.php")
Call<String> create(@Body String country);
}
ApiClient.java:
public class ApiClient {
private static Retrofit getRetrofitInstance() {
Retrofit retrofit = new Retrofit.Builder()
//.baseUrl(AppConst.BASE_URL)
.baseUrl("http://192.168.1.8/demography/")
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.build();
return retrofit;
}
public static ApiService getService() {
return getRetrofitInstance().create(Api.class);
}
}
MainActivity.java:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Gson gson = new Gson();
Realm realm = RealmController.with(getActivity()).getRealm();
Country country = realm.where(Country.class).findFirst();
country = realm.copyFromRealm(country); // One of most important factor
Call<String> call = service.createPatient(gson.toJson(currency));
call.enqueue(new Callback<String>() {
@Override
public void onResponse(Call<String> call, Response<String> response) {
System.out.println("RESPONSE RAW: " + response.raw());
System.out.println("RESPONSE BODY: " + response.body());
}
@Override
public void onFailure(Call<String> call, Throwable t) {
System.out.println("Country data failed to sent with code." + t.toString());
}
});
setContentView(R.layout.activity_main);
}
}
I had the same issue. Came up with this workaround:
1 - Created a generic interface CloneableRealmObject:
public interface CloneableRealmObject<T> {
T cloneRealmObject();
}
2 - Made my realmObjetcs implement the above interface like so:
public class Model extends RealmObject implements CloneableRealmObject<Model> {
@PrimaryKey
long id;
public Model() {
// Empty constructor required by Realm.
}
@Override
public Model cloneRealmObject() {
Model clone = new Model();
clone.id = this.id;
return clone;
}
}
3 - Clone the object before passing to my Retrofit call.
That's it!
The trick is that Gson has no issues dealing with an instance of a RealmObject when the instance is not a managed RealmObject. That's why you wouldn't get the unexpected behavior when working with the cloned object (it's not a managed instance).
Cheers!
@AnixPasBesoin that's because GSON tries to read the fields of the Realm object via reflection, but to obtain the values, you need to use accessor methods - which are automatically applied to all field access in the code via the Realm-transformer, but reflection still sees nulls everywhere
And because GSON is actually a really stupid parser, it doesn't have the option to use accessors instead of fields, and therefore realm.copyFromRealm() on the object before sending it through GSON works
@Zhuinden Great explanation! Thanx a lot!
Realm is such a great tool. However, having to work with this kind of workarounds makes it less attractive.
Hope those incompatibilities will get solved in near future!
@AnixPasBesoin eh, this is an "incompatibility" that GSON would have to solve, and they don't really care since 2010
Ouch. Too bad!
@Zhuinden Thanx a lot for your enlightenment!
Thank you @Zhuinden I was searching for a plausible explanation to copyFromRealm all over.
@shivamsriva31093 you shouldn't use it all over, just when you want to send managed objects through GSON
Okay! Thanks. :)
On Jun 15, 2017 2:00 AM, "Gabor Varadi" notifications@github.com wrote:
@shivamsriva31093 https://github.com/shivamsriva31093 you shouldn't use
it all over, just when you want to send managed objects through GSON—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/realm/realm-java/issues/1127#issuecomment-308548924,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AQer1BPiVEWq97aY6QQVoNLmicJuVfBSks5sEELHgaJpZM4Ebvpb
.
We really appreciate your best effort to solve this error!
Thank you very much!!!
@RadhiFadlillah
Most helpful comment
Nevermind guys, it works. My internet connection was unstable, that's why it failed when connecting to server.
Conclusion
To convert RealmObject into JSON using Gson library, we have to create our own
Serializerand register it to the proxy class of our model, NOT to the model itself. So, for myKonsumenmodel, I have to register myKonsumenSerializerto proxy classio.realm.KonsumenRealmProxy. I hope in near future Realm will support converting RealmObject to JSON internally, so we don't have to create ourr custom serializer.My Final Code
Model
Konsumen:Serializer for my
Konsumen:And last, my
RestClient: