Unleashing the power of GraphQL in your iOS app

Streamline data fetching in your iOS app using GraphQL and Apollo Framework together with SwiftUI and MVVM architecture

Vincenzo Pascarella
14 min readJul 12, 2023

Introduction

GraphQL has revolutionized the way we fetch and manage data in modern development. Unlike traditional REST APIs, GraphQL allows us to retrieve precisely the data we need with a single request, eliminating the need for multiple round trips. With its powerful query language, consumers of the API can specify the exact data they require, resulting in more efficient and flexible data fetching.

GraphQL + Apollo + Swift Image by Vincenzo Pascarella
GraphQL + Apollo + Swift Image by Vincenzo Pascarella

Since its open-source release in 2015, GraphQL has gained immense popularity and has fostered a vibrant community around it. Within this community, several tools and libraries have emerged to enhance the GraphQL ecosystem. One such tool is Apollo, a feature-rich and type-safe caching implementation of GraphQL.

In this tutorial, we will explore the capabilities of GraphQL and Apollo by utilizing the ChargeTrip Playground API as our testing ground. You can find the final code in this GitHub repository. We will dive into GraphQL queries, leveraging the expressive power of the language, and demonstrate how Apollo simplifies data fetching and caching in GraphQL-based applications. So let’s get started and unlock the full potential of GraphQL with Apollo!

Add the Apollo iOS SDK to your project

To begin using the Apollo iOS SDK in your project, follow these steps:

  1. Launch Xcode and open your project. Then, navigate to File > Add Packages…. This will open the Add Package dialog.
  2. By default, the Add Package dialog displays Apple packages. However, you need to add the Apollo iOS package. In the upper right-hand corner of the dialog, paste the following URL into the search bar: https://github.com/apollographql/apollo-ios.
Add Package dialog
Add Package dialog

3. Xcode will now show you the apollo-ios package in the search results. In the right-hand panel, you can select the version you want to use. For this tutorial, I recommend selecting Up to Next Minor from the Version dropdown. Note that there may be minor breaking changes between minor releases, so it's important to choose a version that suits your project's requirements. At the time of writing, the current minor version is 1.3.x.

Up to Next Minor from the Version dropdown
Up to Next Minor from the Version dropdown

4. After selecting the version, click Add Package. Xcode’s Swift Package Manager (SPM) will start checking out the package and integrating it into your project. Once the process is complete, you will see a list of framework targets included in the apollo-ios library.

5. In the list of framework targets, select only the main Apollo target. This ensures that only the necessary components are added to your project.

6. Finally, click Add Package to confirm your selection. SPM will fetch the necessary dependencies for the apollo-ios package. Once the fetch process is complete, you will be able to see the added dependencies in the project navigator of Xcode.

Setup Codegen CLI

The Codegen CLI is an essential tool provided by the Apollo iOS SPM package. It allows you to generate Swift code from your GraphQL schema and queries. To set up the Codegen CLI, follow the instructions below:

  1. In your Xcode project, locate the project file in the file explorer. Right-click on the project file to reveal a context menu.
  2. From the context menu, select the Install CLI plugin command. This will initiate the installation process for the Codegen CLI.
Install CLI context menu
Install CLI context menu

3. During the installation, a dialog may appear, requesting write access to your project directory. Grant the plugin “write” access to proceed with the installation.

4. Once the installation is complete, a symbolic link named apollo-ios-cli will be created in your project's root folder. This link points to the actual executable of the Codegen CLI.

5. To run the Codegen CLI, open the terminal and navigate to your project’s root folder. Execute the following command: ./apollo-ios-cli. This will execute the Codegen CLI and allow you to generate Swift code based on your GraphQL schema and queries.

Note: The apollo-ios-cli symbolic link works only if the compiled CLI executable exists. In most cases, this executable is located in the Xcode Derived Data or .build folder. If you clear these folders or encounter issues, you can rerun the Install CLI plugin to rebuild the CLI executable.

⚠️ Important: There is a known bug in Xcode 14.3 where the Install CLI plugin command may not appear in the right-click menu for your project. This issue is being tracked here. If you encounter this problem, you can either try using a different version of Xcode or manually download a pre-built binary of the CLI. As described below.

For each release of Apollo iOS on GitHub, there is a pre-built CLI binary available for download. You can find these binaries in the releases section. After downloading the binary, you can move it to a convenient local directory. Make sure to run the Codegen CLI from the directory where it is located. A suggested approach is to place it in your project directory.

When you double-click on the apollo-ios-cli binary file, macOS may display an error regarding the unidentified developer and potential malware.

macOS error regarding the unidentified developer
macOS error regarding the unidentified developer

To resolve this issue, right-click on the binary file and select “Open.” The system will prompt you to confirm that you want to open the file. Click “Open” again to indicate that the file is safe to use.

Right-click dropdown menu
Right-click dropdown menu

Once the terminal window opens, close it, and continue following this guide to proceed with the setup process.

Create your Codegen Configuration

To begin, let’s set up the codegen configuration file for our project. Follow the steps below:

  1. Open the Terminal and navigate to your project directory (or the directory where the apollo-ios-cli is located). You can do this by typing cd followed by a space in the Terminal and then dragging the project directory into the Terminal window. Press Enter to navigate to the specified directory.
  2. In the Terminal, run the following command:
./apollo-ios-cli init --schema-namespace ChargeTripAPI --module-type swiftPackageManager

This command generates a basic apollo-codegen-config.json file specifically tailored to our project.

Download your server’s schema

Next, we need to download the schema for our project. To do this, we’ll update the apollo-codegen-config.json file to include a schemaDownloadConfiguration. Follow the steps below:

  1. Open the apollo-codegen-config.json file in a text editor.
  2. Locate the output object within the file. After the output object, add the following JSON code:
"schemaDownloadConfiguration": {
"downloadMethod": {
"introspection": {
"endpointURL": "https://api.chargetrip.io/graphql",
"httpMethod": {
"POST": {}
},
"includeDeprecatedInputValues": false,
"outputFormat": "SDL"
}
},
"downloadTimeout": 60,
"headers": [],
"outputPath": "./graphql/schema.graphqls"
}

This configuration specifies the download method for the schema, including the endpoint URL, HTTP method, and output format. Adjust the values as needed to match your project’s requirements. Save the changes to the apollo-codegen-config.json file.

Now that we’ve updated the configuration file, we can proceed to download the schema. In the Terminal, run the following command:

./apollo-ios-cli fetch-schema

This command will initiate the schema download process using the configured settings.

After running the command, you should see a graphql folder in your project directory. Inside this folder, you'll find a schema.graphqls file. This file contains the downloaded schema from your server.

Write your first query

The most common operation in GraphQL is the query, which allows you to request specific data from your GraphQL server based on its schema.

In the docs section that you can open by pressing the label on the right side of the ChargeTrip Playground, you can see both the query term itself, the return type, and information about parameters that can be passed to the query. We will use that information to write our query.

ChargeTrip Playground Documentation
ChargeTrip Playground Documentation

Now we can start by writing a simple query that retrieves a list of stations. In this example, we’ll request the id, address, city, and name of the station owner for each station. Here's the query:

query StationList {
stationList {
id
address
city
owner {
name
}
}
}

Let’s break down the structure of this query:

  • The outermost brackets define the query operation. In this case, it’s a query operation named StationList.
  • Inside the query operation, we have the selection set enclosed in another set of brackets. The selection set specifies the fields we want to retrieve from the server.
  • Within the selection set, we specify the fields we want to fetch for each station object, including id, address, city, and the name of the station's owner.

The Apollo iOS SDK requires every query to have a name, even though it’s not strictly required by the GraphQL specification. Naming the query operation helps in distinguishing and organizing multiple queries within your app.

To test this query, you can use the ChargeTrip Playground and click the “Play” button in the center of the page. The query will be executed, and you’ll see the results as a JSON object displayed on the right-hand side of the page.

Add the query to Xcode

To use the GraphQL query in your Xcode project, follow the steps below:

  1. In Xcode, locate the graphql folder in your project hierarchy. Right-click on the graphql folder and select New File... from the context menu.
  2. In the New File dialog, navigate to the Other section and select the Empty file template. Click Next to proceed.
  3. In the next dialog, name the file StationList.graphql. Make sure the file is being added to the graphql folder where your schema.graphqls file is located. It's important to ensure that the file is not added to the app target. Click Create to create the file.
New File dialog
New File dialog

4. Open the newly created StationList.graphql file and paste the following query into it:

query StationList {
stationList {
id
address
city
owner {
name
}
}
}

Running code generation

Now that we have both the schema and query files in place, we can run the code generation process. To generate the code, follow these steps:

  1. Open the Terminal and navigate to your project directory.
  2. Run the following command in the Terminal:
./apollo-ios-cli generate

This command will trigger the code generation process using the downloaded schema and query files. As a result, you will see a new folder named ChargeTripAPI in your project directory. This folder contains the Swift package with the generated source code.

Add the generated SPM package to your project

Once the code generation is complete, we need to add the generated Swift Package Manager (SPM) package to our Xcode project. Follow these steps:

  1. In Xcode, go to File > Add Packages….
  2. In the Add Package dialog, select Add Local….
  3. Navigate to the ChargeTripAPI folder in the file dialog and click Add Package.
  4. The ChargeTripAPI package will now be included in your project.
  5. Next, select your project in Xcode, then select the ChargeTrip GraphQL target, and navigate to Build Phases.
  6. Under the Link Binary With Libraries section, click the + sign.
  7. In the dialog that appears, select the ChargeTripAPI library and click Add Package....

Now the generated SPM package is added to your Xcode project, allowing you to use the generated code in your app.

Execute your first query

To execute your first query using the generated code, you need to create an instance of ApolloClient. This instance will use the generated code to make network calls to your server. It's recommended to create a singleton or static instance of ApolloClient that can be accessed from anywhere in your codebase.

Follow these steps to create the ApolloClient instance:

  1. Create a new Swift file within the ChargeTrip GraphQL group in Xcode. Name the file Network.swift and set the target to ChargeTrip GraphQL.
  2. Import the Apollo module at the top of the file.
  3. Create a Network class that holds the ApolloClient instance. Your file should look like this:
import Foundation
import Apollo
import Foundation
import Apollo

class Network {
static let shared = Network()

private init() {}

private(set) lazy var apollo = ApolloClient(url: URL(string: "https://api.chargetrip.io/graphql")!)
}

The Network class uses the singleton pattern to ensure that there is only one instance of ApolloClient throughout your app. The apollo property represents the ApolloClient instance, and it's initialized with the URL of your GraphQL server.

Implement the query

To implement the query and communicate with the server using your ApolloClient instance, follow these steps:

  1. Create a new Swift file called StationListViewModel and import Apollo and ChargeTripAPI. This file will contain the implementation of your ViewModel.
  2. In the StationListViewModel, create a class named StationListViewModel that conforms to the ObservableObject protocol. This allows the ViewModel to publish changes to its properties.
  3. Inside the StationListViewModel, add an initializer. In the initializer, use the Network.shared.apollo instance to fetch the StationListQuery from the server. Handle the result using a closure.
import Foundation
import Apollo
import ChargeTripAPI

@MainActor
final class StationListViewModel: ObservableObject {
init() {
Network.shared.apollo.fetch(query: StationListQuery()) { result in
switch result {
case .success(let graphQLResult):
print("Success! Result: \\(graphQLResult)")
case .failure(let error):
print("Failure! Error: \\(error)")
}
}
}
}

4. Create a new SwiftUI view called StationListView (instead of the default ContentView ). Remember to replace also the ContentView() in the ChargeTrip_GraphQLApp file with the StationListView().

5. Inside the StationListView, add a @StateObject property wrapper to create an instance of StationListViewModel as the ViewModel for this view.

struct StationListView: View {
@StateObject var viewModel = StationListViewModel()

var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundColor(.accentColor)
Text("Hello, world!")
}
.padding()
}
}

struct StationListView_Previews: PreviewProvider {
static var previews: some View {
StationListView()
}
}

Now you have implemented the query and created a SwiftUI view that uses the StationListViewModel. The query will be executed when the StationListViewModel is initialized, and the result will be printed in the console.

Test your query

To test your query, run the app on the simulator. You will notice that errors are printed in the console. This is because you need to authenticate before making requests to the server.

Authenticate your operations

To authenticate your operations with the server, you’ll need to add an interceptor to your ApolloClient instance. This interceptor will add the necessary headers, such as the client ID and app ID, to authenticate the requests.

Let’s delve deeper into the inner workings of ApolloClient in iOS.

The functioning of ApolloClient relies on a component known as NetworkTransport. By default, the client generates an instance of RequestChainNetworkTransport to handle communication with the server via HTTP.

A RequestChain processes your request through an array of ApolloInterceptor objects. These interceptors have the ability to modify the request, check the cache before sending it to the network, and perform additional tasks after receiving a response from the network.

To construct the array of interceptors for each operation executed by the RequestChainNetworkTransport, an object conforming to the InterceptorProvider protocol is utilized. By default, there are a few providers established that return a standard array of interceptors.

The advantage here is that you can also include your own interceptors at any point in the chain to perform custom actions. In this scenario, you want to create an interceptor that adds your keys.

  1. To begin, create a Model to incorporate your keys. Generate a new Swift file called AuthorizationKeys and implement the following struct:
import Foundation

struct AuthorizationKeys {
let clientID: XClientID
let appID: XAppID
init(clientID: String, appID: String){
self.clientID = .init(value: clientID)
self.appID = .init(value: appID)
}
struct XClientID {
let name: String = "x-client-id"
let value: String
}
struct XAppID {
let name: String = "x-app-id"
let value: String
}
}

2. Create a new Swift file called AuthorizationInterceptor. This file will contain the implementation of the interceptor. Paste the following code into the file:

import Foundation
import Apollo

class AuthorizationInterceptor: ApolloInterceptor {
// Any custom interceptors you use are required to be able to identify themselves through an id property.
public var id: String = UUID().uuidString

func interceptAsync<Operation>(
chain: RequestChain,
request: HTTPRequest<Operation>,
response: HTTPResponse<Operation>?,
completion: @escaping (Result<GraphQLResult<Operation.Data>, Error>) -> Void
) where Operation : GraphQLOperation {
let keys = AuthorizationKeys(
clientID: "YOUR_CLIENT_ID",
appID: "YOUR_APP_ID"
)

request.addHeader(name: keys.clientID.name, value: keys.clientID.value)
request.addHeader(name: keys.appID.name, value: keys.appID.value)

chain.proceedAsync(request: request,
response: response,
interceptor: self,
completion: completion)
}
}

3. Create a new Swift file called NetworkInterceptorProvider. This file will subclass the DefaultInterceptorProvider and insert the AuthorizationInterceptor at the beginning of the interceptors array. Paste the following code into the file:

import Foundation
import Apollo

class NetworkInterceptorProvider: DefaultInterceptorProvider {
override func interceptors<Operation>(for operation: Operation) -> [ApolloInterceptor] where Operation : GraphQLOperation {
var interceptors = super.interceptors(for: operation)
interceptors.insert(AuthorizationInterceptor(), at: 0)
return interceptors
}
}

An alternative approach could involve duplicating and inserting the list of interceptors presented by the DefaultInterceptorProvider (which are all accessible), and subsequently positioning your interceptors within the array at the desired locations. Nevertheless, given that we can execute this interceptor as the initial step in this scenario, it is more straightforward to create a subclass.

Get your keys

To get your keys you need to register at the https://account.chargetrip.com/sign-up and then create a new project. Here you need to choose a name and then press Next until you reach the Create button.

ChargeTrip Create new project view
ChargeTrip Create new project view

Here you can now get your keys and replace them in the keys property:

let keys = AuthorizationKeys(
clientID: "YOUR_CLIENT_ID",
appID: "YOUR_APP_ID"
)

Update the Network class to use the interceptor

To update the Network class to use the authentication interceptor, follow these steps:

  1. Open the Network.swift file.
  2. Update the apollo property of the Network class to use the ApolloClient initializer that takes an InterceptorProvider argument. Replace the existing apollo property with the following code:
private(set) lazy var apollo: ApolloClient = {
let networkTransport = RequestChainNetworkTransport(
interceptorProvider: NetworkInterceptorProvider(),
endpointURL: URL(string: "https://api.chargetrip.io/graphql")!
)
return ApolloClient(networkTransport: networkTransport)
}()

The ApolloClient is now initialized with the RequestChainNetworkTransport that uses the NetworkInterceptorProvider as the interceptorProvider. This ensures that the authentication interceptor is applied to the network requests.

By incorporating the authentication interceptor, your app will now send authenticated requests to the server. Now you can run your app and you will see the station list printed in the console.

Adjust the ViewModel to our needs

Now, we need to create some functions to build our UI and the loadStations() method:

import Foundation
import Apollo
import ChargeTripAPI

@MainActor
final class StationListViewModel: ObservableObject {

@Published var stationList: [StationListQuery.Data.StationList] = []

func loadStations() {
Network.shared.apollo.fetch(query: StationListQuery()) { [weak self] result in
guard let self = self else {
return
}

switch result {

case .success(let graphQLResult):
// check the `data` property
if let stationConnection = graphQLResult.data?.stationList {
self.stationList.append(contentsOf: stationConnection.compactMap({ $0 }))
}

if let errors = graphQLResult.errors {
print(errors)
}

case .failure(let error):
print(error)
}

}
}

func address(of station: StationListQuery.Data.StationList) -> String {
var fullAddress = ""
if let address = station.address {
fullAddress.append(address)
}
if let city = station.city {
fullAddress.append(", \\(city)")
}

return fullAddress
}

func ownerName(of station: StationListQuery.Data.StationList) -> String {
station.owner?.name ?? "Unknowed owner"
}

}

In the .success case, you will receive a GraphQLResult. It has both a data attribute and an errors attribute. This is due to GraphQL permitting the retrieval of partial data if it is not null.

Therefore, when you receive a GraphQLResult, it is advisable to examine both the data attribute (to present any obtained results from the server) and the errors attribute (to address any errors received from the server).

Implement the UI

Next, let's implement a simple UI with a .task modifier to load the stations:

import SwiftUI

struct StationListView: View {
// ViewModel instance
@StateObject var viewModel = StationListViewModel()

var body: some View {
VStack(alignment: .leading){
List(viewModel.stationList, id: \\.self){ station in
Section {
Text("Owner name: " + viewModel.ownerName(of: station))
.fontWeight(.bold)

Text("Address: " + viewModel.address(of: station))
.fontWeight(.light)
}
}
}
.task {
viewModel.loadStations()
}
}
}

Conclusion

In this tutorial, we explored how to integrate GraphQL into an iOS app using the Apollo iOS SDK. We covered the steps required to set up the Apollo iOS SDK, authenticate requests, write GraphQL queries, and display the retrieved data in a SwiftUI user interface.

You now have the knowledge to build more complex GraphQL-powered apps and leverage the power of GraphQL to efficiently fetch and manipulate data from a server.

Thanks for reading! If you want to talk or take a coffee, contact me at vincenz.pascarella@gmail.com or connect via LinkedIn.

--

--

Vincenzo Pascarella
Vincenzo Pascarella

Written by Vincenzo Pascarella

I have a BSc in Computer Engineering and a strong passion in technologies and innovations. Now I am an iOS Engineer at Comcast.

Responses (3)