Introduction
Welcome to part two of my series on GraphQL, Android, and RxJava! If you haven’t already, be sure to check out my part one article where I go over the basics of using RxJava with GraphQL on Android. In this article, we’ll go over some more advanced topics, such as handling parallel and dependent requests. Let’s get to it!
Setup
We’re going to continue using the Android app we setup in my last article for the examples here. We’ll continue using the SpaceX GraphQL API for this article as well. Particularly, the rocket
and launchpads
queries. Let’s start off by writing GraphQL queries for these endpoints.
First, the rocket
query:
query RocketQuery($id: ID!) {
rocket(id: $id) {
id
name
description
first_flight
cost_per_launch
}
}
This should be pretty familiar if you’ve read my previous article, the only difference is the $id
parameter we added. That’s used to specify which rocket we want to fetch, and we’ll use it later in our Java code.
Now the launchpads
query:
query LaunchPadsQuery {
launchpads {
vehicles_launched {
id
}
name
}
}
Nothing special here, just some good ole' GraphQL.
With our GraphQL queries written, all that’s left is to make methods for them in our Server class.
First, rocket
:
public static Observable<Response<RocketQuery.Data>> fetchRocket(String id) {
ApolloQueryCall<RocketQuery.Data> call = getApolloClient()
.query(new RocketQuery(id));
return Rx3Apollo.from(call)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.filter((dataResponse -> dataResponse.getData() != null));
}
Again, this is pretty similar to our previous setup. However, notice the id argument in the method definition. We use that to set the $id
argument we defined earlier in our GraphQL query file.
Next, launchpads
:
public static Observable<Response<LaunchPadsQuery.Data>> fetchLaunchPads() {
ApolloQueryCall<LaunchPadsQuery.Data> call = getApolloClient()
.query(new LaunchPadsQuery());
return Rx3Apollo.from(call)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.filter((dataResponse -> dataResponse.getData() != null));
}
This one’s just like our other server methods. We create an ApolloQueryCall
, use that to create a query object, and then convert it into an Observable
so we can work our Rx magic.
That’s it for our Server class. You can reference the repo version to make sure yours is setup correctly. With that done, we should be all set to dive into some advanced request scenarios.
Scenario 1: Parallel Requests
Our first scenario is a very common one, parallel requests. Often times, we need to make multiple requests to get the data we need. In our case, we’re going to make a page that lists information about a rocket along with a list of launchpads it’s used. Since these requests are independent of each other, we should do them in parallel. The best way to handle this scenario is to do both requests in the beginning, show a loading indicator while we wait for a response, and then show the page once both requests are done. Using a combination of GraphQL and RxJava, doing this on Android becomes really easy.
First, let’s create an Observable
of our rocket
query:
// Fetch Rockets
ObservableSource<Response<RocketQuery.Data>> observable = Server.fetchRocket(id);
Notice that we save this observable instead of immediately acting on it. That’s because we’re going to combine it with the our second request so we can act on the combined result. Don’t worry though, the request is made as soon as we call our fetchRocket
method.
We then do our launchpads
query and combine the two requests with zipWith
:
Server.fetchLaunchPads()
.zipWith(observable, (BiFunction<Response<LaunchPadsQuery.Data>, Response<RocketQuery.Data> , Pair<RocketQuery.Rocket, List<String>>>)
(launchPadsResponse, rocketResponse) - > {
List<LaunchPadsQuery.Launchpad> launchpads = launchPadsResponse.getData().launchpads();
RocketQuery.Rocket rocket = rocketResponse.getData().rocket();
String rocketId = rocket.id();
ArrayList<String> filteredPads = new ArrayList<>();
for (LaunchPadsQuery.Launchpad launchpad: launchpads) {
for (LaunchPadsQuery.Vehicles_launched vehicle: launchpad.vehicles_launched()) {
if (vehicle.id().equals(rocketId)) {
filteredPads.add(launchpad.name());
}
}
}
return new Pair<>(rocket, filteredPads);
})
Here, we do our launchpads
query and zip it with the observable
from our previous query. The second argument is a function that describes how to combine the two request responses. In our case, we’ll first sort through the launchpads and only save the ones that were used by the rocket we wanna show. Then, we combine the data from the two requests into a Pair
.
In this step of our pipeline, we’ve combined the responses from our requests into a new object. Our zipWith
function won’t get called until both responses resolve, so all we have to worry about now is handling the combined result. At this point, it’s just like handling a regular request. We can just use subscribeWith
:
// Once both requests have completed, handle the combined result
.subscribeWith(new DisposableObserver<Pair<RocketQuery.Rocket, List<String>>>() {
@Override
public void onNext(@NonNull Pair<RocketQuery.Rocket, List<String>> pair) {
RocketQuery.Rocket rocket = pair.first;
List<String> launchpads = pair.second;
nameView.setText(rocket.name());
flightView.setText(String.format("First flight was on %s", rocket.first_flight()));
costView.setText(String.format("Cost per launch: $%s", rocket.cost_per_launch()));
descView.setText(rocket.description());
LaunchPadsAdapter adapter = new LaunchPadsAdapter(launchpads);
recyclerView.setAdapter(adapter);
}
@Override
public void onError(@NonNull Throwable e) {
}
@Override
public void onComplete() {
// Both requests have finished, so hide loading bar
progressBar.setVisibility(View.GONE);
}
});
Notice that the class definition uses Pair<RocketQuery.Rocket,List<String>>
, this is the return type from our zipWith
function and the types need to match for our pipeline to work. In onNext
we use the Pair
we created in zipWith
to fill in our views with the response data. We then turn off the loading state in onComplete
. For simplicity sake, I didn’t handle error state, but this can be easily done in the onError
event handler. With this step our pipeline should be complete, you can double check your pipeline by checking out this code file.
Using Apollo Android and RxJava, we were able to handle parallel requests with ease. This could easily be scaled up to handle more parallel requests, just add another zipWith
for each new request and update your subscribeWith
accordingly.
Scenario 2: Dependent Requests
In our last scenario, the two requests were independent of each other, but what if they weren’t? Sometimes we need to make multiple requests, but the second request requires data from the first request. In this scenario, we can’t run the requests in parallel, instead we have to run them sequentially.
This time we’re still going to display information about a rocket, but we only have the rocket’s name, not its id. That means that we first have to do a request to the rockets
endpoint(see my last article), get the id from the response, and then call rocket
using that id. Handling this scenario on Android is a breeze with GraphQL and RxJava.
First, we’re going to query the rockets
endpoint, and then use concatMap
to handle the result:
// Fetch rockets and use the response to fetch the rocket we want
Server.fetchRockets()
.concatMap(dataResponse -> {
assert dataResponse.getData() != null;
List<RocketsQuery.Rocket> rocketList = dataResponse.getData().rockets();
for (RocketsQuery.Rocket rocket : rocketList) {
if (rocket.name().equals(ROCKET_NAME)) {
return Server.fetchRocket(rocket.id());
}
}
return null;
})
Here, we use the fetchRockets
method we created in my previous article, but handle it with concatMap
. With concatMap
, we can handle the response and then pass information down to the next step in our RxJava pipeline. First, we sort through the list to find the rocket we’re looking for. Once we find the right rocket, we use its id to make a request to our rocket
endpoint.
Notice that we’re returning the Observable
created by fetchRocket
, that’s because we’ll use it in our next pipeline step, shown below:
// Once response is received, handle result
.subscribeWith(new DisposableObserver<Response<RocketQuery.Data>>() {
@Override
public void onNext(@NonNull Response<RocketQuery.Data> dataResponse) {
assert dataResponse.getData() != null;
RocketQuery.Rocket rocket = dataResponse.getData().rocket();
nameView.setText(rocket.name());
flightView.setText(String.format("First flight was on %s", rocket.first_flight()));
costView.setText(String.format("Cost per launch: $%s", rocket.cost_per_launch()));
descView.setText(rocket.description());
}
@Override
public void onError(@NonNull Throwable e) {
}
@Override
public void onComplete() {
// Requests have finished, so hide loading bar
progressBar.setVisibility(View.GONE);
}
});
This code snippet should be all too familiar by now. Just like our other examples, we use subscribeWith
to handle the response. You can check the completed pipeline here. With RxJava, we can handle complex GraphQL requests on Android with ease.
Conclusion
By taking advantage of the Apollo Android and RxJava libraries, we can handle some pretty complex request scenarios without difficulty. Be sure to check out this article’s companion repo for a more in depth look at the code. In my next article, I’ll talk about how to write Android instrumented tests for the GraphQL requests we’ve made.
As always, thanks for reading and give me a ❤️ if you enjoyed this article! I love seeing how my readers use what they’ve learned, so please share how you’re using these strategies in your own Android apps in the comments below.
Top comments (0)