A set of quick notes on how to use Volley with GSON and Realm

I really don’t like Volley.

First I’d like to note that I consider Volley to be obsolete, especially when compared to its most popular alternative, Retrofit— a REST API client that is far easier and much more elegant to use.

(And for image loading, you can always just use Glide.)

So one could consider that even just mentioning Volley is a waste of time. Generally, I’m inclined to agree; but there are still a few oddities I see every now and then on Stack Overflow that imports Gson in the dependencies but then manually parses JSONObject classes returned by Volley, passes it to main thread then runs UI thread transactions for writing data in the database.

This post was written to address this set of problems.

1.) You are supposed to create only 1 instance of the RequestQueue

In Volley, there is this thing called a RequestQueue. When you want to download something, you pass a request in this queue; and this is also where it can be cancelled (if you feel like doing that).

Some people create a new RequestQueuea for every single request they execute. Don’t.

The same goes for the Gson instance, if you have one. You’re not supposed to create a new one per each request.

The same goes for the Retrofit instance, and the ApiServices that you can create using Retrofit. These should all be singletons.

Even the official guide tells you to create only one RequestQueue, and even shows how. If you go this route, using @Provides @Singletonwith Dagger2 is the best bet.

2.) Just because Volley gives you JSONObjectResponse and JSONArrayResponse, doesn’t mean you can’t use a smarter JSON parsing solution

In fact, GSON was created so that you don’t need to parse every single object manually from JSONObject and JSONArray. There are parsers that do this for you, and parse the data directly into POJO classes. The best thing about it is that sites like jsonschema2pojo.org can generate POJO classes from your JSON response for GSON directly.

So instead of parsing everything by hand like it’s 2010, we can create a new Response type, just like it is indicated in the official tutorial, which can handle JSON parsing using GSON!

public class GsonObjectRequest<T> extends Request<T> {
private final Gson gson;
private final Class<T> clazz;
private final Listener<T> listener;
// ...
  public GsonObjectRequest(Gson gson, String url, Class<T> clazz, /* ... */) {
super(Method.GET, url, errorListener);
   this.gson = gson;
this.clazz = clazz;
// ...
}

// ...
  @Override
protected Response<T> parseNetworkResponse(NetworkResponse response) {
try {
String json = new String(response.data,
HttpHeaderParser.parseCharset(response.headers));
return Response.success(
gson.fromJson(json, clazz),
HttpHeaderParser.parseCacheHeaders(response));
} catch (UnsupportedEncodingException e) {
return Response.error(new ParseError(e));
} catch (JsonSyntaxException e) {
return Response.error(new ParseError(e));
}
}
}

Of course, Retrofit’s .addConverterFactory(GsonConverterFactory.create()) making everything magically work is much nicer. But it’s nice to know about it.

3.) Though the Callback is called on the UI thread, you can still execute background thread logic without unnecessary thread jumps

If you’re using Realm, generally you shouldn’t even need to pass the downloaded data through a callback method to the UI thread, because you’d be using a reactive data layer where you’re listening for writes made to the database, so no manual callbacks are needed in order to update the UI.

In fact, what you do want to do is just save the data to Realm on a background thread, on the same thread that downloaded the data!

Instead of going through meaningless hoops like this one that I just got from Stack Overflow:

// this is terrible code
final Handler handler = new Handler(Looper.getMainLooper());
final String id = // ...
downloadData(context, data, false, (newValue) -> {
handler.post(() -> {
mRealm.executeTransactionAsync((realm) -> {
final MyData newData = realm.where(MyData.class)
.equalTo("id", id)
.findFirst();
newData.setValue(newValue);
}
}
}

In this code, we had a downloadData on background thread, a callback, the callback then manually passes to UI thread, then the UI thread’s Realm is used to create another background thread transaction and there the data is modified.

So we had background thread -> UI thread -> background thread -> UI thread for no real reason, especially considering the original sample didn’t even use lambda expressions.

Instead, when using Realm, we can leverage the fact that we don’t need to call back with the actual data (although sometimes it can be helpful, but most of the time it’s not necessary), because we are already listening for changes made to the database.

Therefore, we can just use parseNetworkResponse()a method of our custom request so that we save the data to the Realm there directly, essentially skipping the 3 thread jumps.

public class RealmGsonRequest<T, M extends RealmModel> extends Response<Void> {
...
  @Override
protected Response<Void> parseNetworkResponse(
NetworkResponse response) {
try {
String json = new String(response.data,
HttpHeaderParser.parseCharset(response.headers));
T data = gson.fromJson(json, clazz);
      // write the downloaded data into the Realm on bg thread
try(Realm r = Realm.getDefaultInstance()) {
M model = mapper.toModel(data);
r.executeTransaction((realm) -> {
realm.insertOrUpdate(model);
});
}
      return Response.success(null,
HttpHeaderParser.parseCacheHeaders(response)
);
} // handle errors
...
}

Honestly, this stands even with Retrofit. Using retrofitService.enqueue() and realm.executeTransactionAsync() is just messy. If you do both on a background thread with retrofitService.execute() and realm.executeTransaction(), the code will be much easier to reason about!

Conclusion

I’m not sure how helpful this article really is, because Volley is obsolete, and the more Retrofit takes over, the better it is.

But still, if you can use GSON and you can avoid messy code, then why stick with JSONObjects, JSONArrays and handlers all over the place?

So I guess the takeaway here is that:

  • use jsonschema2pojo to create POJO classes
  • don’t manually parse JSON unless you have no other option
  • please don’t ping-pong between threads if you don’t need to
  • you should consider using change listeners (and other observable structures such as RealmResults, LiveData, BehaviorRelay, Flowable, etc.) over callbacks, especially when you’re working with displaying data from local data source that is sometimes updated from remote
  • why are you still using Volley when Retrofit is better?
One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.