How to Handle Advanced Requests on Android with GraphQL &RxJava

aeldo

Akash Eldo

Posted on August 2, 2021

How to Handle Advanced Requests on Android with GraphQL &RxJava

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
    }
}
Enter fullscreen mode Exit fullscreen mode

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
    }
}
Enter fullscreen mode Exit fullscreen mode

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));
}
Enter fullscreen mode Exit fullscreen mode

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));
}
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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);
    })
Enter fullscreen mode Exit fullscreen mode

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);
  }
});
Enter fullscreen mode Exit fullscreen mode

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;
  })
Enter fullscreen mode Exit fullscreen mode

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);
  }
});
Enter fullscreen mode Exit fullscreen mode

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.

💖 💪 🙅 🚩
aeldo
Akash Eldo

Posted on August 2, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related