From f67f3553138d7cc9c2c80dfac0553ebbe90ba3de Mon Sep 17 00:00:00 2001 From: Stephen Barlow Date: Wed, 2 Feb 2022 19:20:48 -0800 Subject: [PATCH] Fix tutorial URL and tweak downloading a schema article (#2133) --- docs/source/downloading-schema.md | 12 +- docs/source/initialization.md | 146 +++++++++--------- docs/source/screenshot/schema_location.jpg | Bin 0 -> 55576 bytes .../source/tutorial/tutorial-execute-query.md | 42 ++--- docs/source/tutorial/tutorial-mutations.md | 88 +++++------ .../source/tutorial/tutorial-obtain-schema.md | 4 +- .../source/tutorial/tutorial-subscriptions.md | 36 ++--- 7 files changed, 165 insertions(+), 163 deletions(-) create mode 100644 docs/source/screenshot/schema_location.jpg diff --git a/docs/source/downloading-schema.md b/docs/source/downloading-schema.md index 55e2396fe7..a666c98d5e 100644 --- a/docs/source/downloading-schema.md +++ b/docs/source/downloading-schema.md @@ -2,21 +2,21 @@ title: Downloading a schema --- -Apollo iOS requires a GraphQL schema file as input to the code generation process. A schema file is a JSON file that contains the results of an introspection query. Conventionally this file is called `schema.json`, and you store it next to the `.graphql` files in your target. +Apollo iOS requires a GraphQL schema file as input to its code generation process. You can provide your schema either as a JSON file (most commonly the result of an introspection query) or as a `.graphqls` file that uses GraphQL SDL syntax. Conventionally, this file is named `schema.json` or `schema.graphqls` (depending on its format), and you store it in the same folder as your project's `App`: -> 🚧 BETA ALERT 🚧 : Instead of writing the rest of this in Bash, try using our new [Swift Scripting Library](./swift-scripting), now in Beta! It supports downloading a schema and generating code. +Location of schema file in project -You can use the [Apollo CLI](https://www.apollographql.com/docs/devtools/cli/) to download a GraphQL schema by sending an introspection query to the server. +> 📣 **Check it out:** Instead of writing the scripts below in Bash, try using our new [Swift Scripting Library](./swift-scripting), now in Beta! It supports downloading a schema and generating code. -If you've installed the CLI globally, you can use the following command to download your schema: +You can use the [Apollo CLI](https://www.apollographql.com/docs/devtools/cli/) to download a GraphQL schema by sending an introspection query to the server. If you've installed the CLI globally, you can use the following command to download your schema: ```sh apollo schema:download --endpoint=http://localhost:8080/graphql schema.json ``` -Note that if you're using the local version set up for codegen, you'll want to use the same method you're using in the [Add a code generation build step](/installation/#5-add-a-code-generation-build-step) instructions to access that specific CLI. For example, if you're using CocoaPods, you can set it up like this to download your schema: +Note that if you're using the local version set up for codegen, you should use the same method you're using in the [Add a code generation build step](/installation/#5-add-a-code-generation-build-step) instructions to access that specific CLI. For example, if you're using CocoaPods, you can set it up like this to download your schema: -```sh +```bash SCRIPT_PATH="${PODS_ROOT}/Apollo/scripts" cd "${SRCROOT}/${TARGET_NAME}" "${SCRIPT_PATH}"/run-bundled-codegen.sh schema:download --endpoint=http://localhost:8080/graphql schema.json diff --git a/docs/source/initialization.md b/docs/source/initialization.md index 517e47859b..b1ccf8e3f6 100644 --- a/docs/source/initialization.md +++ b/docs/source/initialization.md @@ -2,37 +2,39 @@ title: Creating a client --- -## Basic Client Creation +Before you can execute GraphQL operations in your app, you need to initialize an `ApolloClient` instance. -In most cases, you'll want to create a single shared instance of `ApolloClient` and point it at your GraphQL server. The easiest way to do this is to create a singleton: +## Basic client creation + +In most cases, you can create a single shared instance of `ApolloClient` and point it at your GraphQL server. The recommended way to do this is to create a singleton: ```swift import Foundation import Apollo class Network { - static let shared = Network() - + static let shared = Network() + private(set) lazy var apollo = ApolloClient(url: URL(string: "http://localhost:8080/graphql")!) } ``` -Under the hood, this will create a client using `RequestChainNetworkTransport` with a default configuration. You can then use this client from anywhere in your code with `Network.shared.apollo`. +Under the hood, this creates a client using `RequestChainNetworkTransport` with a default configuration. You can then use this client from anywhere in your code with `Network.shared.apollo`. -## Advanced Client Creation +## Advanced client creation -For more advanced usage of the client, you can use this initializer which allows you to pass in an object conforming to the `NetworkTransport` protocol, as well as a store: +For more advanced usage of the client, you can use this initializer which allows you to pass in an object conforming to the `NetworkTransport` protocol, as well as a store: ```swift -public init(networkTransport: NetworkTransport, +public init(networkTransport: NetworkTransport, store: ApolloStore) ``` -The available implementations are: +The available implementations are: - **`RequestChainNetworkTransport`**, which passes a request through a chain of interceptors that can do work both before and after going to the network, and uses standard HTTP requests to communicate with the server -- **`WebSocketTransport`**, which will send everything using a web socket. If you're using CocoaPods, make sure to install the `Apollo/WebSocket` sub-spec to access this. -- **`SplitNetworkTransport`**, which will send subscription operations via a web socket and all other operations via HTTP. If you're using CocoaPods, make sure to install the `Apollo/WebSocket` sub-spec to access this. +- **`WebSocketTransport`**, which will send everything using a web socket. If you're using CocoaPods, make sure to install the `Apollo/WebSocket` sub-spec to access this. +- **`SplitNetworkTransport`**, which will send subscription operations via a web socket and all other operations via HTTP. If you're using CocoaPods, make sure to install the `Apollo/WebSocket` sub-spec to access this. ### Using `RequestChainNetworkTransport` @@ -40,96 +42,96 @@ The initializer for `RequestChainNetworkTransport` has several properties which - `interceptorProvider`: The interceptor provider to use when constructing chains for a request. See below for details on interceptor providers. - `endpointURL`: The GraphQL endpoint URL to use for all calls. -- `additionalHeaders`: Any additional headers that should be automatically added to **every** request, such as an API key or a language setting. Headers that should not be sent with every request (or whose values can change across requests) should be configured through an interceptor. Defaults to an empty dictionary. -- `autoPersistQueries`: Pass `true` if [Automatic Persisted Queries](https://www.apollographql.com/docs/apollo-server/performance/apq/) should be used to send an operation's hash instead of the full operation body by default. **NOTE:** To use APQs, you need to make sure to generate your types with operation identifiers. In your Swift Script, make sure to pass a non-nil `operationIDsURL` to have this output. Due to this restriction, this option defaults to `false`. You will also want to make sure you're using the `AutomaticPersistedQueryInterceptor` in your chain after a network request has come back to handle known APQ errors. +- `additionalHeaders`: Any additional headers that should be automatically added to **every** request, such as an API key or a language setting. Headers that should not be sent with every request (or whose values can change across requests) should be configured through an interceptor. Defaults to an empty dictionary. +- `autoPersistQueries`: Pass `true` if [Automatic Persisted Queries](https://www.apollographql.com/docs/apollo-server/performance/apq/) should be used to send an operation's hash instead of the full operation body by default. **NOTE:** To use APQs, you need to make sure to generate your types with operation identifiers. In your Swift Script, make sure to pass a non-nil `operationIDsURL` to have this output. Due to this restriction, this option defaults to `false`. You will also want to make sure you're using the `AutomaticPersistedQueryInterceptor` in your chain after a network request has come back to handle known APQ errors. - `requestCreator`: The `RequestCreator` object to use to build your `URLRequest`. Defaults to the provided `ApolloRequestCreator` implementation. -- `useGETForQueries`: Sends all requests of `query` type using `GET` instead of `POST`. This is mostly useful for large companies taking advantage of CDNs (Content Distribution Networks) that allow local caches instead of going all the way to your server for data which does not change often. This defaults to `false` to preserve existing behavior in older versions of the client. -- `useGETForPersistedQueryRetry`: Pass `true` to use `GET` instead of `POST` for a retry of a persisted query. Defaults to `false`. +- `useGETForQueries`: Sends all requests of `query` type using `GET` instead of `POST`. This is mostly useful for large companies taking advantage of CDNs (Content Distribution Networks) that allow local caches instead of going all the way to your server for data which does not change often. This defaults to `false` to preserve existing behavior in older versions of the client. +- `useGETForPersistedQueryRetry`: Pass `true` to use `GET` instead of `POST` for a retry of a persisted query. Defaults to `false`. ### How the `RequestChain` works -A `RequestChain` is constructed using an array of interceptors, to be run in the order given, and handles calling back on a specified `DispatchQueue` after all work is complete. +A `RequestChain` is constructed using an array of interceptors, to be run in the order given, and handles calling back on a specified `DispatchQueue` after all work is complete. A chain is started by calling `kickoff`. This causes the chain to start running through the chain of interceptors in order. -In each interceptor, work can be performed asynchronously on any thread. To move along to the next interceptor in the chain, call `proceedAsync`. +In each interceptor, work can be performed asynchronously on any thread. To move along to the next interceptor in the chain, call `proceedAsync`. By default, when the interceptor chain ends, if you have a parsed result available, this result will be returned to the caller. If you want to directly return a value to the caller, call `returnValueAsync`. If you want to have the chain return an error, call `handleErrorAsync`. Both of these methods will call your completion block on the queue specified when creating the `RequestChain`. -Note that calling `returnValue` does **NOT** forbid calling `handleError` - or calling each more than once. For example, if you want to return data from the cache to the UI while a network fetch executes, you'd want to make sure that `returnValueAsync` was called twice. +Note that calling `returnValue` does **NOT** forbid calling `handleError` - or calling each more than once. For example, if you want to return data from the cache to the UI while a network fetch executes, you'd want to make sure that `returnValueAsync` was called twice. -The chain also includes a `retry` mechanism, which will go all the way back to the first interceptor in the chain, then start running through the interceptors again. +The chain also includes a `retry` mechanism, which will go all the way back to the first interceptor in the chain, then start running through the interceptors again. **IMPORTANT**: Do not call `retry` blindly. If your server is returning 500s or if the user has no internet, this will create an infinite loop of requests that are retrying (especially if you're not using something like the `MaxRetryInterceptor` to limit how many retries are made). This **will** kill your user's battery, and might also run up the bill on their data plan. Make sure to only request a retry when there's something your code can actually do about the problem! -In the `RequestChainNetworkTransport`, each request creates an individual request chain, and uses an `InterceptorProvider` to figure out which interceptors should be handed to that chain. +In the `RequestChainNetworkTransport`, each request creates an individual request chain, and uses an `InterceptorProvider` to figure out which interceptors should be handed to that chain. ### Setting up `ApolloInterceptor` chains with `InterceptorProvider` -Every operation sent through a `RequestChainNetworkTransport` will be passed into an `InterceptorProvider` before going to the network. This protocol creates an array of interceptors for use by a single request chain based on the provided operation. +Every operation sent through a `RequestChainNetworkTransport` will be passed into an `InterceptorProvider` before going to the network. This protocol creates an array of interceptors for use by a single request chain based on the provided operation. -Interceptors themselves are designed to be **short-lived**. A new set of interceptors should be provided for each request in order to avoid having multiple calls hitting the same instance of a single interceptor at the same time. +Interceptors themselves are designed to be **short-lived**. A new set of interceptors should be provided for each request in order to avoid having multiple calls hitting the same instance of a single interceptor at the same time. Holding references to individual interceptors (outside of test verification) is generally not recommended. Instead, you can create an interceptor that holds on to a longer-lived object, and the provider can pass this object into each new set of interceptors. That way an interceptor itself can be easily disposable, but you don't have to recreate the underlying object doing heavier work. `DefaultInterceptorProvider` is a default implementation of an interceptor provider. It works with our existing parsing and caching system and tries to replicate the experience of using the old `HTTPNetworkTransport` as closely as possible. It takes a `URLSessionClient` and an `ApolloStore` to pass into the interceptors it uses. **This is the provider that developers will want to use at this time.** You can also sublcass this interceptor provider if you only need to insert interceptors at the beginning or end of the chain rather than intersperse them throughout. -If you wish to make your own `InterceptorProvider` instead of using the provided one, you can take advantage of several interceptors that are included in the library: +If you wish to make your own `InterceptorProvider` instead of using the provided one, you can take advantage of several interceptors that are included in the library: #### Pre-network -- `MaxRetryInterceptor` checks to make sure a query has not been tried more than a maximum number of times. +- `MaxRetryInterceptor` checks to make sure a query has not been tried more than a maximum number of times. - `CacheReadInterceptor` reads from a provided `ApolloStore` based on the `cachePolicy`, and will return a result if one is found. -#### Network -- `NetworkFetchInterceptor` takes a `URLSessionClient` and uses it to send the prepared `HTTPRequest` (or subclass thereof) to the server. +#### Network +- `NetworkFetchInterceptor` takes a `URLSessionClient` and uses it to send the prepared `HTTPRequest` (or subclass thereof) to the server. #### Post-Network -- `ResponseCodeInterceptor` checks to make sure a valid response status code has been returned. **NOTE**: Most errors at the GraphQL level are returned with a `200` status code and information in the `errors` array per the GraphQL Spec. This interceptor helps with things like server errors and errors that are returned by middleware. [This article on error handling in GraphQL](https://medium.com/@sachee/200-ok-error-handling-in-graphql-7ec869aec9bc) is a really helpful look at how and why these differences occur. +- `ResponseCodeInterceptor` checks to make sure a valid response status code has been returned. **NOTE**: Most errors at the GraphQL level are returned with a `200` status code and information in the `errors` array per the GraphQL Spec. This interceptor helps with things like server errors and errors that are returned by middleware. [This article on error handling in GraphQL](https://medium.com/@sachee/200-ok-error-handling-in-graphql-7ec869aec9bc) is a really helpful look at how and why these differences occur. - `AutomaticPersistedQueryInterceptor` handles checking responses to see if an error is because an automatic persisted query failed, and the full operation needs to be resent to the server. -- `JSONResponseParsingInterceptor` parses code generated by our current Typescript-based code generation. +- `JSONResponseParsingInterceptor` parses code generated by our current Typescript-based code generation. - `CacheWriteInterceptor` writes to a provided `ApolloStore` based on code from our current Typescript-based code generation. #### The Additional Error Interceptor -The `InterceptorProvider` can optionally provide an `additionalErrorInterceptor` which will get called before returning an error to the caller, regardless of the origin of the error. This is mostly useful for logging and tracing errors. +The `InterceptorProvider` can optionally provide an `additionalErrorInterceptor` which will get called before returning an error to the caller, regardless of the origin of the error. This is mostly useful for logging and tracing errors. -Note that if there is a particular _expected_ error, such as an expired authentication token, that type of error is best handled by having an interceptor within the interceptor chain, which will allow you to retry your request much more easily. +Note that if there is a particular _expected_ error, such as an expired authentication token, that type of error is best handled by having an interceptor within the interceptor chain, which will allow you to retry your request much more easily. ### The URLSessionClient class -Since `URLSession` only supports use in the background using the delegate-based API, we have created our own `URLSessionClient` which handles the basics of setup for that. +Since `URLSession` only supports use in the background using the delegate-based API, we have created our own `URLSessionClient` which handles the basics of setup for that. -One thing to be aware of: Because setting up a delegate is only possible in the initializer for `URLSession`, you can only pass in a `URLSessionConfiguration`, **not** an existing `URLSession`, to this class's initializer. +One thing to be aware of: Because setting up a delegate is only possible in the initializer for `URLSession`, you can only pass in a `URLSessionConfiguration`, **not** an existing `URLSession`, to this class's initializer. By default, instances of `URLSessionClient` use `URLSessionConfiguration.default` to set up their URL session, and instances of `DefaultInterceptorProvider` use the default initializer for `URLSessionClient`. -The `URLSessionClient` class and most of its methods are `open` so you can subclass it if you need to override any of the delegate methods for the `URLSession` delegates we're using or you need to handle additional delegate scenarios. +The `URLSessionClient` class and most of its methods are `open` so you can subclass it if you need to override any of the delegate methods for the `URLSession` delegates we're using or you need to handle additional delegate scenarios. ### Example Advanced Client Setup -Here's a sample how to use an advanced client with some custom interceptors. This code assumes you've got the following classes in your own code (**these are not part of the Apollo library**): +Here's a sample how to use an advanced client with some custom interceptors. This code assumes you've got the following classes in your own code (**these are not part of the Apollo library**): - **`UserManager`** to check whether the user is logged in, perform associated checks on errors and responses to see if they need to renew their token, and perform that renewal - **`Logger`** to handle printing logs based on their level, and which supports `.debug`, `.error`, or `.always` log levels. #### Example interceptors -##### Sample `UserManagementInterceptor` +##### Sample `UserManagementInterceptor` -An interceptor which checks if the user is logged in and then renews the user's token if it is expired asynchronously before continuing the chain, using the above-mentioned `UserManager` class: +An interceptor which checks if the user is logged in and then renews the user's token if it is expired asynchronously before continuing the chain, using the above-mentioned `UserManager` class: ```swift import Apollo class UserManagementInterceptor: ApolloInterceptor { - + enum UserError: Error { case noUserLoggedIn } - + /// Helper function to add the token then move on to the next step private func addTokenAndProceed( _ token: Token, @@ -137,21 +139,21 @@ class UserManagementInterceptor: ApolloInterceptor { chain: RequestChain, response: HTTPResponse?, completion: @escaping (Result, Error>) -> Void) { - + request.addHeader(name: "Authorization", value: "Bearer \(token.value)") chain.proceedAsync(request: request, response: response, completion: completion) } - + func interceptAsync( chain: RequestChain, request: HTTPRequest, response: HTTPResponse?, completion: @escaping (Result, Error>) -> Void) { - + guard let token = UserManager.shared.token else { - // In this instance, no user is logged in, so we want to call + // In this instance, no user is logged in, so we want to call // the error handler, then return to prevent further work chain.handleErrorAsync(UserError.noUserLoggedIn, request: request, @@ -159,7 +161,7 @@ class UserManagementInterceptor: ApolloInterceptor { completion: completion) return } - + // If we've gotten here, there is a token! if token.isExpired { // Call an async method to renew the token @@ -167,11 +169,11 @@ class UserManagementInterceptor: ApolloInterceptor { guard let self = self else { return } - + switch tokenRenewResult { case .failure(let error): - // Pass the token renewal error up the chain, and do - // not proceed further. Note that you could also wrap this in a + // Pass the token renewal error up the chain, and do + // not proceed further. Note that you could also wrap this in a // `UserError` if you want. chain.handleErrorAsync(error, request: request, @@ -198,21 +200,21 @@ class UserManagementInterceptor: ApolloInterceptor { } ``` -##### Sample `RequestLoggingInterceptor` +##### Sample `RequestLoggingInterceptor` An interceptor which logs the outgoing request using the above-mentioned `Logger` class, then moves on: ```swift -import Apollo +import Apollo class RequestLoggingInterceptor: ApolloInterceptor { - + func interceptAsync( chain: RequestChain, request: HTTPRequest, response: HTTPResponse?, completion: @escaping (Result, Error>) -> Void) { - + Logger.log(.debug, "Outgoing request: \(request)") chain.proceedAsync(request: request, response: response, @@ -223,32 +225,32 @@ class RequestLoggingInterceptor: ApolloInterceptor { ##### Sample `‌ResponseLoggingInterceptor` -An interceptor using the above-mentioned `Logger` which logs the incoming response if it exists, and moves on. +An interceptor using the above-mentioned `Logger` which logs the incoming response if it exists, and moves on. -Note that this is an example of an interceptor which can both proceed **and** throw an error - we don't necessarily want to stop processing if this was set up in the wrong place, but we do want to know about it. +Note that this is an example of an interceptor which can both proceed **and** throw an error - we don't necessarily want to stop processing if this was set up in the wrong place, but we do want to know about it. ```swift -import Apollo +import Apollo class ResponseLoggingInterceptor: ApolloInterceptor { - + enum ResponseLoggingError: Error { case notYetReceived } - + func interceptAsync( chain: RequestChain, request: HTTPRequest, response: HTTPResponse?, completion: @escaping (Result, Error>) -> Void) { - + defer { // Even if we can't log, we still want to keep going. chain.proceedAsync(request: request, response: response, completion: completion) } - + guard let receivedResponse = response else { chain.handleErrorAsync(ResponseLoggingError.notYetReceived, request: request, @@ -256,9 +258,9 @@ class ResponseLoggingInterceptor: ApolloInterceptor { completion: completion) return } - + Logger.log(.debug, "HTTP Response: \(receivedResponse.httpResponse)") - + if let stringData = String(bytes: receivedResponse.rawData, encoding: .utf8) { Logger.log(.debug, "Data: \(stringData)") } else { @@ -270,25 +272,25 @@ class ResponseLoggingInterceptor: ApolloInterceptor { #### Example Custom Interceptor Provider -This `InterceptorProvider` uses all of the interceptors that (as of this writing) are in the `DefaultInterceptorProvider`, interspersed at the appropriate points with the sample interceptors created above: +This `InterceptorProvider` uses all of the interceptors that (as of this writing) are in the `DefaultInterceptorProvider`, interspersed at the appropriate points with the sample interceptors created above: ```swift import Foundation -import Apollo +import Apollo struct NetworkInterceptorProvider: InterceptorProvider { - + // These properties will remain the same throughout the life of the `InterceptorProvider`, even though they // will be handed to different interceptors. private let store: ApolloStore private let client: URLSessionClient - + init(store: ApolloStore, client: URLSessionClient) { self.store = store self.client = client } - + func interceptors(for operation: Operation) -> [ApolloInterceptor] { return [ MaxRetryInterceptor(), @@ -312,26 +314,26 @@ This is the equivalent of what you'd set up in the [Basic Client Creation](#basi ```swift import Foundation -import Apollo +import Apollo class Network { static let shared = Network() - + private(set) lazy var apollo: ApolloClient = { // The cache is necessary to set up the store, which we're going to hand to the provider let cache = InMemoryNormalizedCache() let store = ApolloStore(cache: cache) - + let client = URLSessionClient() let provider = NetworkInterceptorProvider(store: store, client: client) - let url = URL(string: "https://apollo-fullstack-tutorial.herokuapp.com/")! + let url = URL(string: "https://apollo-fullstack-tutorial.herokuapp.com/graphql")! let requestChainTransport = RequestChainNetworkTransport(interceptorProvider: provider, endpointURL: url) - - // Remember to give the store you already created to the client so it - // doesn't create one on its own + + // Remember to give the store you already created to the client so it + // doesn't create one on its own return ApolloClient(networkTransport: requestChainTransport, store: store) }() @@ -339,4 +341,4 @@ class Network { ``` -An example of setting up a client which can handle web sockets and subscriptions is included in the [subscription documentation](subscriptions/#sample-subscription-supporting-initializer). +An example of setting up a client which can handle web sockets and subscriptions is included in the [subscription documentation](subscriptions/#sample-subscription-supporting-initializer). diff --git a/docs/source/screenshot/schema_location.jpg b/docs/source/screenshot/schema_location.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c48e9c306f72374ebe8ba88cd1a662cc2087479e GIT binary patch literal 55576 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≻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 literal 0 HcmV?d00001 diff --git a/docs/source/tutorial/tutorial-execute-query.md b/docs/source/tutorial/tutorial-execute-query.md index 9a3f6e79b0..1cc198e02f 100644 --- a/docs/source/tutorial/tutorial-execute-query.md +++ b/docs/source/tutorial/tutorial-execute-query.md @@ -2,15 +2,15 @@ title: "3. Execute your first query" --- -The most common GraphQL operation is the **query**, which requests data from your graph in a structure that conforms to your server's schema. If you return to [the Sandbox](https://studio.apollographql.com/sandbox/explorer?endpoint=https%3A%2F%2Fapollo-fullstack-tutorial.herokuapp.com) for your server, you can see available queries in the Schema Reference tab you opened earlier. +The most common GraphQL operation is the **query**, which requests data from your graph in a structure that conforms to your server's schema. If you return to [the Sandbox](https://studio.apollographql.com/sandbox/explorer?endpoint=https%3A%2F%2Fapollo-fullstack-tutorial.herokuapp.com/graphql) for your server, you can see available queries in the Schema Reference tab you opened earlier. Scroll down to the `launches` query to get details about it: Detail about launches query -Here, you see both the query term itself, the return type, and information about parameters that can be passed to the query. You can use this information to write a query you'll eventually add to your app. +Here, you see both the query term itself, the return type, and information about parameters that can be passed to the query. You can use this information to write a query you'll eventually add to your app. -To start working with this query in the Sandbox Explorer, select the "play" button to the right side of the information: +To start working with this query in the Sandbox Explorer, select the "play" button to the right side of the information: Open in Explorer @@ -18,29 +18,29 @@ This brings you back into Sandbox's Explorer tab with the sidebar on the left sh Docs open in the left sidebar -Notice the small button next to the `launches` icon. Click this button to add the query to the middle "operations" panel: +Notice the small button next to the `launches` icon. Click this button to add the query to the middle "operations" panel: Click the button to add this query -When the query is added, it will look like this: +When the query is added, it will look like this: The query once it's been added to the Operations section -Let's break down what you're seeing here: +Let's break down what you're seeing here: - The type of the operation, `query`, followed by the name of the operation, currently `Query` (we'll make that more specific in a second), is the outermost set of brackets. - The actual query being called is the next set of brackets in. Since the `arguments` for this query both have default values, they are not automatically added to the query for you. -- An error in the empty space between the brackets, which is where you'll put the list of information you want back from each launch. +- An error in the empty space between the brackets, which is where you'll put the list of information you want back from each launch. -The Apollo iOS SDK requires every query to have a name (even though this isn't required by the GraphQL spec). Since you're going to be creating more than one query, it's also a good idea to give this operation a specific name other than `Query`. Change the name of the operation to `LaunchList`: +The Apollo iOS SDK requires every query to have a name (even though this isn't required by the GraphQL spec). Since you're going to be creating more than one query, it's also a good idea to give this operation a specific name other than `Query`. Change the name of the operation to `LaunchList`: Renaming the query -Next, on the left hand side, you can select what fields you want back in the returned object. Start by clicking the button next to the `cursor` field. It will mark that field as selected, then insert it into your operations: +Next, on the left hand side, you can select what fields you want back in the returned object. Start by clicking the button next to the `cursor` field. It will mark that field as selected, then insert it into your operations: After adding the cursor field. -This is probably the easiest way to add fields to your object, since it knows how everything is spelled and what type everything is. +This is probably the easiest way to add fields to your object, since it knows how everything is spelled and what type everything is. However, you can also use auto-complete to help you with this. Add a newline below `cursor` in the Operations panel and start typing `ha`. An autocomplete box pops up and shows you options based on what's in the schema: @@ -50,21 +50,21 @@ The Sandbox Explorer is a great tool for building and verifying queries so you d As the schema indicates, the `launches` query returns a `LaunchConnection` object. This object includes a list of launches, along with fields related to pagination (`cursor` and `hasMore`). The query you've written so far indicates exactly which fields of this `LaunchConnection` object you want to be returned. -Run this query by pressing the "Submit Operation" button, which should now have the name of your query, `LaunchList`: +Run this query by pressing the "Submit Operation" button, which should now have the name of your query, `LaunchList`: Submit the operation -You'll quickly see the query returns results as a JSON object on the right-hand side of the page: +You'll quickly see the query returns results as a JSON object on the right-hand side of the page: Query JSON in Sandbox Explorer This query executes successfully, but it doesn't include any information about the `launches`! That's because we didn't include the necessary field in the query. -Click the button next to the `launches` field at the bottom of the left column. It will add a set of braces for `launches` to the operations section, and then move the documentation to show information for the `Launch` type: +Click the button next to the `launches` field at the bottom of the left column. It will add a set of braces for `launches` to the operations section, and then move the documentation to show information for the `Launch` type: Status after adding launches field -The fields you add in this set of brackets will be fetched for every launch in the list. Click the buttons next to `id` and `site` properties to add those two fields. When you're done, your operation should look like this: +The fields you add in this set of brackets will be fetched for every launch in the list. Click the buttons next to `id` and `site` properties to add those two fields. When you're done, your operation should look like this: ```graphql:title=(Sandbox%20Explorer) query LaunchList { @@ -75,11 +75,11 @@ query LaunchList { id site } - } + } } ``` -Run the operation again, and you'll now see that in addition to the information you got back before, you're also getting a list of launches with their ID and site information: +Run the operation again, and you'll now see that in addition to the information you got back before, you're also getting a list of launches with their ID and site information: Updated query JSON in Sandbox Explorer @@ -93,7 +93,7 @@ Now that your query is fetching the right data, head back to Xcode. 2. Click **Next** and name the file `LaunchList.graphql`. Make sure it's saved at the same level as your `schema.json` file. As previously, don't add it to any target. -3. Copy your final operation from Sandbox Explorer by selecting the three dot (aka "meatball") menu to the right of your operation name and selecting "Copy Operation": +3. Copy your final operation from Sandbox Explorer by selecting the three dot (aka "meatball") menu to the right of your operation name and selecting "Copy Operation": Copy operation from Explorer Sandbox @@ -113,9 +113,9 @@ You're now ready to generate code from the combination of your saved query and s ### The `API.swift` file -Open the `API.swift` file. It defines a root class, `LaunchListQuery`, with many nested structs below it. If you compare the structs to the JSON data returned in Sandbox Explorer, you see that the structure matches. These structs include properties only for the fields that your query requests. +Open the `API.swift` file. It defines a root class, `LaunchListQuery`, with many nested structs below it. If you compare the structs to the JSON data returned in Sandbox Explorer, you see that the structure matches. These structs include properties only for the fields that your query requests. -Try commenting out the `id` property in `LaunchList.graphql` using a `#`, saving, then building again. When the build completes, the innermost `Launch` now only includes the built-in `__typename` and the requested `site` property. +Try commenting out the `id` property in `LaunchList.graphql` using a `#`, saving, then building again. When the build completes, the innermost `Launch` now only includes the built-in `__typename` and the requested `site` property. Uncomment `id` in `LaunchList.graphql` and rebuild to restore the property. @@ -127,7 +127,7 @@ To use the generated operations in `API.swift`, you first create an instance of 1. Create a new Swift file called `Network.swift` and copy the code from [Basic client creation](/initialization/#basic-client-creation) into it. Make sure to add `import Apollo` to the top of the file. -2. Update the URL string to be `https://apollo-fullstack-tutorial.herokuapp.com` instead of the `localhost` URL in the example. +2. Update the URL string to be `https://apollo-fullstack-tutorial.herokuapp.com/graphql` instead of the `localhost` URL in the example. 3. To make sure your `ApolloClient` instance is communicating correctly with the server, add the following code to `AppDelegate.swift` in the `application:didFinishLaunchingWithOptions` method, above `return true`: @@ -142,7 +142,7 @@ To use the generated operations in `API.swift`, you first create an instance of } ``` -Build and run your application. The web host might take a few seconds to spin up your GraphQL server if nobody's been using it recently, but once it's up, you should see a response that resembles the following: +Build and run your application. The web host might take a few seconds to spin up your GraphQL server if nobody's been using it recently, but once it's up, you should see a response that resembles the following: Success log output diff --git a/docs/source/tutorial/tutorial-mutations.md b/docs/source/tutorial/tutorial-mutations.md index cc63c6e3e7..a30466b055 100644 --- a/docs/source/tutorial/tutorial-mutations.md +++ b/docs/source/tutorial/tutorial-mutations.md @@ -6,17 +6,17 @@ In this section, you'll learn how to build authenticated mutations and handle in ## Add authentication handling -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. +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. +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 `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 **TokenAddingInterceptor.swift**, and make sure it's added to the **RocketReserver** target. Open that file, and add the following code: +First, create the new interceptor. Go to **File > New > File...** and create a new **Swift File**. Name it **TokenAddingInterceptor.swift**, and make sure it's added to the **RocketReserver** target. Open that file, and add the following code: ```swift:title=TokenAddingInterceptor.swift import Foundation @@ -28,7 +28,7 @@ class TokenAddingInterceptor: ApolloInterceptor { request: HTTPRequest, response: HTTPResponse?, completion: @escaping (Result, Error>) -> Void) { - + // TODO } } @@ -47,17 +47,17 @@ let keychain = KeychainSwift() if let token = keychain.get(LoginViewController.loginKeychainKey) { request.addHeader(name: "Authorization", value: token) } // else do nothing - + 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. +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 `TokenAddingInterceptor` before the other interceptors provided by the `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 `TokenAddingInterceptor` before the other interceptors provided by the `DefaultInterceptorProvider`: ```swift:title=NetworkInterceptorProvider.swift import Foundation @@ -72,20 +72,20 @@ class NetworkInterceptorProvider: DefaultInterceptorProvider { } ``` -> 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. +> 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. -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: +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/")! + let url = URL(string: "https://apollo-fullstack-tutorial.herokuapp.com/graphql")! let transport = RequestChainNetworkTransport(interceptorProvider: provider, endpointURL: url) return ApolloClient(networkTransport: transport, store: store) @@ -94,7 +94,7 @@ class Network { ``` Now, go back to **TokenAddingInterceptor.swift**. -Click on the line numbers to add a breakpoint at the line where you're instantiating the `Keychain`: +Click on the line numbers to add a breakpoint at the line where you're instantiating the `Keychain`: adding a breakpoint @@ -116,7 +116,7 @@ You can see in the left sidebar that this takes an argument of an array of IDs ( * A `message` string to display to the user * A list of `launches` the current user has booked -Click the plus signs next to `success` and `message` to add those to your operation. +Click the plus signs next to `success` and `message` to add those to your operation. In the "Variables" section of Sandbox Explorer, add an array of identifiers. In this case, we'll use a single identifier to book one trip: @@ -124,11 +124,11 @@ In the "Variables" section of Sandbox Explorer, add an array of identifiers. In {"bookTripsLaunchIds": ["25"]} ``` -Next, directly next to the word "Variables", you'll see the word "Headers". Click that to bring up the headers section. Click the "New Header" button, and add "Authorization" in the header key text box and paste the token you got back in the last section for the value: +Next, directly next to the word "Variables", you'll see the word "Headers". Click that to bring up the headers section. Click the "New Header" button, and add "Authorization" in the header key text box and paste the token you got back in the last section for the value: The headers section -Now, click the Submit Operation button to run your authorized query. You'll get back information regarding the trips (or in this case, trip) you've just booked. +Now, click the Submit Operation button to run your authorized query. You'll get back information regarding the trips (or in this case, trip) you've just booked. > Note: If you receive an error that says "Cannot read property 'id' of null", that means your user was not found based on the token you passed through. Make sure your authorization header is properly formatted and that you're actually logged in! @@ -136,7 +136,7 @@ Now, click the Submit Operation button to run your authorized query. You'll get With a mutation written like this, you can book any number of trips you want at the same time. However, the booking mechanism in our application will only let you book one trip at a time. -Luckily, there's an easy way to update the mutation so it's required to only take a single object. First, update the name of your operation in Explorer to the singular `BookTrip` (and remove `Mutation` since that will be added for us by code generation). Next, update the mutationto take a single `$id`, then pass an array containing that `$id` to the `bookTrips` mutation: +Luckily, there's an easy way to update the mutation so it's required to only take a single object. First, update the name of your operation in Explorer to the singular `BookTrip` (and remove `Mutation` since that will be added for us by code generation). Next, update the mutationto take a single `$id`, then pass an array containing that `$id` to the `bookTrips` mutation: ```graphql:title=(Sandbox%20Explorer) mutation BookTrip($id:ID!) { @@ -147,7 +147,7 @@ mutation BookTrip($id:ID!) { } ``` -This is helpful because the Swift code generation will now generate a method that only accepts a single `ID` instead of an array, but you'll still be calling the same mutation under the hood, without the backend needing to change anything. +This is helpful because the Swift code generation will now generate a method that only accepts a single `ID` instead of an array, but you'll still be calling the same mutation under the hood, without the backend needing to change anything. In the Variables section of Sandbox Explorer, update the JSON dictionary to use `id` as the key, and remove the array brackets from around the identifier: @@ -167,7 +167,7 @@ Build the application to run the code generation. Then, in `DetailViewController private func bookTrip(with id: GraphQLID) { Network.shared.apollo.perform(mutation: BookTripMutation(id: id)) { [weak self] result in guard let self = self else { - return + return } switch result { case .success(let graphQLResult): @@ -188,7 +188,7 @@ private func bookTrip(with id: GraphQLID) { ``` -Then, update the `cancelTrip` method print the ID of the flight being cancelled (you'll be adding the actual cancellation in the next step): +Then, update the `cancelTrip` method print the ID of the flight being cancelled (you'll be adding the actual cancellation in the next step): ```swift:title=DetailViewController.swift private func cancelTrip(with id: GraphQLID) { @@ -197,7 +197,7 @@ private func cancelTrip(with id: GraphQLID) { } ``` -Next, update the `bookOrCancelTapped` method to use the two methods you've just added instead of `print`ing: +Next, update the `bookOrCancelTapped` method to use the two methods you've just added instead of `print`ing: ```swift:title=DetailViewController.swift if launch.isBooked { @@ -207,7 +207,7 @@ if launch.isBooked { } ``` -In `bookTrip`, replace the `TODO` with code to handle what comes back in the `success` property: +In `bookTrip`, replace the `TODO` with code to handle what comes back in the `success` property: ```swift:title=DetailViewController.swift if bookingResult.success { @@ -219,7 +219,7 @@ if bookingResult.success { } ``` -You've now got the code to book a trip. Before you run it, let's add the code to cancel a trip as well. +You've now got the code to book a trip. Before you run it, let's add the code to cancel a trip as well. ## Add the `CancelTrip` mutation @@ -227,13 +227,13 @@ The process for the `CancelTrip` mutation is similar to the one for `BookTrip`. Documentation for the cancel trip mutation -Click the play button to the right to open this operation in Explorer, add a new tab to Explorer for this new operation, then click the plus button to create your operation: +Click the play button to the right to open this operation in Explorer, add a new tab to Explorer for this new operation, then click the plus button to create your operation: Documentation for the cancel trip mutation -Check off `success` and `message` again to add those properties to the list of ones you want to get back with your cancellation information. +Check off `success` and `message` again to add those properties to the list of ones you want to get back with your cancellation information. -Again, Explorer's gotten a little verbose here, so update your operation's name and variables to be a little shorter: +Again, Explorer's gotten a little verbose here, so update your operation's name and variables to be a little shorter: ```graphql:title=(Sandbox%20Explorer) mutation CancelTrip($id: ID!) { @@ -246,7 +246,7 @@ mutation CancelTrip($id: ID!) { One key difference from `bookTrips` is that you're only allowed to cancel one trip at a time because only one `ID!` is accepted as a parameter. -In the Variables section of Sandbox Explorer, you can use the exact same JSON that you used for `BookTrip` (because it also used a single identifier called "id"): +In the Variables section of Sandbox Explorer, you can use the exact same JSON that you used for `BookTrip` (because it also used a single identifier called "id"): ```json:title=(GraphiQL) {"id": "25"} @@ -256,13 +256,13 @@ Make sure that in the Headers section, you add your authorization token again (t The headers section -Click the Submit Operation button to cancel the trip, and you should see a successful request: +Click the Submit Operation button to cancel the trip, and you should see a successful request: Successful cancel trip request It works! Once again, go back to Xcode and create a new empty file, and name it `CancelTrip.graphql`. Paste in the final query from Sandbox Explorer. Build the application without running it to cause the code generation to see this new mutation and generate code for it. -Next, go to the `cancelTrip(with id:)` method in `DetailViewController.swift`. Replace the `print` statement with code that makes the call to cancel the trip: +Next, go to the `cancelTrip(with id:)` method in `DetailViewController.swift`. Replace the `print` statement with code that makes the call to cancel the trip: ```swift:title=DetailViewController.swift Network.shared.apollo.perform(mutation: CancelTripMutation(id: id)) { [weak self] result in @@ -289,33 +289,33 @@ Network.shared.apollo.perform(mutation: CancelTripMutation(id: id)) { [weak self ``` -In `cancelTrip(with id:)`, replace the `TODO` with code to handle what comes back in that mutation's `success` property: +In `cancelTrip(with id:)`, replace the `TODO` with code to handle what comes back in that mutation's `success` property: ```swift:title=DetailViewController.swift if cancelResult.success { - self.showAlert(title: "Trip cancelled", + self.showAlert(title: "Trip cancelled", message: cancelResult.message ?? "Your trip has been officially cancelled.") } else { - self.showAlert(title: "Could not cancel trip", + self.showAlert(title: "Could not cancel trip", message: cancelResult.message ?? "Unknown failure.") } ``` -Build and run the application. Select any launch and try to book it. You'll get a success message, but you'll notice that the UI doesn't update, even if you go out of the detail view and back into it again. +Build and run the application. Select any launch and try to book it. You'll get a success message, but you'll notice that the UI doesn't update, even if you go out of the detail view and back into it again. -Why is that? Because the trip you've got stored locally in your cache still has the old value for `isBooked`. +Why is that? Because the trip you've got stored locally in your cache still has the old value for `isBooked`. There are a number of ways to change this, a couple of which you'll learn in the next section. For now we'll focus on the one that requires the fewest changes to your code: re-fetching the booking info from the network. ## Force a fetch from the network -The `fetch` method of `ApolloClient` provides defaults for most of its parameters, so if you're using the default configuration, the only value you need to provide yourself is the `Query`. +The `fetch` method of `ApolloClient` provides defaults for most of its parameters, so if you're using the default configuration, the only value you need to provide yourself is the `Query`. -However, an important parameter to be aware of is the `cachePolicy`. By default, this has the value of `returnCacheDataElseFetch`, which does essentially what it says on the label: it looks in the current cache (by default an in-memory cache) for data, and fetches it from the network if it's not present. +However, an important parameter to be aware of is the `cachePolicy`. By default, this has the value of `returnCacheDataElseFetch`, which does essentially what it says on the label: it looks in the current cache (by default an in-memory cache) for data, and fetches it from the network if it's not present. -If the data *is* present, the default behavior is to return the local copy to prevent an unnecessary network fetch. However, this is sometimes not the desired behavior (especially after executing a mutation). +If the data *is* present, the default behavior is to return the local copy to prevent an unnecessary network fetch. However, this is sometimes not the desired behavior (especially after executing a mutation). -There are [several different cache policies available to you](../caching/#specifying-a-cache-policy), but the easiest way to absolutely force a refresh from the network that still updates the cache is to use `fetchIgnoringCacheData`. This policy bypasses the cache when going to the network, but it also stores the results of the fetch in the cache for future use. +There are [several different cache policies available to you](../caching/#specifying-a-cache-policy), but the easiest way to absolutely force a refresh from the network that still updates the cache is to use `fetchIgnoringCacheData`. This policy bypasses the cache when going to the network, but it also stores the results of the fetch in the cache for future use. Update the `loadLaunchDetails` method to take a parameter to determine if it should force reload. If it should force reload, update the cache policy from the default `.returnCacheDataElseFetch`, which will return data from the cache if it exists, to `.fetchIgnoringCacheData`: @@ -327,14 +327,14 @@ private func loadLaunchDetails(forceReload: Bool = false) { // This is the launch we're already displaying, or the ID is nil. return } - + let cachePolicy: CachePolicy if forceReload { cachePolicy = .fetchIgnoringCacheData } else { cachePolicy = .returnCacheDataElseFetch - } - + } + Network.shared.apollo.fetch(query: LaunchDetailsQuery(launchId: launchID), cachePolicy: cachePolicy) { [weak self] result in // (Handling of the network call's completion remains the same) } @@ -345,8 +345,8 @@ Next, add the following line to **both** the `bookingResult.success` and `cancel ```swift:title=DetailViewController.swift self.loadLaunchDetails(forceReload: true) -``` +``` -Run the application. When you book or cancel a trip, the application will fetch the updated state and update the UI with the correct state. When you go out and back in, the cache will be updated with the most recent state, and the most recent state will display. +Run the application. When you book or cancel a trip, the application will fetch the updated state and update the UI with the correct state. When you go out and back in, the cache will be updated with the most recent state, and the most recent state will display. -In the next section, you'll learn how to use [subscriptions](/tutorial/tutorial-subscriptions/) with the Apollo client. +In the next section, you'll learn how to use [subscriptions](/tutorial/tutorial-subscriptions/) with the Apollo client. diff --git a/docs/source/tutorial/tutorial-obtain-schema.md b/docs/source/tutorial/tutorial-obtain-schema.md index eaff6ab827..db5a4c1377 100644 --- a/docs/source/tutorial/tutorial-obtain-schema.md +++ b/docs/source/tutorial/tutorial-obtain-schema.md @@ -6,7 +6,7 @@ This tutorial uses a modified version of the GraphQL server you build as part of The Sandbox query explorer -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: +You'll know that this Sandbox instance is pointed at our server because its URL, `https://apollo-fullstack-tutorial.herokuapp.com/graphql`, is in the box at the top left of the page. If Sandbox is properly connected, you'll see a green dot: A closeup of the URL box with a dot indicating it's connected @@ -51,7 +51,7 @@ To use the Apollo CLI from Xcode, add a **Run Script** build phase to your app: 7. Before the script can generate code, it needs a local copy of your GraphQL server's schema. For now, using a `#`, **comment out the last line** of the script you pasted and add the following line below it: ``` - "${SCRIPT_PATH}"/run-bundled-codegen.sh schema:download --endpoint="https://apollo-fullstack-tutorial.herokuapp.com/" + "${SCRIPT_PATH}"/run-bundled-codegen.sh schema:download --endpoint="https://apollo-fullstack-tutorial.herokuapp.com/graphql" ``` This line runs the Apollo CLI's `schema:download` command, which downloads the schema to a `schema.json` file at the same level of your project as the `AppDelegate.swift` file. diff --git a/docs/source/tutorial/tutorial-subscriptions.md b/docs/source/tutorial/tutorial-subscriptions.md index 7cb0af1d45..6d5eb70fa9 100644 --- a/docs/source/tutorial/tutorial-subscriptions.md +++ b/docs/source/tutorial/tutorial-subscriptions.md @@ -2,12 +2,12 @@ title: "9. Write your first subscription" --- -In this section, you will use subscriptions to get notified whenever someone books a flight 🚀! [Subscriptions](https://graphql.org/blog/subscriptions-in-graphql-and-relay/) allow you to be notified in real time whenever an event happens on your server. The [fullstack backend](https://apollo-fullstack-tutorial.herokuapp.com) supports subscriptions based on [WebSockets](https://en.wikipedia.org/wiki/WebSocket). +In this section, you will use subscriptions to get notified whenever someone books a flight 🚀! [Subscriptions](https://graphql.org/blog/subscriptions-in-graphql-and-relay/) allow you to be notified in real time whenever an event happens on your server. The [fullstack backend](https://apollo-fullstack-tutorial.herokuapp.com/graphql) supports subscriptions based on [WebSockets](https://en.wikipedia.org/wiki/WebSocket). ## Write your subscription -Open your [Sandbox](https://studio.apollographql.com/sandbox/explorer?endpoint=https%3A%2F%2Fapollo-fullstack-tutorial.herokuapp.com) back up, click on the Schema tab at the far left. In addition to `queries` and `mutations`, you will see a third type of operations, `subscriptions`. Click on subscriptions to see the `tripsBooked` subscription: +Open your [Sandbox](https://studio.apollographql.com/sandbox/explorer?endpoint=https%3A%2F%2Fapollo-fullstack-tutorial.herokuapp.com/graphql) back up, click on the Schema tab at the far left. In addition to `queries` and `mutations`, you will see a third type of operations, `subscriptions`. Click on subscriptions to see the `tripsBooked` subscription: The definition of tripsBooked in the schema @@ -57,11 +57,11 @@ subscription TripsBooked { } ``` -Build your project, and the subscription will be picked up and added to your `API.swift` file. +Build your project, and the subscription will be picked up and added to your `API.swift` file. ## Configure your ApolloClient to use subscriptions -In `Network.swift`, you'll need to set up a transport which supports subscriptions in addition to general network usage. In practice, this means adding a `WebSocketTransport` which will allow real-time communication with your server. +In `Network.swift`, you'll need to set up a transport which supports subscriptions in addition to general network usage. In practice, this means adding a `WebSocketTransport` which will allow real-time communication with your server. First, at the top of the file, add an import for the **ApolloWebSocket** framework to get access to the classes you'll need: @@ -69,7 +69,7 @@ First, at the top of the file, add an import for the **ApolloWebSocket** framewo import ApolloWebSocket ``` -Next, in the lazy declaration of the `apollo` variable, immediately after `transport` is declared, set up what you need to add subscription support to your client: +Next, in the lazy declaration of the `apollo` variable, immediately after `transport` is declared, set up what you need to add subscription support to your client: ```swift:title=Network.swift // 1 @@ -82,16 +82,16 @@ let webSocketTransport = WebSocketTransport(websocket: webSocket) let splitTransport = SplitNetworkTransport(uploadingNetworkTransport: transport, webSocketNetworkTransport: webSocketTransport) -// 4 +// 4 return ApolloClient(networkTransport: splitTransport, store: store) ``` -What's happening here? +What's happening here? 1. You've created a web socket with the server's web socket URL - `wss://` is the protocol for a secure web socket. -2. You've created a `WebSocketTransport`, which allows the Apollo SDK to communicate with the web socket. -3. You've created a `SplitNetworkTransport`, which can decide whether to use a web socket or not automatically, with both the `RequestChainNetworkTransport` you had previously set up, and the `WebSocketTransport` you just set up. -4. You're now passing the `splitTransport` into the `ApolloClient`, so that it's the main transport being used in your `ApolloClient`. +2. You've created a `WebSocketTransport`, which allows the Apollo SDK to communicate with the web socket. +3. You've created a `SplitNetworkTransport`, which can decide whether to use a web socket or not automatically, with both the `RequestChainNetworkTransport` you had previously set up, and the `WebSocketTransport` you just set up. +4. You're now passing the `splitTransport` into the `ApolloClient`, so that it's the main transport being used in your `ApolloClient`. Now, you're ready to actually use your subscription! @@ -124,7 +124,7 @@ private func startSubscription() { } } } - } + } } private func handleSubscriptionEvent() { @@ -132,7 +132,7 @@ private func handleSubscriptionEvent() { } ``` -Finally, add a line to `viewDidLoad` which actually starts the subscription: +Finally, add a line to `viewDidLoad` which actually starts the subscription: ```swift:title=LaunchesViewController.swift override func viewDidLoad() { @@ -142,19 +142,19 @@ override func viewDidLoad() { } ``` -Build and run your app and go back to Sandbox Explorer, and select the tab where you set up the `BookTrip` mutation. Book a new trip while your app is open, you'll see a log print out: +Build and run your app and go back to Sandbox Explorer, and select the tab where you set up the `BookTrip` mutation. Book a new trip while your app is open, you'll see a log print out: ``` Trips booked: 1 ``` -Cancel that same trip, and you'll see another log: +Cancel that same trip, and you'll see another log: ``` Trips booked: -1 ``` -Now, let's display that information in a view! Replace the `print` statement in `handleTripsBooked` with code to use the included `NotificationView` to show a brief alert at the bottom of the screen with information about a trip being booked or cancelled: +Now, let's display that information in a view! Replace the `print` statement in `handleTripsBooked` with code to use the included `NotificationView` to show a brief alert at the bottom of the screen with information about a trip being booked or cancelled: ```swift:title=LaunchesViewController.swift private func handleTripsBooked(value: Int) { @@ -169,18 +169,18 @@ private func handleTripsBooked(value: Int) { message: " Subscription returned unexpected value: \(value)") return } - + NotificationView.show(in: self.navigationController!.view, with: message, for: 4.0) } ``` -Build and run the application to your simulator, then use Studio to send bookings and cancellations again, and your iOS app should see some shiny new notifications pop up: +Build and run the application to your simulator, then use Studio to send bookings and cancellations again, and your iOS app should see some shiny new notifications pop up: A new trip was booked (rocket) -And you've done it! You've completed the tutorial. +And you've done it! You've completed the tutorial. ## More resources