-
Notifications
You must be signed in to change notification settings - Fork 700
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Co-authored-by: Calvin Cestari <calvincestari@users.noreply.github.com>
- Loading branch information
1 parent
a21503f
commit fc3f3ba
Showing
83 changed files
with
1,697 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
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
BIN
+33.2 KB
docs/source/tutorial/images/explorer_launch_list_initial_response.png
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Binary file added
BIN
+188 KB
docs/source/tutorial/images/simulator_subscription_notification_view.png
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
115
docs/source/tutorial/tutorial-authenticate-operations.mdx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
Oops, something went wrong.