EXPEDIA GROUP TECHNOLOGY — SOFTWARE

Migrating a Retrofit-Based Android App to the Apollo SDK

Improving the quality and productivity of GraphQL clients

Clay Tobolka
Expedia Group Technology

--

Photo of a distant airplane flying in a dusky sky
Photo by Edvin Richardson from Pexels

We have a fancy GraphQL back end, but our process for interfacing with it from Android apps was a very manual and tedious process prone to type safety errors. Apollo looks to solve those issues with their Apollo Android software development kit (SDK).

Apollo Android is a GraphQL client that generates Java and Kotlin models from GraphQL queries. These models give you a type-safe API to work with GraphQL servers. Apollo helps you keep your GraphQL query statements together, organized, and easy to access.

GraphQL is a specification for a query language for your application programming interfaces (APIs) and data that allows the client, in this case an Android app, to define the data it would like to get back from an API call. Instead of with typical REST, where the responses are predefined, GraphQL flips that around and exposes that power to the client. Apollo is a working implementation of this specification.

On Android, a goto for networking and defining your API calls is a library called Retrofit. Its an extremely straightforward library that gives you a lot of bang for your buck. While it does provide type safety, it doesn’t inherently have an understanding of GraphQL or do anything to help you manage the actual request and response data classes that it provides type safety against. In our case that ended up being a very manual process.

Where we started

To give some initial context, here is an example of one of the classes that we used to define our GraphQL API calls with Retrofit.

Below is the class used to create the request and define the GraphQL query for that call:

And here is the series of classes that defined the response:

All of this had to be created and maintained by hand which, as you can imagine or are currently experiencing yourself, is annoying and tedious. This also comes with no runtime type checking, so it waits as long as possible to fail on you and is prone to frequent syntax errors.

Why we moved to the Apollo SDK

This leads into the reasons we chose to move from our old manual process of defining our GraphQL APIs with Retrofit and developer-maintained model objects to using the Apollo Android SDK.

I’m going to stay away from the GraphQL vs. REST comparison as this article is more focused on the tools used to interface with a GraphQL API that already exists
If you are interested in the GraphQL vs. REST comparison, this is probably a good place to start — https://www.apollographql.com/docs/intro/benefits/

Why did we switch?

  • Having a networking client that is built to speak GraphQL allows you to more easily take full advantage of the GraphQL specification. (at least the portion currently supported by the SDK)
  • We gained serious type safety, and runtime type safety at that. The SDK generates the request and response data objects directly from the same schema.json file used by the GraphQL backend you are trying to interface with.
  • The Apollo Android SDK provides an awesome built-in cache that can be easily leveraged for your GraphQL API calls.
  • Moving to the SDK allows a consistent tool to be used as your networking layer across web and mobile.

Why might you not want to switch:

  • It does add an extra layer or two of mystery to your networking stack.
  • You lose control of how your response data objects are defined. This caused us some issues.
  • The cache can be fiddly if your API doesn’t perfectly fit what it is expecting.

Where we are now

Here is that same call but with the Apollo Android SDK.

You define your call in a .graphql file. This file is type- checked against your API’s schema.json .

The SDK does an awesome job surfacing the errors. Example:

Failed to parse GraphQL file /Users/ctobolka/Workspace/Mobile/Android/android-tx-checkout/api/src/main/graphql/com/homeaway/android/graphql/checkout/checkout.graphql (83:12)
Can’t query `paymentSessionIdd` on type `ThreeDSConfiguration`
— — — — — — — — — — — — — — — — — — — — — — — — — —
[82]: threeDSConfiguration {
[83]: paymentSessionIdd
[84]: initConfig
— — — — — — — — — — — — — — — — — — — — — — — — — —

Here is the same call from above, but defined in an Apollo SDK .graphql file.

Apollo will then generate all of the classes related to this call for you. If we peek inside that Mutation class, we’ll see it looks pretty similar to the one we were creating by hand above, GraphQLCreateCheckoutRequest.

After your app builds and the Apollo SDK gets a chance to generate the objects that make up the request and response you just defined, you can simply execute that API call using the appropriate method on an instance of the Apollo client.

The ApolloClient is just what you use to interface with the SDK. It requires a little setup, but you only have to do it once.

Please note that we are using an older version of the Apollo SDK in the Vrbo™ (part of Expedia Group™) Android traveler app, so some things may be slightly different with the latest version.

You use the Apollo client along with the generated classes to send your API requests. The CreateCheckoutRequest, its builder, and CreateCheckoutMutation class are all generated by the Apollo SDK and are type safe against the API’s schema.json.

This will return a response that contains a Data object corresponding to the query defined in the .graphql file for this mutation. In this case, for the call shown above, it’s CreateCheckoutMutation.Data and inside of that a CreateCheckout class that encapsulates the query-defined fields.

Again, you can see how this is similar to the GraphQLCheckoutResponse and CheckoutPresentationData objects where we’re defining by hand with Retrofit.

That may seem like a similar amount of code, but in the case of the Apollo SDK, a majority of it is now generated for us with a better guarantee of correctness than before.

Apollo explains how to implement caching on Android, and my colleague Nikola Dobrijevic explains what we do in more detail in his blog, How Apollo-Android Client Cache Works at Vrbo.
Essentially, you just need to set up a CacheKeyResolver and define what fragments you want to cache and values you want to use as IDs and then the Apollo SDK takes care of the rest for you.

Some of the challenges we faced

In the checkout module we were passing around a lot of model objects between screens. I know it’s not a best practice, but it’s the way things were.

This became a problem with Apollo because the model objects it generates are neither Serializable or Parcelable, a property we relied heavily on with our previous model objects.

  • Short term fix: We leveraged the Apollo cache as somewhat of a data store to pull from as we moved between screens. This isn’t the best solution, but it served as an intermediate one that allowed us to set up a pattern of each screen pulling its own data instead of relying on it being passed in from the previous screen.
  • Long term fix: We are currently working on some larger architectural changes in the checkout module that will eliminate the dependence on the Apollo cache outside of its intended use in the networking stack.

If you are in this camp, I’d suggest tackling this before trying to migrate to the Apollo SDK. It definitely was a huge source of headache.

Decorative separator

The built in caching functionality really relies on you being able to have a unique key for data/fragments you want to cache, as you would expect. We ended up having a data fragment where this wasn’t something we were able to form given the queryable data, so the same data was getting cached multiple times and other data was unintentionally getting evicted from the cache.

Luckily, for this specific fragment, we only ever need one version, so we got around this issue by using a hardcoded cache key for that fragment type resulting in only one instance of that type ever existing in the cache.

Decorative separator

While it may take a little bit of effort to get set up, that is a one time cost that the benefits of using the Android Apollo SDK greatly outweighed for us. As a team, we have already seen a huge improvement in ease of development with our GraphQL APIs and foresee further user and developer experience improvements in the future as a result of this move.

Decorative separator

Some good additional resources if you want to learn more:

Learn more about technology at Expedia Group

--

--