Skip to content

Instantly share code, notes, and snippets.

@nikolasburk
Last active March 31, 2017 16:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nikolasburk/92f6a871180cf95e061eb67916342464 to your computer and use it in GitHub Desktop.
Save nikolasburk/92f6a871180cf95e061eb67916342464 to your computer and use it in GitHub Desktop.

Using GraphQL with the Apollo iOS client

Were you ever annoyed when working with a REST API because the endpoints didn't give you the data you needed for the views in your app? Getting the right information either required multiple server requests or you had to bug the backend developers to adjust the API? Worry no more, GraphQL and Apollo to the rescue! ๐Ÿš€

GraphQL is a new API design paradigm that was open-sourced by Facebook in 2015. It introduces a new era for APIs by eliminating a lot of the ineffencies with today's de-facto standard REST. In contrast to REST, GraphQL APIs only expose a single endpoint and the consumer of the API can precisely specify what data they require with every request.

In this tutorial, you're going to build an iPhone app that helps users plan which iOS conferences they'd like to attend. You'll setup your own GraphQL server and interact with it from the app using the Apollo iOS Client, a networking library that makes working with a GraphQL APIs a breeze :]

The app will have the following features:

  • Display list of iOS conferences
  • Mark yourself as attending / not attending
  • View who else is going to attend a conference

For this tutorial, you'll have to install some tooling using the Node Package Manager, so make sure to have the latest version of npm installed before continuing!

Getting Started

Download and open the starter project for this tutorial. It already contains all the required UI components so that we can focus on interacting with the API and bringing the right data into the app.

Notice that we're using CocoaPods for this app, so you'll have to open the ConferencePlanner.xcworkspace after the download. The Apollo pod is already included in the project to give you a head-start.

Why GraphQL?

GraphQL vs REST

REST APIs expose multiple endpoints where each endpoint returns specific information. For example, you might have the following two endpoints:

  • /conferences: Returns a list of all the conferences where each conferences has id, name, city and year
  • /conferences/<id>/attendees: Returns a list of all attendees (each having an id and name) of the conferences with the given id

Now imagine you're writing an app where you're supposed to display a list of all the conferences, plus a sneak of the first three attendees per conference. What are your options?

  1. Tell your backend developers they should change the API so that each call to /conferences also returns the first three atteendees
  2. Make n+1 requests (where n is the number of conferences) to retrieve the required information, accepting that you might exhaust the user's data plan because you're downloading all the conferences' attendees but actually only display the first three

Both options are not very compelling and especially don't scale well in larger development projects!

Using GraphQL, you'd be able to simply specify your data requirements in a single request using GraphQL syntax and describe what data you need in a declarative fashion:

The response of this query will contain an array of conferences, each carrying a name, city and year as well as the first 3 attendees.

Using GraphQL on iOS with Apollo

GraphQL isn't very popular in the mobile developer communities (yet!), but that might change with more tooling evolving around it. A first step in that direction is the Apollo iOS client which implements handy features that you'll need when working with an API.

Currently, its major features are:

  1. Static type generation based on your data requirements
  2. Caching and automatic UI updates

You'll get to know both of them extensively throughout this tutorial.

Interacting with GraphQL

When interacting with an API, our main goals generally are:

  • Fetching data
  • Creating, updating and deleting data

In GraphQL, fetching data is done using queries, writing to the database can be achieved through so-called mutations.

Notice however that a mutation is also always a query. So, even when mutating data, you need to declare some information that you'd like to have returned by the server. This allows to retrieve the updated information in a single roundtrip! ๐Ÿ‘Œ

Let's take a sneek peak at two simple examples:

This query retrieves all the conferences and returns a JSON array where each object carries the id, the name and the city of one conference.

This mutation creates a new conference and likewise returns the id and the name of the conference that was created.

Don't worry if you don't quite grok the syntax yet, we'll discuss it in more detail later in the tutorial.

Preparing your GraphQL server

Getting a GraphQL endpoint

For the purpose of this tutorial, you're going to use a service called Graphcool that allows create GraphQL backends by simply specifying a data model. Speaking of the data model, here is what it looks like for our application, expressed in the GraphQL Interface Definition Language (IDL):

GraphQL has its own type system that we can build upon. Using the GraphQL IDL, we extend the type system with custom types. The new types in our case are Conference and Attendee. Each new type also has a number of properties, which are called fields in GraphQL lingo. Notice that the ! following the type of each field means that this field is required.

Enough talking, let's create your well-deserved GraphQL server!

  1. Install the Graphcool CLI with npm:

  2. Use it to create your GraphQL server:

    This command will create a Graphcool project named ConferencePlanner for you. Before the project is created, it'll also open up a browser window where you need to create a Graphcool account. Once that's done, you'll have access to the full power of GraphQL ๐Ÿ’ช

  3. Copy the endpoint for the Simple API and save it for later usage.

That's it, you now have a access to a fully-fledged GraphQL API that you can manage in the Graphcool console.

Entering initial conference data

Before we continue, let's add some data to the backend so that we have something to when we start querying the API.

Copy the endpoint from the Simple API that you received in the previous step and paste it in the address bar of your browser. This will open a GraphQL Playground.

A GraphQL Playground let's you explore the capabilities of a GraphQL API through direct interaction.

To create some initial data, paste the following GraphQL code into the left section of the Playground:

This snippet contains code for two GraphQL mutations. Click the Play-button and select each of the mutations that are displayed in the dropdown exactly once.

This will create two new conferences. To convince yourself that the conferences have actually been created, you can either send the allConferences query that we saw before in the Playground or view the current state of your database using the data browser in the Graphcool console:

Configuring Xcode and setting up the Apollo iOS client

Preparing static type generation

As quickly mentioned before, the Apollo iOS client features static type generation. This means you effectively don't have to write the model types which you'd use to represent the information from your application domain. Instead, the Apollo iOS client uses the information from your GraphQL queries to create exactly the Swift types that you need for your data requirements!

This approach eliminates the inconvenience that you usually have when parsing JSON in Swift. Since JSON by itself is not typed, the only real safe approach to parse it is by having optional properties on the Swift types since you can never be 100% sure whether a particular property is actually included in the JSON data.

To benefit from static type generation in Xcode, you'll have to go through some configuration steps:

  1. Install apollo-codegen

Use the following command in a terminal to install apollo-codegen:

  1. Add a build phase to the Xcode project

To add the build phase, follow these instructions:

  1. In Xcode, select the ConferencePlanner in the Project Navigator
  2. Select the only application target called ConferencePlanner
  3. Select the Build Phases tab on top
  4. Click the + button on the top left
  5. Select New Run Script Phase from the menu that pops up
  6. Rename the newly added build phase to Generate Apollo GraphQL API
  7. Drag and drop the build phase to a new position right before the one called Compile Sources
  8. Copy the following code snippet into the field that currently says: Type a script or drag a script file from your workspace to insert its path

Verify your settings look as follows:

  1. Add a schema file to the Xcode project

This is where we need the endpoint for the Simple API again. Download the schema file using the following command in a terminal (where you replace __SIMPLE_API_ENDPOINT__ with your custom GraphQL endpoint that we generated before):

Notice that if you lost your GraphQL endpoint, you can always find it in the Graphcool console by clicking the ENDPOINTS-button in the bottom-left corner:

Then move this file into the root directory of the Xcode project. This is the same directory where AppDelegate.swift is located: ConferencePlanner-starter/ConferencePlanner:

Here is a quick summary of what you just did: You first installed apollo-codegen, the command-line tool that will generate the Swift types. You then added a build phase the Xcode project where that tool will be invoked on every build just before compilation. Next to your actual GraphQL queries (which we're going to add in just a bit), this tool requires a schema file to be available in the root directory of your project which you downloaded in the last step.

Instantiate the ApolloClient

We're finally getting to the point where you can write some actual code! ๐ŸŽ‰

Open AppDelegate.swift, add the following code snippet where you replace __SIMPLE_API_ENDPOINT__ with your own endpoint for the Simple API:

You need to pass the endpoint for the Simple API to the initializer so that the ApolloClient knows which GraphQL server to talk to. The resulting apollo object will be your main interface to the API.

Creating your attendee and querying the conference list

You're all set to start interacting with the GraphQL API! Let's first make sure that users of the app can register themselves by picking a username.

This functionality will be implemented in the RegisterViewController that currently contains a simple UITextField where users can provide their username.

Writing your first mutation

We want to take the string that the user provides in the text field and create a new Attendee in the database. You can do so using the following mutation:

  1. This part represents the signature of the mutation (somewhat similar to the one of a Swift function). The mutation is called CreateAttendee and takes an argument called name of type String. The exclamation mark means that this argument is required.
  2. createAttendee refers to a mutation that is exposed by the GraphQL API. The Graphcool Simple API provides create-mutations for each type out of the box.
  3. This is the payload of the mutation, i.e. the data we'd like the server to return after the mutation was performed.

Create a new file in Xcode, using the Empty file template from the Other section and call it RegisterViewController.graphql:

Once created, paste the mutation from above into it. Upon the next build of the project, apollo-codegen will find this code and generate a Swift representation for the mutation from it. Hit CMD+B to build the project.

The first time apollo-codegen runs, it creates a new file in the root directory of the project called API.swift. All subsequent invocations will just update the existing file.

The API.swift file that was created is now located in the root directory of the project, but we still need to add it to Xcode. Drag and drop it into the project in the GraphQL group:

When inspecting the contents of API.swift, you'll see that a class called CreateAttendeeMutation has been generated. Its initializer takes the name variable as an argument. It also has a nested struct called data which again nests a struct called CreateAttendee. This will carry the id and the name of the attendee that we specified as return data in the mutation.

Next, you'll incorporate the mutation. Open RegisterViewController.swift and implement the createAttendee method like so:

What's happening here?

  1. Instantiate the mutation by providing the string that was entered by the user
  2. Use the apollo instance to send the mutation to the API
  3. Retrieve the data that was returned by the server and store it globally as the information about the current user

Notice that all the API calls you'll be doing in this tutorial will follow this pattern: First instantiate a query or mutation, then pass it to the ApolloClient and finally make use of the results in a callback.

Since users are allowed to change their usernames, go ahead and add the second mutation right away. Again, open RegisterViewController.graphql and paste the following code:

Hit CMD+B so that apollo-codegen generates the Swift code for this mutation, then open RegisterViewController.swift and add the following snippet for the updateAttendee method:

The code is almost the same as the one for createAttendee. Except that this time, you also have to pass the id of the user so that the GraphQL server know which user it should update.

Run the app and type a name into the text field, click the Save-button and a new attendee will be created in the GraphQL backend ๐Ÿ™Œ

Querying all conferences

Next, we want to display all the conferences in the ConferencesTableViewController.

Create a new file in the Xcode project, call it ConferenceTableViewController.graphql and paste the following GraphQL code:

What is this fragment we're seeing there? Fragments are simply reusable sub-parts that bundle a number of fields of a GraphQL type. They come in very handy in combination with the static type generation since they enhance the reusability of the information that we receive from the GraphQL server - each fragment will be represented by its own struct.

Fragments can be integrated in any query or mutation using ... plus the fragment name. When the AllConferences query is sent, ...ConferenceDetails gets replaced with all the fields that are contained within the ConferenceDetails fragment.

Next we want to use the query to populate our table view. Press CMD+B to make sure the types for the new query and fragment are generated, then open ConferencesTableViewController.swift and add a new property to its top:

At the end of viewDidLoad, add the following code to send the query and display the results:

You're using the same pattern that we saw in the first mutations, except that this time you're sending a query instead. After instantiating the query, you pass it to the apollo instance and retrieve the lists of conferences in the callback. This list is of type [AllConferencesQuery.Data.AllConference], so in order to use its information you first must retrieve the values of type ConferenceDetails by mapping over it and accessing the fragments.

All that's left to do is tell the UITableView how to display the conference data.

Open ConferenceCell.swift, add a property and implement didSet like so:

Notice that the code doesn't compile since numberOfAttendees is not available, we'll fix that in a second!

Then, in ConferencesTableViewController.swift, replace the current implementation of UITableViewDataSource with the following:

This is a pretty standard implementation of UITableViewDataSource and shouldn't contain any surprises. However, the compiler complains about isAttendedBy that it can't find anywhere on the ConferenceDetails type.

Both, numberOfAttendees and isAttendedBy represent useful information we would expect as utility functions on our "model" ConferenceDetails. However, remember that ConferenceDetails is a generated type and lives in API.swift. You should never make manual changes in that file, since they will be overriden the next time Xcode builds the project!

As a way out of that dilemma, you can simply create an extension in a different file where you implement the desired functionality. Open Utils.swift and add the following snippet:

Run the app and you'll see the conferences that we added in the beginning displayed in the table view! ๐Ÿ™‹

Displaying conference details

The ConferenceDetailViewController will display information about the selected conference, including the list of attendees.

Let's prepare everything by providing writing our GraphQL queries and letting the required Swift types get generated. Create a new file called ConferenceDetailViewController.graphql and paste the following GraphQL code:

In the first query, we ask for a specific conference by providing an id. The second query returns all attendees for a specific conference where for each attendee, all the info that is specified in AttendeeDetails will be returned by the server, i.e. the attendee's id, name and the number of conferences they're attending. The _conferencesMeta field in AttendeeDetails fragment allows to retrieve additional information about the relation, here we're asking for the number of attendees using count.

Hit CMD+B so that the Swift types are generated.

Next, open ConferenceDetailViewController.swiftand add the following two properties right after all the IBOutlet declarations:

The first two properties implement the didSet property observer to make sure the UI gets updated after they're set. The last one is a computed property that is a simple utility to find out whether the current user attends the conference that's displayed.

The updateUI method will configure the UI elements with the information about the selected conference. Implement it as follows:

Also in ConferenceDetailViewcontroller.swift, replace the current implementation of tableView(_:numberOfRowsInSection:) and tableView(_:cellForRowAt:) with the following:

Similar to what we saw before, the compiler complains about numberOfConferencesAttending not being available on AttendeeDetails. Let's fix that by implementing this in an extension of AttendeeDetails.

Open Utils.swift and add the following code:

Finish up the implementation of the ConferenceDetailViewController by loading the data about the conference in viewDidLoad:

Lastly, you need to pass the information about which conference was selected to the ConferenceDetailViewController, this can be done when preparing the segue.

Open ConferencesTableViewController.swift and implement prepare(for:sender:) like so:

That's it! Run the app and select one of the conferences in the table view. On the details screen, you'll now see the info about the selected conference being displayed ๐Ÿค“

Automatic UI updates when changing the attending status

A major advantage of working with the Apollo iOS client is that it normalizes and caches the data from previous queries. When sending a mutation, it knows what bits of data got changed and can update these specifically in the cache without having to resend the initial query. A nice side-effect of this approach is that it allows for automatic UI updates which is what we'll explore next.

In the ConferenceDetailViewController, there is a UIButton that allows the user to change their attending status of the conference. To be able to change that status in the backend, you first have to create two mutations in ConferenceDetailViewController.graphql:

The first mutation is used to add an attendee to a conference, the second one complements it and is responsible for removing an attendee.

Like before, hit CMD+B to make sure the types for these mutations are created.

Back in ConferenceDetailViewController.swift, implement the attendingButtonPressed method like so:

If you run the app now, you'll be able to change your attending status on a conference (you can verify this by using the data browser in the Graphcool console). However, this change is not yet reflected in the UI โ˜น๏ธ

No problem, the Apollo iOS client has you covered! With the GraphQLQueryWatcher you can observe changes that are happening through mutations. To incorporate the GraphQLQueryWatcher, only a few minor changes are required.

First, in ConferenceDetailViewController.swift, add two more properties to the top:

Now, you have to slightly change the way you send the queries in viewDidLoad, namely by using the method watch instead of fetch and assigning the return value of the call to the properties you just created:

and

The effect of these changes will be that every time that data that is related to the ConferenceDetailsQuery or to the AttendeesForConferenceQuery changes in the cache, the trailing closure that we're passing to the call to watch will be executed, thus taking care of updating the UI.

One last thing we have to do for the watchers to work correctly is to implement the cacheKeyForObject method on our instance of the ApolloClient. This method tells Apollo how we'd like to uniquely identify the objects that it's putting into the cache, in our case that's simply by looking at the id property.

A good place to implement cacheKeyForObject is when our app launches for the first time. Open AppDelegate.swift and add the following line in application(_:didFinishLaunchingWithOptions:) right before the return statement:

If you want to know more about why that's required and generally how the caching in Apollo works, you can read about it on the Apollo blog.

Running the app again and changing your attending status on a conference will now immediately update the UI. However, when navigating back to the ConferencesTableViewController, you'll notice that the status is not updated in the conference cell.

To fix that, we can use the same approach using a GraphQLQueryWatcher again. Open ConferencesTableViewController.swift and add the following property to the top of the class

Then update the way how we send the query in viewDidLoad:

This will make sure that after a mutation to change the attending status has been performed and thus data that's related to the AllConferencesQuery in the cache was changed, the trailing closure that is passed to watch is getting executed and thus the UI gets updated.

Where to go from here?

If you struggled with the implementation along the way, take a look at the final project.

If you want to find out more about GraphQL, you can start by reading the excellent docs or subscribe to GraphQL weekly. More great content around everything that's happening in the GraphQL community can further be found on the Apollo and Graphcool blogs.

As a challenge, you can try to implement functionality for adding new conferences yourself.

We hope you enjoyed learning about GraphQL! Let us know what you think about this new API paradigm by joining the discussion in the forum below.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment