Skip to content

Commit

Permalink
Adding new iOS Tutorial (#3019)
Browse files Browse the repository at this point in the history
Co-authored-by: Calvin Cestari <calvincestari@users.noreply.github.com>
  • Loading branch information
BobaFetters and calvincestari committed May 17, 2023
1 parent a21503f commit fc3f3ba
Show file tree
Hide file tree
Showing 83 changed files with 1,697 additions and 1 deletion.
18 changes: 17 additions & 1 deletion docs/source/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,23 @@
"v1.2": "/migrations/1.2"
},
"Tutorial": {
"Code Generation": "/tutorial/codegen-getting-started"
"Code Generation": "/tutorial/codegen-getting-started",
"Build a project with Apollo": {
"Introduction": "/tutorial/tutorial-introduction",
"1. Configure your project": "/tutorial/tutorial-configure-project",
"2. Add the GraphQL schema": "/tutorial/tutorial-add-graphql-schema",
"3. Write your first query": "/tutorial/tutorial-write-your-first-query",
"4. Running code generation": "/tutorial/tutorial-running-code-generation",
"5. Execute your first query": "/tutorial/tutorial-execute-first-query",
"6. Connect your queries to your UI": "/tutorial/tutorial-connect-queries-to-ui",
"7. Add more info to the list": "/tutorial/tutorial-add-more-info-to-list",
"8. Paginate results": "/tutorial/tutorial-paginate-results",
"9. Complete the details view": "/tutorial/tutorial-complete-details-view",
"10. Write your first mutation": "/tutorial/tutorial-first-mutation",
"11. Authenticate your operations": "/tutorial/tutorial-authenticate-operations",
"12. Define additional mutations": "/tutorial/tutorial-define-additional-mutations",
"13. Write your first subscription": "/tutorial/tutorial-subscriptions"
}
},
"API Reference": {
"Overview": "https://www.apollographql.com/docs/ios/docc/documentation/index",
Expand Down
Binary file added docs/source/tutorial/images/add_info_to_list.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/source/tutorial/images/explorer_cursor.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/source/tutorial/images/explorer_new_tab.png
Binary file added docs/source/tutorial/images/launches_detail.png
Binary file added docs/source/tutorial/images/next_minor.png
Binary file added docs/source/tutorial/images/sandbox_green_dot.png
Binary file added docs/source/tutorial/images/sandbox_landing.png
Binary file added docs/source/tutorial/images/schema_icon.png
Binary file added docs/source/tutorial/images/select_libs.png
Binary file added docs/source/tutorial/images/tap_to_load_more.png
71 changes: 71 additions & 0 deletions docs/source/tutorial/tutorial-add-graphql-schema.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
---
title: "2. Add the GraphQL Schema"
---

import SPMXcodeInstallCLI from "../../shared/cli-install/spm-xcode.mdx"

This tutorial uses a modified version of the GraphQL server you build as part of [the Apollo full-stack tutorial](https://www.apollographql.com/docs/tutorial/introduction/). You can visit [that server's Apollo Studio Sandbox Explorer](https://studio.apollographql.com/sandbox/explorer?endpoint=https%3A%2F%2Fapollo-fullstack-tutorial.herokuapp.com%2Fgraphql) to explore its schema without needing to be logged in:

<img src="images/sandbox_landing.png" alt="The Sandbox query explorer" class="screenshot"/>

You'll know that this Sandbox instance is pointed at our server because its URL, `https://apollo-fullstack-tutorial.herokuapp.com`, is in the box at the top left of the page. If Sandbox is properly connected, you'll see a green dot:

<img src="images/sandbox_green_dot.png" alt="A closeup of the URL box with a dot indicating it's connected" class="screenshot"/>

The schema defines which GraphQL operations your server can execute. At the top left, click the schema icon to get an overview of your schema:

<img src="images/schema_icon.png" alt="The schema icon to click" class="screenshot"/>

In the **Reference** tab, you can now see a list of all of the things available to you as a consumer of this API, along with available fields on all objects:

<img src="images/sandbox_schema_reference.png" alt="Apollo sandbox showing the schema reference" class="screenshot"/>

## Setup Codegen CLI

<SPMXcodeInstallCLI />

> **Note:** Xcode 14.3 has a bug where the `Install CLI` plugin command does not show up in the menu when right-clicking on your project which is being tracked [here](https://github.com/apollographql/apollo-ios/issues/2919). If you experience this issue an alternative is to use another version of Xcode, or follow the instructions to get a pre-built binary of the CLI on the [Codegen CLI](https://www.apollographql.com/docs/ios/code-generation/codegen-cli#installation) page.
## Create your Codegen Configuration

Next we need to setup our [codegen configuration](https://www.apollographql.com/docs/ios/code-generation/codegen-configuration) file. To do this run the following command in Terminal from project directory:

```bash
./apollo-ios-cli init --schema-namespace RocketReserverAPI --module-type swiftPackageManager
```

This generates a basic `apollo-codegen-config.json` file for our project.

## Download your server's schema

Next we need to download the schema for our project to use. To do so, first we need to update our `apollo-codegen-config.json` to include a [`schemeDownloadConfiguration`](https://www.apollographql.com/docs/ios/code-generation/codegen-configuration#schema-download-configuration). Add the following JSON to the end of the config file after the `output` object:

```json
"schemaDownloadConfiguration": {
"downloadMethod": {
"introspection": {
"endpointURL": "https://apollo-fullstack-tutorial.herokuapp.com/graphql",
"httpMethod": {
"POST": {}
},
"includeDeprecatedInputValues": false,
"outputFormat": "SDL"
}
},
"downloadTimeout": 60,
"headers": [],
"outputPath": "./graphql/schema.graphqls"
}
```

> For more information about downloading schemas, see the [Downloading a Schema](https://www.apollographql.com/docs/ios/code-generation/downloading-schema) documentation.
Now that we have updated our config, we can download the schema by running the following command in Terminal:

```bash
./apollo-ios-cli fetch-schema
```

After running this command you should see a `graphql` folder in your project directory containing a `schema.graphqls` file.

In the next step you will [write your first query.](tutorial-write-your-first-query)
80 changes: 80 additions & 0 deletions docs/source/tutorial/tutorial-add-more-info-to-list.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
---
title: "7. Add more info to the list"
---

Go back to `LaunchList.graphql`. Your query is already fetching most of the information you want to display, but it would be nice to display both the name of the mission and an image of the patch.

Looking at the schema in Sandbox Explorer, you can see that `Launch` has a property of `mission`, which allows you to get details of the mission. A mission has both a `name` and a `missionPatch` property, and the `missionPatch` can optionally take a parameter about what size something needs to be.

Because loading a list view with large images can impact performance, ask for the name and a `SMALL` mission patch. Update your query to look like the following:

```graphql title="LaunchList.graphql"
query LaunchList {
launches {
hasMore
cursor
launches {
id
site
mission {
name
missionPatch(size: SMALL)
}
}
}
}
```

When you re-run code generation if you look in `LaunchListQuery.graphql.swift`, you'll see a new nested type, `Mission`, with the two properties you requested.

Any GraphQL field can take arguments like `missionPatch` above, and arguments can be of scalar or complex types. In this case, `SMALL` is an enum in the GraphQL schema. It can take a finite list of values. If you look at the Schema section in Sandbox, you can see a list of the enums. You can then click in to see that `PatchSize` can only take two values: `SMALL` and `LARGE`

<img src="images/sandbox_patch_size_docs.png" alt="The patch size enum in Sandbox's Schema tab" class="screenshot"/>

## Connect the data to the UI

Go to `LaunchRow.swift` and add the following import to the top of the file:

```swift title="LaunchRow.swift"
import RocketReserverAPI
import SDWebImageSwiftUI // highlight-line
import SwiftUI
```

Next replace the existing `placeholderImg` reference with the following code:

```swift title="LaunchRow.swift"
if let missionPatch = launch.mission?.missionPatch {
WebImage(url: URL(string: missionPatch))
.resizable()
.placeholder(placeholderImg)
.indicator(.activity)
.scaledToFit()
.frame(width: 50, height: 50)
} else {
placeholderImg
.resizable()
.scaledToFit()
.frame(width: 50, height: 50)
}
```

Finally let's update the text label for the mission name:

```swift title="LaunchRow.swift"
VStack(alignment: .leading) {
Text(launch.mission?.name ?? "Mission Name") // highlight-line
Text(launch.site ?? "Launch Site")
.font(.system(size: 14))
}
```

## Test your query

Build and run the application, and you will see all the information for current launches.

<img src="images/add_info_to_list.png" alt="Add info to List" class="screenshot"/>

If you scroll down, you'll see the list includes only about 20 launches. This is because the list of launches is **paginated**, and you've only fetched the first page.

Next, you will [use a cursor-based loading system to load the entire list of launches](tutorial-paginate-results).
115 changes: 115 additions & 0 deletions docs/source/tutorial/tutorial-authenticate-operations.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
---
title: "11. Authenticate your operations"
---

In this section you will learn how to add your login authorization token to your operations.

## Create the `AuthorizationInterceptor`

Before you can book a trip, you need to be able to pass your authentication token along to the example server. To do that, let's dig a little deeper into how iOS's `ApolloClient` works.

The `ApolloClient` uses something called a `NetworkTransport` under the hood. By default, the client creates a `RequestChainNetworkTransport` instance to handle talking over HTTP to your server.

A `RequestChain` runs your request through an array of `ApolloInterceptor` objects which can mutate the request and/or check the cache before it hits the network, and then do additional work after a response is received from the network.

The `RequestChainNetworkTransport` uses an object that conforms to the `InterceptorProvider` protocol in order to create that array of interceptors for each operation it executes. There are a couple of providers that are set up by default, which return a fairly standard array of interceptors.

The nice thing is that you can also add your own interceptors to the chain anywhere you need to perform custom actions. In this case, you want to have an interceptor that will add your token.

First, create the new interceptor. Go to **File > New > File...** and create a new **Swift File**. Name it **AuthorizationInterceptor.swift**, and make sure it's added to the **RocketReserver** target. Open that file, and add the following code:

```swift title="AuthorizationInterceptor.swift"
import Foundation
import Apollo
import ApolloAPI

class AuthorizationInterceptor: ApolloInterceptor {

func interceptAsync<Operation>(
chain: RequestChain,
request: HTTPRequest<Operation>,
response: HTTPResponse<Operation>?,
completion: @escaping (Result<GraphQLResult<Operation.Data>, Error>) -> Void
) where Operation : GraphQLOperation {
// TODO
}

}
```

Next, import `KeychainSwift` at the top of the file so you can access the key you stored in the keychain in the last step of the tutorial:

```swift title="AuthorizationInterceptor.swift"
import KeychainSwift
```

Then, replace the `TODO` within the `interceptAsync` method with code to get the token from the keychain, and add it to your headers if it exists:

```swift title="AuthorizationInterceptor.swift"
let keychain = KeychainSwift()
if let token = keychain.get(LoginView.loginKeychainKey) {
request.addHeader(name: "Authorization", value: token)
}

chain.proceedAsync(request: request,
response: response,
completion: completion)
```

An array of `ApolloInterceptor`s which are handed off to each request to perform in order is set up by an object conforming to the `InterceptorProvider` protocol. There's a `DefaultInterceptorProvider` which has an array with most of the Interceptors you'd want to use.

You can also make your own object conforming to `InterceptorProvider` - or, in this case, since the interceptor only needs to be added to the beginning of the list to run before all the other interceptors, you can subclass the existing `DefaultInterceptorProvider`.

Go to **File > New > File...** and create a new **Swift File**. Name it **NetworkInterceptorProvider.swift**, and make sure it's added to the **RocketReserver** target. Add code which inserts your `AuthorizationInterceptor` before the other interceptors provided by the `DefaultInterceptorProvider`:

```swift title="NetworkInterceptorProvider.swift"
import Foundation
import Apollo
import ApolloAPI

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
}

}
```

> Another way to do this would be to copy and paste the list interceptors provided by the `DefaultInterceptorProvider` (which are all public), and then place your interceptors in the points in the array where you want them. However, since in this case we can run this interceptor first, it's simpler to subclass.
## Use the interceptor

Next, go back to your `Network` class. Replace the `ApolloClient` with an updated `lazy var` which creates the `RequestChainNetworkTransport` manually, using your custom interceptor provider:

```swift title="Network.swift"
class Network {

static let shared = Network()

private(set) lazy var apollo: ApolloClient = {
let client = URLSessionClient()
let cache = InMemoryNormalizedCache()
let store = ApolloStore(cache: cache)
let provider = NetworkInterceptorProvider(client: client, store: store)
let url = URL(string: "https://apollo-fullstack-tutorial.herokuapp.com/graphql")!
let transport = RequestChainNetworkTransport(interceptorProvider: provider, endpointURL: url)

return ApolloClient(networkTransport: transport, store: store)
}()

}
```

Now, go back to **AuthorizationInterceptor.swift**.
Click on the line numbers to add a breakpoint at the line where you're instantiating the `Keychain`:

```swift title="AuthorizationInterceptor.swift"
let keychain = KeychainSwift()
```

Build and run the application. Whenever a network request goes out, that breakpoint should now get hit. If you're logged in, your token will be sent to the server whenever you make a request!

Now that your operations are being authenticated, it's time to [define additional mutations](tutorial-define-additional-mutations) to be able to book and cancel trips.

0 comments on commit fc3f3ba

Please sign in to comment.