Getting started with GraphQL & Apollo on iOS

Nikolas Burk

Getting started with GraphQL & Apollo on iOS

Did you ever feel frustrated 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 from the server either required multiple requests or you had to bug the backend developers to adjust the API? Worry no more — it’s GraphQL and Apollo to the rescue!

GraphQL is a new API design paradigm open-sourced by Facebook in 2015, but has been powering their mobile apps since 2012. It eliminates many of the inefficiencies with today’s REST API. In contrast to REST, GraphQL APIs only expose a single endpoint and the consumer of the API can specify precisely what data they need.

In this GraphQL & Apollo on iOS 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 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 GraphQL & Apollo on iOS tutorial, you’ll have to install some tooling using the Node Package Manager, so make sure to have npm version 4.5.0 (or higher) installed before continuing!

Getting Started

Download and open the starter project for this GraphQL & Apollo on iOS tutorial. It already contains the required UI components, so you can focus on interacting with the API and bringing the right data into the app.

Here is what the Storyboard looks like:

Application Main Storyboard

You’re using CocoaPods for this app, so you’ll have to open the ConferencePlanner.xcworkspace after you’ve downloaded the package. The Apollo pod is already included in the project. However, you should ensure you have the most recent version installed.

Open a new Terminal window, navigate to the directory where you downloaded the starter project to, and execute pod install to update to the latest version:

Pod Install Output

Why GraphQL?

REST APIs expose multiple endpoints where each endpoint returns specific information. For example, you might have the following 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 conference attendees (each having an id and name) and the conference id.

Imagine you’re writing an app to display a list of all the conferences, plus the three latest registered attendees per conference. What are your options?

iOS Screen Application Running

Option 1: Adjust the API

Tell your backend developers to change the API so each call to /conferences also returns the last three registrations:

Conferences REST API Endpoint Data

Option 2: Make n+1 requests

Send n+1 requests (where n is the number of conferences) to retrieve the required information, accepting you might exhaust the user’s data plan because you’re downloading all the conferences’ attendees but actually only display the last three:

Conference Attendee REST Endpoint Data

Both options are not terribly compelling, and won’t scale well in larger development projects!

Using GraphQL, you’re able to simply specify your data requirements in a single request using GraphQL syntax and describe the data you need in a declarative fashion:

{
  allConferences {
    name
    city
    year
    attendees(last: 3) {
      name
    }
  }
}

The response of this query will contain an array of conferences, each carrying a name, city and year as well as the last three 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 you’ll need when working with APIs.

Currently, its major features are as follows:

  1. Static type generation based on your data requirements
  2. Caching and watching queries

You’ll get experience with both of these extensively throughout this GraphQL & Apollo on iOS tutorial.

Interacting with GraphQL

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

  • Fetching data
  • Creating, updating and deleting data

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

A mutation, much like a query, also allows you to declare information to be returned by the server and thus enables you to retrieve the updated information in a single roundtrip!

Consider the following two simple examples:

query AllConferences {
  allConferences {
    id
    name
  }
}

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

mutation CreateConference {
  createConference(name: "WWDC", city: "San Jose", year: "2017") {
    id 
  }
}

This mutation creates a new conference and likewise returns its id.

Don’t worry if you don’t quite grok the syntax yet — it’ll be discussed in more detail later!

Preparing Your GraphQL Server

For the purpose of this GraphQL & Apollo on iOS tutorial, you’re going to use a service called Graphcool to generate a full-blown GraphQL server based on a data model.

Speaking of the data model, here is what it looks like for the application, expressed in a syntax called GraphQL Interface Definition Language (IDL):

type Conference {
  id: String!
  name: String!
  city: String!
  year: String!
  attendees: [Attendee] @relation(name: Attendees)
}

type Attendee {
  id: String!
  name: String!
  conferences: [Conference] @relation(name: Attendees)
}

GraphQL has its own type system you can build upon. The types in this case are Conference and Attendee. Each type has a number of properties, called fields in GraphQL terminology. Notice the ! following the type of each field, which means this field is required.

Enough talking, go ahead and create your well-deserved GraphQL server!

Install the Graphcool CLI with npm. Open a Terminal window and type the following:

npm install -g graphcool

Use graphcool to create your GraphQL server by typing the following into a Terminal window:

graphcool init --schema http://graphqlbin.com/conferences.graphql --name ConferencePlanner

This command will create a Graphcool project named ConferencePlanner. Before the project is created, it’ll also open up a browser window where you need to create a Graphcool account. Once created, you’ll have access to the full power of GraphQL:

GraphCool command output showing Simple API and Relay API

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

That’s it! You now have access to a fully-fledged GraphQL API you can manage in the Graphcool console.

Entering Initial Conference Data

Before continuing, you’ll add some initial data to the database.

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

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

mutation createUIKonfMutation {
  createConference(name: "UIKonf", city: "Berlin", year: "2017") {
    id
  }
}

mutation createWWDCMutation {
  createConference(name: "WWDC", city: "San Jose", year: "2017") {
    id
  }
}

This snippet contains code for two GraphQL mutations. Click the Play button and select each of the mutations displayed in the dropdown only once:

GraphQL playground

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

GraphQL Playground AllConferences query result

Configuring Xcode and Setting Up the Apollo iOS Client

As 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 generate the Swift types you need!

Note: This approach eliminates the inconvenience of parsing JSON in Swift. Since JSON 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

apollo-codegen will search for GraphQL code in the Xcode project and generate the Swift types.

Open a Terminal window and type the following command:

npm install -g apollo-codegen

NPM results from apollo-codegen installation

2. Add a build phase

In Xcode, select the ConferencePlanner in the Project Navigator. Select the application target called ConferencePlanner. Select the Build Phases tab on top, and click the + button on the top left.

Select New Run Script Phase from the menu:

Xcode altering build phases adding new run script

Rename the newly added build phase to Generate Apollo GraphQL API. Drag and drop the build phase to be above the Compile Sources.

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:

APOLLO_FRAMEWORK_PATH="$(eval find $FRAMEWORK_SEARCH_PATHS -name "Apollo.framework" -maxdepth 1)"

if [ -z "$APOLLO_FRAMEWORK_PATH" ]; then
echo "error: Couldn't find Apollo.framework in FRAMEWORK_SEARCH_PATHS; make sure to add the framework to your project."
exit 1
fi

cd "${SRCROOT}/${TARGET_NAME}"
$APOLLO_FRAMEWORK_PATH/check-and-run-apollo-codegen.sh generate $(find . -name '*.graphql') --schema schema.json --output API.swift

Verify your Build Phases look like this:

Build Script Run Phase Code to Run

3. Add the schema file

This is where you need the endpoint for the Simple API again. Open a Terminal window and type the following command (replacing __SIMPLE_API_ENDPOINT__ with the custom GraphQL endpoint you previously generated):

apollo-codegen download-schema __SIMPLE_API_ENDPOINT__ --output schema.json

Note: If you lose your GraphQL endpoint, you can always find it in the Graphcool console by clicking the ENDPOINTS button in the bottom-left corner:
GraphCool Endpoint Console

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

Finder File Listing showing Schema.json

Here is a quick summary of what you just did:

  • You first installed apollo-codegen, the command-line tool that generates the Swift types.
  • Next, you added a build phase to the Xcode project where apollo-codegen will be invoked on every build just before compilation.
  • Next to your actual GraphQL queries (which you’re going to add in just a bit), apollo-codegen requires a schema file to be available in the root directory of your project which you downloaded in the last step.

Instantiate the ApolloClient

You’re finally going to write some actual code!

Open AppDelegate.swift, and add the following code replacing __SIMPLE_API_ENDPOINT__ with your own endpoint for the Simple API:

import Apollo

let graphQLEndpoint = "__SIMPLE_API_ENDPOINT__"
let apollo = ApolloClient(url: URL(string: graphQLEndpoint)!)

You need to pass the endpoint for the Simple API to the initializer so 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! First, make sure users of the app can register themselves by picking a username.

Writing Your First Mutation

Create new file in the GraphQL Xcode group using the Empty file template from the Other section and name it RegisterViewController.graphql:

Xcode New File Picker

Next, add the following mutation into that file:

# 1
mutation CreateAttendee($name: String!) {
  # 2
  createAttendee(name: $name) {
    # 3
    id
    name
  }
}

Here’s what’s going on in that mutation:

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

On 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.

Note: If you’d like to have syntax highlighting for your GraphQL code, you can follow the instructions here to set it up.

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

The generated API.swift file is located in the root directory of the project, but you still need to add it to Xcode. Drag and drop it into the GraphQL group. Make sure to uncheck the Copy items if needed checkbox!

Xcode Project Navigator showing API.swift in the GraphQL group

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

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

func createAttendee(name: String) {
  activityIndicator.startAnimating()
  
  // 1  
  let createAttendeeMutation = CreateAttendeeMutation(name: name)
  
  // 2
  apollo.perform(mutation: createAttendeeMutation) { [weak self] result, error in
     self?.activityIndicator.stopAnimating()
      
    if let error = error {
      print(error.localizedDescription)
      return
    }
      
    // 3
    currentUserID = result?.data?.createAttendee?.id
    currentUserName = result?.data?.createAttendee?.name
      
    self?.performSegue(withIdentifier: "ShowConferencesAnimated", sender: nil)
  }
}

In the code above, you:

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

Note: All the API calls you’ll be doing in this GraphQL & Apollo on iOS 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, you can add the second mutation right away. Open RegisterViewController.graphql and add the following code at the end:

mutation UpdateAttendeeName($id: ID!, $newName: String!) {
  updateAttendee(id: $id, name: $newName) {
    id
    name
  }
}

Press Cmd+B to make apollo-codegen generate the Swift code for this mutation. Next, open RegisterViewController.swift and replace updateAttendee with the following:

func updateAttendee(id: String, newName: String) {
  activityIndicator.startAnimating()
  
  let updateAttendeeNameMutation = UpdateAttendeeNameMutation(id: id, newName: newName)
  apollo.perform(mutation: updateAttendeeNameMutation) { [weak self] result, error in
    self?.activityIndicator.stopAnimating()
    
    if let error = error {
      print(error.localizedDescription)
      return
    }
    
    currentUserID = result?.data?.updateAttendee?.id
    currentUserName = result?.data?.updateAttendee?.name
    
    self?.performSegue(withIdentifier: "ShowConferencesAnimated", sender: nil)
  }
}

The code is almost identical to createAttendee, except this time you also pass the id of the user so the GraphQL server knows which user it should update.

Build and run the app, type a name into the text field, then click the Save button. A new attendee will be created in the GraphQL backend.

User Settings page for the application

You can validate this by checking the data browser or sending the allAttendees query in a Playground:

GraphCool Playground showing AllAttendees query

Querying All Conferences

The next goal is to display all the conferences in the ConferencesTableViewController.

Create a new file in the GraphQL group, name it ConferenceTableViewController.graphql and add the following GraphQL code:

fragment ConferenceDetails on Conference {
  id
  name
  city
  year
  attendees {
    id
  }
}

query AllConferences {
  allConferences {
    ...ConferenceDetails
  }
}

What’s that fragment thing 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 returned by the GraphQL server, and 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 is replaced with all the fields contained within the ConferenceDetails fragment.

Next it’s time to use the query to populate the 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 at the top:

var conferences: [ConferenceDetails] = [] {
  didSet {
    tableView.reloadData()
  }
}

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

let allConferencesQuery = AllConferencesQuery()
apollo.fetch(query: allConferencesQuery) { [weak self] result, error in
  guard let conferences = result?.data?.allConferences else { return }  
  self?.conferences = conferences.map { $0.fragments.conferenceDetails }
}

You’re using the same pattern you saw in the first mutations, except 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, and add the following property:

var conference: ConferenceDetails! {
  didSet {
    nameLabel.text = "\(conference.name) \(conference.year)"
    let attendeeCount = conference.numberOfAttendees
    infoLabel.text = 
      "\(conference.city) (\(attendeeCount) \(attendeeCount == 1 ? "attendee" : "attendees"))"
  }
} 

Notice the code doesn’t compile since numberOfAttendees is not available. You’ll fix that in a second

Next, open ConferencesTableViewController.swift, and replace the current implementation of UITableViewDataSource with the following:

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  return conferences.count
}

override func tableView(_ tableView: UITableView,
                        cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  let cell = tableView.dequeueReusableCell(withIdentifier: "ConferenceCell") as! ConferenceCell
  let conference = conferences[indexPath.row]
  cell.conference = conference
  cell.isCurrentUserAttending = conference.isAttendedBy(currentUserID!)
  return cell
}

This is a standard implementation of UITableViewDataSource. However, the compiler complains isAttendedBy can’t be found on the ConferenceDetails type.

Both numberOfAttendees and isAttendedBy represent useful information that could be expected as utility functions on the “model” ConferenceDetails. However, remember ConferenceDetails is a generated type and lives in API.swift. You should never make manual changes in that file, since they’ll be overridden the next time Xcode builds the project!

A way out of this dilemma is to create an extension in a different file where you implement the desired functionality. Open Utils.swift and add the following extension:

extension ConferenceDetails {
  
  var numberOfAttendees: Int {
    return attendees?.count ?? 0
  }
  
  func isAttendedBy(_ attendeeID: String) -> Bool {
    return attendees?.contains(where: { $0.id == attendeeID }) ?? false
  }
}

Run the app and you’ll see the conferences you added in the beginning displayed in the table view:

Listing of the conferences

Displaying Conference Details

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

You’ll prepare everything by writing the GraphQL queries and generating the required Swift types.

Create a new file named ConferenceDetailViewController.graphql and add the following GraphQL code:

query ConferenceDetails($id: ID!) {
  conference: Conference(id: $id) {
    ...ConferenceDetails
  }
}

query AttendeesForConference($conferenceId: ID!) {
  conference: Conference(id: $conferenceId) {
    id
    attendees {
      ...AttendeeDetails
    }
  }
}

fragment AttendeeDetails on Attendee {
  id
  name
  _conferencesMeta {
    count
  }
}

In the first query, you 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 is specified in AttendeeDetails will be returned by the server. That includes the attendee’s id, name and the number of conferences they’re attending.

The _conferencesMeta field in AttendeeDetails fragment allows you to retrieve additional information about the relation. Here you’re asking for the number of attendees using count.

Build the application to generate the Swift types.

Next, open ConferenceDetailViewController.swift and add the following properties below the
IBOutlet declarations:

var conference: ConferenceDetails! {
  didSet {
    if isViewLoaded {
      updateUI()
    }
  }
}
  
var attendees: [AttendeeDetails]? {
  didSet {
    attendeesTableView.reloadData()
  }
}

var isCurrentUserAttending: Bool {
  return conference?.isAttendedBy(currentUserID!) ?? false
}

The first two properties implement the didSet property observer to make sure the UI gets updated after they’re set. The last one computes if the current user attends the conference being displayed.

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

func updateUI() {
  nameLabel.text = conference.name
  infoLabel.text = "\(conference.city), \(conference.year)"
  attendingLabel.text = isCurrentUserAttending ? attendingText : notAttendingText
  toggleAttendingButton.setTitle(isCurrentUserAttending ? attendingButtonText : notAttendingButtonText, for: .normal)
}

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

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return attendees?.count ?? 0
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  guard let attendees = self.attendees else { return UITableViewCell() }
  
  let cell = tableView.dequeueReusableCell(withIdentifier: "AttendeeCell")!
  let attendeeDetails = attendees[indexPath.row]
  cell.textLabel?.text = attendeeDetails.name
  let otherConferencesCount = attendeeDetails.numberOfConferencesAttending - 1
  cell.detailTextLabel?.text = "attends \(otherConferencesCount) other conferences"
  return cell
}

Similarly to what you saw before, the compiler complains about numberOfConferencesAttending not being available on AttendeeDetails. You’ll fix that by implementing this in an extension of AttendeeDetails.

Open Utils.swift and add the following extension:

extension AttendeeDetails {
  
  var numberOfConferencesAttending: Int {
    return conferencesMeta.count
  }
  
}

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

let conferenceDetailsQuery = ConferenceDetailsQuery(id: conference.id) 
apollo.fetch(query: conferenceDetailsQuery) { result, error in
  guard let conference = result?.data?.conference else { return }
  self.conference = conference.fragments.conferenceDetails
}

let attendeesForConferenceQuery = AttendeesForConferenceQuery(conferenceId: conference.id)
apollo.fetch(query: attendeesForConferenceQuery) { result, error in
  guard let conference = result?.data?.conference else { return }
  self.attendees = conference.attendees?.map { $0.fragments.attendeeDetails }
}

Finally, you need to pass the information about which conference was selected to the ConferenceDetailViewController, this can be done right before the segue is performed.

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

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
  let conferenceDetailViewController = segue.destination as! ConferenceDetailViewController
  conferenceDetailViewController.conference = conferences[tableView.indexPathForSelectedRow!.row]
}

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 it normalizes and caches the data from previous queries. When sending a mutation, it knows what bits of data changed and can update these specifically in the cache without having to resend the initial query. A nice side-effect is it allows for “automatic UI updates”, which you’ll explore next.

In ConferenceDetailViewController, there’s a button to allow the user to change their attending status of the conference. To change that status in the backend, you first have to create two mutations in ConferenceDetailViewController.graphql:

mutation AttendConference($conferenceId: ID!, $attendeeId: ID!) {
  addToAttendees(conferencesConferenceId: $conferenceId, attendeesAttendeeId: $attendeeId) {
    conferencesConference {
      id
      attendees {
        ...AttendeeDetails
      }
    }
  }
}

mutation NotAttendConference($conferenceId: ID!, $attendeeId: ID!) {
  removeFromAttendees(conferencesConferenceId: $conferenceId, attendeesAttendeeId: $attendeeId) {
    conferencesConference {
      id
      attendees {
        ...AttendeeDetails
      }
    }
  }
}

The first mutation is used to add an attendee to a conference; the second, to remove an attendee.

Build the application to make sure the types for these mutations are created.

Open, ConferenceDetailViewController.swift and replace the attendingButtonPressed method with the following:

@IBAction func attendingButtonPressed() {
  if isCurrentUserAttending {
    let notAttendingConferenceMutation = 
      NotAttendConferenceMutation(conferenceId: conference.id,
                                  attendeeId: currentUserID!)
    apollo.perform(mutation: notAttendingConferenceMutation, resultHandler: nil)
  } else {
    let attendingConferenceMutation = 
      AttendConferenceMutation(conferenceId: conference.id,
                               attendeeId: currentUserID!)
    apollo.perform(mutation: attendingConferenceMutation, resultHandler: nil)
  }
}

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 worries: The Apollo iOS client has you covered! With the GraphQLQueryWatcher, you can observe changes occurring through mutations. To incorporate the GraphQLQueryWatcher, a few minor changes are required.

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

var conferenceWatcher: GraphQLQueryWatcher<ConferenceDetailsQuery>?
var attendeesWatcher: GraphQLQueryWatcher<AttendeesForConferenceQuery>?

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

...
let conferenceDetailsQuery = ConferenceDetailsQuery(id: conference.id)
conferenceWatcher = apollo.watch(query: conferenceDetailsQuery) { [weak self] result, error in      guard let conference = result?.data?.conference else { return }
  self?.conference = conference.fragments.conferenceDetails
}
...

and

...
let attendeesForConferenceQuery = AttendeesForConferenceQuery(conferenceId: conference.id)
attendeesWatcher = apollo.watch(query: attendeesForConferenceQuery) { [weak self] result, error in
  guard let conference = result?.data?.conference else { return }
  self?.attendees = conference.attendees?.map { $0.fragments.attendeeDetails }
}
...

Every time data related to the ConferenceDetailsQuery or to the AttendeesForConferenceQuery changes in the cache, the trailing closure you’re passing to the call to watch will be executed, thus taking care of updating the UI.

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

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

apollo.cacheKeyForObject = { $0["id"] }

Note: 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 the status is not updated in the conference cell:

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

var allConferencesWatcher: GraphQLQueryWatcher<AllConferencesQuery>?

Next, update the query in viewDidLoad:

...
let allConferencesQuery = AllConferencesQuery()
allConferencesWatcher = apollo.watch(query: allConferencesQuery) { result, error in
  guard let conferences = result?.data?.allConferences else { return }
  self.conferences = conferences.map { $0.fragments.conferenceDetails }
}
...

This will make sure to execute the trailing closure passed to watch when the data in the cache relating to AllConferencesQuery changes.

Where to Go From Here?

Take a look at the final project for this GraphQL & Apollo on iOS tutorial in case you want to compare it against your work.

If you want to learn 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 be found on the Apollo and Graphcool blogs.

As a challenge, you can try to implement functionality for adding new conferences yourself! This feature is also included in the sample solution.

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.

Team

Each tutorial at www.raywenderlich.com is created by a team of dedicated developers so that it meets our high quality standards. The team members who worked on this tutorial are:

Nikolas Burk

Nikolas is an experienced iOS developer and passionate about teaching and sharing his knowledge.
Having witnessed the birth of Swift as a Student Scholar at WWDC 14, he's particularly excited about Swift's functional language features and new architectural patterns that emerge from these. After he graduated with a Master's degree in Management of Information Systems in 2015, he attended the Recurse Center in NYC to study functional programming by digging into Haskell.
Nikolas then worked as an iOS Instructor at Make School in San Francisco before he joined Graphcool to evangelize GraphQL as a new API standard among native iOS Developers.

Other Items of Interest

Save time.
Learn more with our video courses.

raywenderlich.com Weekly

Sign up to receive the latest tutorials from raywenderlich.com each week, and receive a free epic-length tutorial as a bonus!

Advertise with Us!

PragmaConf 2016 Come check out Alt U

Our Books

Our Team

Video Team

... 20 total!

Swift Team

... 15 total!

iOS Team

... 44 total!

Android Team

... 15 total!

macOS Team

... 11 total!

Unity Team

... 11 total!

Articles Team

... 15 total!

Resident Authors Team

... 17 total!

Podcast Team

... 8 total!

Recruitment Team

... 9 total!