diff --git a/.circleci/config.yml b/.circleci/config.yml index b0c1e40a00..ea2b8a8bee 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -104,16 +104,6 @@ commands: working_directory: Tests/CodegenCLITests/pod-install-test/ command: ./Pods/Apollo/apollo-ios-cli generate name: CocoaPods - CLI Test (generate) - swiftpm_plugin_test: - steps: - - run: - working_directory: Tests/CodegenCLITests/swiftpm-test/ - command: swift build - name: SwiftPM - Build package - - run: - working_directory: Tests/CodegenCLITests/swiftpm-test/ - command: swift package plugin --allow-writing-to-package-directory apollo-generate --verbose - name: SwiftPM - Plugin Test (generate) save-xcodebuild-artifacts: description: Save artifacts logs, crash reports and test results generated by xcodebuild steps: @@ -248,7 +238,6 @@ jobs: steps: - common_test_setup - cocoapods_install_test - - swiftpm_plugin_test workflows: version: 2 diff --git a/Apollo.xcodeproj/project.pbxproj b/Apollo.xcodeproj/project.pbxproj index d405c0b565..9f8c482c65 100644 --- a/Apollo.xcodeproj/project.pbxproj +++ b/Apollo.xcodeproj/project.pbxproj @@ -724,7 +724,6 @@ E6203346284F252A00A291D1 /* MockUnionsFileGeneratorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6203345284F252A00A291D1 /* MockUnionsFileGeneratorTests.swift */; }; E6203348284F25DF00A291D1 /* MockUnionsTemplateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6203347284F25DF00A291D1 /* MockUnionsTemplateTests.swift */; }; E623FD2A2797A6F4008B4CED /* InterfaceTemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E623FD292797A6F4008B4CED /* InterfaceTemplate.swift */; }; - E639F20528F0A8FA00441FF4 /* IgnoredOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E639F20428F0A8FA00441FF4 /* IgnoredOptions.swift */; }; E63F0C0328EE0F2A009069EA /* ApolloCodegenFrontendBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = E63F0C0028EE099A009069EA /* ApolloCodegenFrontendBundle.swift */; }; E64F226D28B8B3FE0011292F /* LogLevelSetter.swift in Sources */ = {isa = PBXBuildFile; fileRef = E64F226C28B8B3FE0011292F /* LogLevelSetter.swift */; }; E64F227128B8BEE10011292F /* MockLogLevelSetter.swift in Sources */ = {isa = PBXBuildFile; fileRef = E64F227028B8BEE10011292F /* MockLogLevelSetter.swift */; }; @@ -1882,7 +1881,6 @@ E6203347284F25DF00A291D1 /* MockUnionsTemplateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockUnionsTemplateTests.swift; sourceTree = ""; }; E623FD292797A6F4008B4CED /* InterfaceTemplate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InterfaceTemplate.swift; sourceTree = ""; }; E623FD2B2797A700008B4CED /* InterfaceTemplateTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InterfaceTemplateTests.swift; sourceTree = ""; }; - E639F20428F0A8FA00441FF4 /* IgnoredOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IgnoredOptions.swift; sourceTree = ""; }; E63F0C0028EE099A009069EA /* ApolloCodegenFrontendBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApolloCodegenFrontendBundle.swift; sourceTree = ""; }; E64F226C28B8B3FE0011292F /* LogLevelSetter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogLevelSetter.swift; sourceTree = ""; }; E64F227028B8BEE10011292F /* MockLogLevelSetter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockLogLevelSetter.swift; sourceTree = ""; }; @@ -3977,7 +3975,6 @@ isa = PBXGroup; children = ( E687B3CC28B398E600A9551C /* InputOptions.swift */, - E639F20428F0A8FA00441FF4 /* IgnoredOptions.swift */, ); path = OptionGroups; sourceTree = ""; @@ -5749,7 +5746,6 @@ E687B3E828B398E600A9551C /* SchemaDownloadProvider.swift in Sources */, E64F226D28B8B3FE0011292F /* LogLevelSetter.swift in Sources */, E687B3E428B398E600A9551C /* Initialize.swift in Sources */, - E639F20528F0A8FA00441FF4 /* IgnoredOptions.swift in Sources */, E687B3E128B398E600A9551C /* String+Data.swift in Sources */, E687B3DD28B398E600A9551C /* InputOptions.swift in Sources */, E687B3E728B398E600A9551C /* CodegenProvider.swift in Sources */, diff --git a/Package.swift b/Package.swift index 42424eef9d..6d9c331bda 100644 --- a/Package.swift +++ b/Package.swift @@ -20,9 +20,6 @@ let package = Package( .library(name: "ApolloWebSocket", targets: ["ApolloWebSocket"]), .library(name: "ApolloTestSupport", targets: ["ApolloTestSupport"]), .executable(name: "apollo-ios-cli", targets: ["apollo-ios-cli"]), - .plugin(name: "ApolloCodegenPlugin-Initialize", targets: ["ApolloCodegenPlugin-Initialize"]), - .plugin(name: "ApolloCodegenPlugin-Fetch", targets: ["ApolloCodegenPlugin-Fetch"]), - .plugin(name: "ApolloCodegenPlugin-Generate", targets: ["ApolloCodegenPlugin-Generate"]), ], dependencies: [ .package( @@ -106,40 +103,16 @@ let package = Package( "Info.plist", ]), .plugin( - name: "ApolloCodegenPlugin-Initialize", + name: "InstallCLI", capability: .command( intent: .custom( - verb: "apollo-initialize-codegen-config", - description: "Initialize a new code generation configuration with defaults."), + verb: "apollo-cli-install", + description: "Installs the Apollo iOS Command line interface."), permissions: [ - .writeToPackageDirectory(reason: "Adds a codegen JSON configuration file.") + .writeToPackageDirectory(reason: "Creates a symbolic link to the CLI executable in your project directory.") ]), dependencies: [ "apollo-ios-cli" - ]), - .plugin( - name: "ApolloCodegenPlugin-Fetch", - capability: .command( - intent: .custom( - verb: "apollo-fetch-schema", - description: "Download a GraphQL schema from the Apollo Registry or via GraphQL introspection."), - permissions: [ - .writeToPackageDirectory(reason: "Downloads the GraphQL schema to a file.") - ]), - dependencies: [ - "apollo-ios-cli" - ]), - .plugin( - name: "ApolloCodegenPlugin-Generate", - capability: .command( - intent: .custom( - verb: "apollo-generate", - description: "Generate Swift code for the configured GraphQL schema and operations."), - permissions: [ - .writeToPackageDirectory(reason: "Generates Swift files for the schema and operations.") - ]), - dependencies: [ - "apollo-ios-cli" - ]), + ]) ] ) diff --git a/Plugins/ApolloCodegenPlugin-Fetch/ApolloCodegenPluginFetch.swift b/Plugins/ApolloCodegenPlugin-Fetch/ApolloCodegenPluginFetch.swift deleted file mode 100644 index 31125e9c48..0000000000 --- a/Plugins/ApolloCodegenPlugin-Fetch/ApolloCodegenPluginFetch.swift +++ /dev/null @@ -1,22 +0,0 @@ -import Foundation -import PackagePlugin - -@main struct ApolloCodegenPluginFetch: CodegenCommand { - static var commandName: String = "fetch-schema" -} - -extension ApolloCodegenPluginFetch: CommandPlugin { - func performCommand(context: PluginContext, arguments: [String]) async throws { - try performCommand(contextProvider: context, arguments: arguments) - } -} - -#if canImport(XcodeProjectPlugin) -import XcodeProjectPlugin - -extension ApolloCodegenPluginFetch: XcodeCommandPlugin { - func performCommand(context: XcodePluginContext, arguments: [String]) throws { - try performCommand(contextProvider: context, arguments: arguments) - } -} -#endif diff --git a/Plugins/ApolloCodegenPlugin-Fetch/Symbolic Links/README.md b/Plugins/ApolloCodegenPlugin-Fetch/Symbolic Links/README.md deleted file mode 100644 index 3c1c8074d2..0000000000 --- a/Plugins/ApolloCodegenPlugin-Fetch/Symbolic Links/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# Symbolic Links - -This directory contains symbolic links to shared source code used by the plugin. -This is a workaround until SwiftPM has native support for sharing code between plugins. diff --git a/Plugins/ApolloCodegenPlugin-Fetch/Symbolic Links/SharedPackageFiles b/Plugins/ApolloCodegenPlugin-Fetch/Symbolic Links/SharedPackageFiles deleted file mode 120000 index caaaf01585..0000000000 --- a/Plugins/ApolloCodegenPlugin-Fetch/Symbolic Links/SharedPackageFiles +++ /dev/null @@ -1 +0,0 @@ -../../SharedPackageFiles \ No newline at end of file diff --git a/Plugins/ApolloCodegenPlugin-Generate/ApolloCodegenPluginGenerate.swift b/Plugins/ApolloCodegenPlugin-Generate/ApolloCodegenPluginGenerate.swift deleted file mode 100644 index 0e2bf84e9b..0000000000 --- a/Plugins/ApolloCodegenPlugin-Generate/ApolloCodegenPluginGenerate.swift +++ /dev/null @@ -1,22 +0,0 @@ -import Foundation -import PackagePlugin - -@main struct ApolloCodegenPluginGenerate: CodegenCommand { - static var commandName: String = "generate" -} - -extension ApolloCodegenPluginGenerate: CommandPlugin { - func performCommand(context: PluginContext, arguments: [String]) async throws { - try performCommand(contextProvider: context, arguments: arguments) - } -} - -#if canImport(XcodeProjectPlugin) -import XcodeProjectPlugin - -extension ApolloCodegenPluginGenerate: XcodeCommandPlugin { - func performCommand(context: XcodePluginContext, arguments: [String]) throws { - try performCommand(contextProvider: context, arguments: arguments) - } -} -#endif diff --git a/Plugins/ApolloCodegenPlugin-Generate/Symbolic Links/README.md b/Plugins/ApolloCodegenPlugin-Generate/Symbolic Links/README.md deleted file mode 100644 index 3c1c8074d2..0000000000 --- a/Plugins/ApolloCodegenPlugin-Generate/Symbolic Links/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# Symbolic Links - -This directory contains symbolic links to shared source code used by the plugin. -This is a workaround until SwiftPM has native support for sharing code between plugins. diff --git a/Plugins/ApolloCodegenPlugin-Generate/Symbolic Links/SharedPackageFiles b/Plugins/ApolloCodegenPlugin-Generate/Symbolic Links/SharedPackageFiles deleted file mode 120000 index caaaf01585..0000000000 --- a/Plugins/ApolloCodegenPlugin-Generate/Symbolic Links/SharedPackageFiles +++ /dev/null @@ -1 +0,0 @@ -../../SharedPackageFiles \ No newline at end of file diff --git a/Plugins/ApolloCodegenPlugin-Initialize/ApolloCodegenPluginInitialize.swift b/Plugins/ApolloCodegenPlugin-Initialize/ApolloCodegenPluginInitialize.swift deleted file mode 100644 index 6caccacb3e..0000000000 --- a/Plugins/ApolloCodegenPlugin-Initialize/ApolloCodegenPluginInitialize.swift +++ /dev/null @@ -1,22 +0,0 @@ -import Foundation -import PackagePlugin - -@main struct ApolloCodegenPluginInitialize: CodegenCommand { - static var commandName: String = "init" -} - -extension ApolloCodegenPluginInitialize: CommandPlugin { - func performCommand(context: PluginContext, arguments: [String]) async throws { - try performCommand(contextProvider: context, arguments: arguments) - } -} - -#if canImport(XcodeProjectPlugin) -import XcodeProjectPlugin - -extension ApolloCodegenPluginInitialize: XcodeCommandPlugin { - func performCommand(context: XcodePluginContext, arguments: [String]) throws { - try performCommand(contextProvider: context, arguments: arguments) - } -} -#endif diff --git a/Plugins/ApolloCodegenPlugin-Initialize/Symbolic Links/README.md b/Plugins/ApolloCodegenPlugin-Initialize/Symbolic Links/README.md deleted file mode 100644 index 3c1c8074d2..0000000000 --- a/Plugins/ApolloCodegenPlugin-Initialize/Symbolic Links/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# Symbolic Links - -This directory contains symbolic links to shared source code used by the plugin. -This is a workaround until SwiftPM has native support for sharing code between plugins. diff --git a/Plugins/ApolloCodegenPlugin-Initialize/Symbolic Links/SharedPackageFiles b/Plugins/ApolloCodegenPlugin-Initialize/Symbolic Links/SharedPackageFiles deleted file mode 120000 index caaaf01585..0000000000 --- a/Plugins/ApolloCodegenPlugin-Initialize/Symbolic Links/SharedPackageFiles +++ /dev/null @@ -1 +0,0 @@ -../../SharedPackageFiles \ No newline at end of file diff --git a/Plugins/InstallCLI/InstallCLIPluginCommand.swift b/Plugins/InstallCLI/InstallCLIPluginCommand.swift new file mode 100644 index 0000000000..5a6207390c --- /dev/null +++ b/Plugins/InstallCLI/InstallCLIPluginCommand.swift @@ -0,0 +1,37 @@ +import Foundation +import PackagePlugin + +@main +struct InstallCLIPluginCommand: CommandPlugin { + + func performCommand(context: PackagePlugin.PluginContext, arguments: [String]) async throws { + let pathToCLI = try context.tool(named: "apollo-ios-cli").path + try createSymbolicLink(from: pathToCLI, to: context.package.directory) + } + + func createSymbolicLink(from: PackagePlugin.Path, to: PackagePlugin.Path) throws { + let task = Process() + task.standardInput = nil + task.environment = ProcessInfo.processInfo.environment + task.arguments = ["-c", "ln -f -s \(from.string) \(to.string)"] + task.executableURL = URL(fileURLWithPath: "/bin/zsh") + try task.run() + task.waitUntilExit() + } + +} + +#if canImport(XcodeProjectPlugin) +import XcodeProjectPlugin + +extension InstallCLIPluginCommand: XcodeCommandPlugin { + + /// 👇 This entry point is called when operating on an Xcode project. + func performCommand(context: XcodePluginContext, arguments: [String]) throws { + print("Installing Apollo CLI Plugin to Xcode project \(context.xcodeProject.displayName)") + let pathToCLI = try context.tool(named: "apollo-ios-cli").path + try createSymbolicLink(from: pathToCLI, to: context.xcodeProject.directory) + } + +} +#endif diff --git a/Plugins/SharedPackageFiles/CodegenCommand.swift b/Plugins/SharedPackageFiles/CodegenCommand.swift deleted file mode 100644 index 7c262f73f7..0000000000 --- a/Plugins/SharedPackageFiles/CodegenCommand.swift +++ /dev/null @@ -1,18 +0,0 @@ -import Foundation - -protocol CodegenCommand { - static var commandName: String { get } - - func performCommand(contextProvider: PluginContextProvider, arguments: [String]) throws -} - -extension CodegenCommand { - func performCommand(contextProvider: PluginContextProvider, arguments: [String]) throws { - let process = Process() - process.executableURL = try contextProvider.codegenExecutable - process.arguments = [Self.commandName] + arguments - - try process.run() - process.waitUntilExit() - } -} diff --git a/Plugins/SharedPackageFiles/PluginContext+codegenExecutableURL.swift b/Plugins/SharedPackageFiles/PluginContext+codegenExecutableURL.swift deleted file mode 100644 index ad48571eef..0000000000 --- a/Plugins/SharedPackageFiles/PluginContext+codegenExecutableURL.swift +++ /dev/null @@ -1,23 +0,0 @@ -import Foundation -import PackagePlugin - -protocol PluginContextProvider { - func tool(named name: String) throws -> PackagePlugin.PluginContext.Tool -} - -extension PluginContextProvider { - var codegenExecutable: URL { - get throws { - let executable = try tool(named: "apollo-ios-cli") - return URL(fileURLWithPath: executable.path.string) - } - } -} - -extension PluginContext: PluginContextProvider {} - -#if canImport(XcodeProjectPlugin) -import XcodeProjectPlugin - -extension XcodePluginContext: PluginContextProvider {} -#endif diff --git a/Sources/CodegenCLI/Commands/FetchSchema.swift b/Sources/CodegenCLI/Commands/FetchSchema.swift index 7a54a3f80b..df6bf2c108 100644 --- a/Sources/CodegenCLI/Commands/FetchSchema.swift +++ b/Sources/CodegenCLI/Commands/FetchSchema.swift @@ -12,7 +12,6 @@ public struct FetchSchema: ParsableCommand { ) @OptionGroup var inputs: InputOptions - @OptionGroup var ignored: IgnoredOptions // MARK: - Implementation diff --git a/Sources/CodegenCLI/Commands/Generate.swift b/Sources/CodegenCLI/Commands/Generate.swift index 99a0999f0e..b4184050e0 100644 --- a/Sources/CodegenCLI/Commands/Generate.swift +++ b/Sources/CodegenCLI/Commands/Generate.swift @@ -11,7 +11,6 @@ public struct Generate: ParsableCommand { ) @OptionGroup var inputs: InputOptions - @OptionGroup var ignored: IgnoredOptions @Flag( name: .shortAndLong, diff --git a/Sources/CodegenCLI/Commands/Initialize.swift b/Sources/CodegenCLI/Commands/Initialize.swift index a9dae66a4c..d7ab132ace 100644 --- a/Sources/CodegenCLI/Commands/Initialize.swift +++ b/Sources/CodegenCLI/Commands/Initialize.swift @@ -11,8 +11,6 @@ public struct Initialize: ParsableCommand { abstract: "Initialize a new configuration with defaults." ) - @OptionGroup var ignored: IgnoredOptions - @Option( name: [.long, .customShort("n")], help: "Name used to scope the generated schema type files." diff --git a/Sources/CodegenCLI/OptionGroups/IgnoredOptions.swift b/Sources/CodegenCLI/OptionGroups/IgnoredOptions.swift deleted file mode 100644 index de2f5d2afe..0000000000 --- a/Sources/CodegenCLI/OptionGroups/IgnoredOptions.swift +++ /dev/null @@ -1,19 +0,0 @@ -import ArgumentParser - -/// Shared group of arguments that are sent by various automation tools, such as Xcode, but are -/// ignored from input. -struct IgnoredOptions: ParsableArguments { - @Option( - name: .long, - help: ArgumentHelp( - "Ignored - used by Xcode only.", - discussion: """ - Xcode will automatically include this option when calling any plugin command to pass the - target name of the Swift package. This option is ignored and is not intended to be sent - by anything other than Xcode. - """, - visibility: .hidden - ) - ) - var target: String? -} diff --git a/docs/shared/spm-install-cli.mdx b/docs/shared/spm-install-cli.mdx new file mode 100644 index 0000000000..3a5712d9db --- /dev/null +++ b/docs/shared/spm-install-cli.mdx @@ -0,0 +1,19 @@ +The Apollo iOS SPM package includes the Codegen CLI as an executable target. This ensures you always have a valid CLI version for your Apollo iOS version. + +To simplify accessing the Codegen CLI, you can run the included `InstallCLI` SPM plugin. + +This plugin builds the CLI and creates a symbolic link to the executable in your project root. + +When using a `Package.swift` file, install the CLI by running: + +```bash +swift package --allow-writing-to-package-directory apollo-cli-install +``` + +When using Swift packages through Xcode, right-click on your project in the Xcode file explorer and at the bottom of the menu you will find the `InstallCLI` plugin command. Clicking on this will present a dialog asking for permission for the plugin to write to your project directory. + +Where to find the SPM plugin commands in Xcode + +After running the installation plugin, a symbolic link to the Codegen CLI named `apollo-ios-cli` is located in your project root folder. You can now run the CLI from the command line with `./apollo-ios-cli`. + +> **Note:** Because the `apollo-ios-cli` in your project root is only a symbolic link, it will only work if the compiled CLI exectuable exists. This is generally located in your Xcode Derived Data or the `.build` folder. If these are cleared, you can run the install plugin again to re-build the CLI executable. diff --git a/docs/shared/spm-setup-codegen-panel.mdx b/docs/shared/spm-setup-codegen-panel.mdx index a877cf6e36..2a2166ce0d 100644 --- a/docs/shared/spm-setup-codegen-panel.mdx +++ b/docs/shared/spm-setup-codegen-panel.mdx @@ -1,27 +1,32 @@ +import SPMInstallCLI from "../shared/spm-install-cli.mdx" + -Apollo iOS exposes the Codegen CLI as an SPM plugin. This means you can run the Codegen CLI using the `swift package` command. + + + -> **Note:** SPM Plugins require permission to write files to your project directory. Each time you run these commands, you will be prompted to give the plugin permission. To bypass this prompt, we recommend including the `--allow-writing-to-package-directory` flag before the plugin command name. +#### Install the Codegen CLI - + + + #### Initialize the code generation configuration -The Codegen CLI uses a JSON file to configure the code generation engine. Initializing the Codegen CLI creates this file with default values. From the directory of your `Package.swift` file, run: +The Codegen CLI uses a JSON file to configure the code generation engine. Use the Codegen CLI `init` command to create this file with default values. From your project's root directory, run: ```bash -swift package --allow-writing-to-package-directory apollo-initialize-codegen-config --schema-name ${MySchemaName} +./apollo-ios-cli init --schema-name ${MySchemaName} ``` -`${MySchemaName}` should be the name you would like to be used as the namespace for your generated schema files. +`${MySchemaName}` should be the name you want for the namespace of your generated schema files. -This will create an `apollo-codegen-config.json` file with default options. +This will create an `apollo-codegen-config.json` file with the default values. - #### Configure code generation options @@ -31,57 +36,33 @@ Open the `apollo-codegen-config.json` file to configure code generation for your The default configuration will: - Find all GraphQL schema files ending with the file extension `.graphqls` within your project directory. - Find all GraphQL operation and fragment definition files ending with the file extension `.graphql` within your project directory. -- Create a Swift Package for your generated schema with the `schema-name` provided. -- Generate operation and fragment models relative to the `.graphql` files that define them. +- Generate Swift code for the schema types in a directory with the `schema-name` provided. +- Generate Swift code for the operation and fragment models relative to the `.graphql` files that define them. - #### Run code generation -From the directory of your `Package.swift` file, run: +From your project's root directory, run: ```bash -swift package --allow-writing-to-package-directory apollo-generate +./apollo-ios-cli generate ``` Your generated files will be created with the file extension `.graphql.swift`. - -#### Add the generated schema package to your project - -A new Swift Package for your generated schema files will be located in a directory with the `schema-name` you provided. Any targets in your project that include generated operation models will need to import this package. +#### Add the generated schema and operation files to your target -If your project uses a `Package.swift` file, include the schema package as a local package with this dependency: - -```swift title="Package.swift" -dependencies: [ - .package(name: "MySchemaName", path: "./MySchemaName"), -], -``` - -For an Xcode project or workspace, go to **File > Add Packages...** and then click the `Add Local` button. Find the local package in your project directory and select it to include it in your project. - - - - - -#### Add the generated operation files to your target - -By default, your generated operation and fragments will be generated in the same location as the `.graphql` files they are defined by. - -If your generated operations are included in paths that are defined as sources for a target in your `Package.swift` file, Xcode should automatically include your generated operations in your targets. +By default, a directory containing your generated schema files will be located in a directory with the `schema-name` you provided; your generated operation and fragment files will be in the same location as the `.graphql` files they are defined by. If your target is created in an Xcode project or workspace, you will need to manually add the generated files to your target. -> **Note:** Because adding generated files to your Xcode targets must be done manually each time you generate new files, we highly recommend defining your project targets with SPM. Alternatively, you can generate your operations into the package that includes your schema files. For more information see the documentation for [Code Generation Confiugration](./codegen-configuration). +> **Note:** Because adding generated files to your Xcode targets must be done manually each time you generate new files, we highly recommend defining your project targets with SPM. Alternatively, you can generate your operations into the package that includes your schema files. For more information see the documentation for [Code Generation Configuration](./codegen-configuration). - - - \ No newline at end of file + diff --git a/docs/source/code-generation/codegen-cli.mdx b/docs/source/code-generation/codegen-cli.mdx index ff1274b731..366d3d1ff9 100644 --- a/docs/source/code-generation/codegen-cli.mdx +++ b/docs/source/code-generation/codegen-cli.mdx @@ -1,7 +1,9 @@ --- -title: The codegen CLI +title: The Codegen CLI --- +import SPMInstallCLI from "../../shared/spm-install-cli.mdx" + The Codegen CLI provides a command line tool that streamlines the process of running code generation. The CLI can be ran manually from Terminal (or any other shell program) or can be called into from bash scripts. The Codegen CLI has three primary commands: @@ -15,47 +17,13 @@ The Codegen CLI has three primary commands: ## Installation -When Apollo iOS is included as a dependency through Swift Package Manager (SPM) or Cocoapods, the CLI is built and packaged with the depedency automatically. This ensures you always have a valid version of the CLI for the version of Apollo iOS you are using and you never have to worry about installation or updates. +When Apollo iOS is included as a dependency through Swift Package Manager (SPM) or Cocoapods, the CLI is built and packaged with the dependency automatically. This ensures you always have a valid version of the CLI for the version of Apollo iOS you are using and you never have to worry about installation or updates. -Each package manager deploys the Codegen CLI using different methods. To learn how to run the Codegen CLI with your chosen package manager, open the appropriate section: +To learn how to run the Codegen CLI with your chosen package manager, open the appropriate section: -When using Swift packages through Xcode, Apollo iOS exposes the Codegen CLI as SPM plugins which are available from the Xcode menu. - -After adding the Apollo iOS package as a dependency for your Xcode project, right-click on your project and at the bottom of the menu you will find an 'Apollo' labeled group of menu items that are the plugin commands. Clicking on any of these will present another screen where you are able to add any custom arguments. - -Where to find the SPM plugin commands in Xcode - -See [Usage](#usage) for a list of all commands and their options. - - - - - -When using Swift Package Manager, Apollo iOS exposes the Codegen CLI as an SPM plugin. This means you can run the Codegen CLI using the `swift package` command. - -After installing the Apollo iOS package, you can run the Codegen CLI from the directory of your `Package.swift` file: - -```bash -swift package --allow-writing-to-package-directory ${Command Name} -${Command Arguments} -``` - -> **Plugin Permissions** -> -> SPM Plugins require permission to write files to your project directory. Each time you run these commands, you will be prompted to give the plugin permission. To bypass this prompt, we recommend including the `--allow-writing-to-package-directory` flag. -> -> This is a flag passed to the `swift package` command, so it must be provided *before* the name of the command. -> -> Using `--allow-writing-to-package-directory` is especially necessary when running the CLI from a non-interactive environment, such as a CI pipeline. - -In SPM, each CLI command is exposed as an individual plugin. To avoid naming conflicts with other SPM plugins, the commands have an `apollo`-prefixed command name. To call each command replace `${Command Name}` with the name of the command plugin you want to run: - -| Command | Plugin Command Name | -| ------- | ----------- | -| [Initialize](#initialize) | `apollo-initialize-codegen-config` | -| [Fetch Schema](#fetch-schema) | `apollo-fetch-schema` | -| [Generate](#generate) | `apollo-generate` | + @@ -138,10 +106,6 @@ The default configuration will: `apollo-ios-cli init [--schema-name ]` -**SPM Plugin Command:** - -`swift package --allow-writing-to-package-directory apollo-initialize-codegen-config [--schema-name ]` - #### Options: | Option | Description | @@ -163,10 +127,6 @@ Downloads a GraphQL schema from the Apollo Registry or GraphQL introspection and `apollo-ios-cli fetch-schema [--path ] [--string ]` -**SPM Plugin Command:** - -`swift package --allow-writing-to-package-directory apollo-fetch-schema [--path ] [--string ]` - #### Options: | Option | Description | @@ -187,10 +147,6 @@ Runs the code generation engine to generate Swift source code using the configur `apollo-ios-cli generate [--path ] [--string ]` -**SPM Plugin Command:** - -`swift package --allow-writing-to-package-directory apollo-generate [--path ] [--string ]` - #### Options: | Option | Description | diff --git a/docs/source/code-generation/introduction.mdx b/docs/source/code-generation/introduction.mdx index f4713d5c32..bf7f310686 100644 --- a/docs/source/code-generation/introduction.mdx +++ b/docs/source/code-generation/introduction.mdx @@ -8,6 +8,8 @@ Each generated operation contains a set of robust, strongly-typed models for its Because generated response models are operation-specific, they include properties _only_ for the GraphQL fields included in their corresponding operation. This means you can rely on the Swift type checker to flag data access errors at compile time. +> Check out our step-by-step tutorial on how to [get started with Apollo iOS code generation](./../tutorial/codegen-getting-started). + ## GraphQL source files To generate models, Apollo iOS requires two input sources: diff --git a/docs/source/config.json b/docs/source/config.json index 391bb736e2..f5bd4b3d4b 100644 --- a/docs/source/config.json +++ b/docs/source/config.json @@ -7,6 +7,9 @@ "sidebar": { "Introduction": "/", "Get Started": "/get-started", + "Tutorial": { + "Code Generation": "/tutorial/codegen-getting-started" + }, "API Reference": { "Overview": "https://www.apollographql.com/docs/ios/docc/documentation/index", "Apollo Client": "https://www.apollographql.com/docs/ios/docc/documentation/Apollo", @@ -17,7 +20,6 @@ }, "Code Generation": { "Introduction": "/code-generation/introduction", - "Getting Started": "/code-generation/codegen-getting-started", "The Codegen CLI": "/code-generation/codegen-cli", "Configuration": "/code-generation/codegen-configuration", "Downloading a Schema": "/code-generation/downloading-schema", diff --git a/docs/source/get-started.mdx b/docs/source/get-started.mdx index 531b47be26..03379cf626 100644 --- a/docs/source/get-started.mdx +++ b/docs/source/get-started.mdx @@ -5,8 +5,8 @@ sidebar_title: Get Started import SPMXcodeInstallationPanel from "../shared/spm-xcode-installation-panel.mdx" import SPMPackageInstallationPanel from "../shared/spm-package-installation-panel.mdx" -import SPMSetupCodegenPanel from "../shared/spm-setup-codegen-panel.mdx" import PodsInstallationPanel from "../shared/pods-installation-panel.mdx" +import SPMSetupCodegenPanel from "../shared/spm-setup-codegen-panel.mdx" import PodsSetupCodegenPanel from "../shared/pods-setup-codegen-panel.mdx" Follow the steps below to add Apollo iOS to your app: @@ -26,7 +26,9 @@ You can add Apollo iOS into your project using Swift Package Manager or CocoaPod ## 2. Add a schema file to your target directory -For Apollo iOS to generate models for your GraphQL operations, you need a local copy of your GraphQL server's schema. See [Downloading a schema](./code-generation/downloading-schema) for more details. +For Apollo iOS to generate models for your GraphQL operations, you need a local copy of your GraphQL server's schema. + +> See [Downloading a schema](./code-generation/downloading-schema) for more details. ## 3. Create `.graphql` files for your GraphQL operations @@ -34,6 +36,8 @@ Apollo iOS generates code from the GraphQL queries and mutations defined in your GraphQL operation and fragment definitions traditionally have the file extension `.graphql`. The generated models will have the file extension `.graphql.swift`. +> See [Defining operations](./fetching/fetching-data#defining-operations) for more details. + ## 4. Setup and run code generation Apollo iOS code generation uses your `.graphql` files to generate API code that helps you execute GraphQL operations and parse and cache operation responses. @@ -46,12 +50,12 @@ The easiest way to do this is with the Codegen CLI provided with Apollo iOS. > > To use Apollo's code generation and schema downloader from within any Swift script or library, check out [Running code generation in Swift code](./code-generation/run-codegen-in-swift-code). - - - - + + + + ## 5. Create an `ApolloClient` Before you can execute GraphQL operations in your app, you need to initialize an `ApolloClient` instance. @@ -63,6 +67,8 @@ import Apollo let apolloClient = ApolloClient(url: URL(string: "http://localhost:4000/graphql")!) ``` +> See [Creating a client](./networking/client-creation) for more details. + ## 6. Fetch a query `ApolloClient` can fetch your generated operation definitions, and return the response as a type-safe generated data model. @@ -85,3 +91,5 @@ apolloClient.fetch(query: HeroNameQuery) { result in print(data.hero.name) // Luke Skywalker } ``` + +> See [Fetching data](./fetching/fetching-data) for more details. \ No newline at end of file diff --git a/docs/source/screenshot/apollo-xcode-plugin.png b/docs/source/screenshot/apollo-xcode-plugin.png new file mode 100644 index 0000000000..db1251bcfc Binary files /dev/null and b/docs/source/screenshot/apollo-xcode-plugin.png differ diff --git a/docs/source/screenshot/xcode_menu_plugins.png b/docs/source/screenshot/xcode_menu_plugins.png deleted file mode 100644 index dfe858311a..0000000000 Binary files a/docs/source/screenshot/xcode_menu_plugins.png and /dev/null differ diff --git a/docs/source/code-generation/codegen-getting-started.mdx b/docs/source/tutorial/codegen-getting-started.mdx similarity index 100% rename from docs/source/code-generation/codegen-getting-started.mdx rename to docs/source/tutorial/codegen-getting-started.mdx diff --git a/docs/source/tutorial/images/add_package_dialog.png b/docs/source/tutorial/images/add_package_dialog.png deleted file mode 100644 index 6c68565508..0000000000 Binary files a/docs/source/tutorial/images/add_package_dialog.png and /dev/null differ diff --git a/docs/source/tutorial/images/book_trip_printout.png b/docs/source/tutorial/images/book_trip_printout.png deleted file mode 100644 index 0d3950e532..0000000000 Binary files a/docs/source/tutorial/images/book_trip_printout.png and /dev/null differ diff --git a/docs/source/tutorial/images/build_phases.png b/docs/source/tutorial/images/build_phases.png deleted file mode 100644 index 4683c35ec3..0000000000 Binary files a/docs/source/tutorial/images/build_phases.png and /dev/null differ diff --git a/docs/source/tutorial/images/completed_id_query.png b/docs/source/tutorial/images/completed_id_query.png deleted file mode 100644 index 259a466709..0000000000 Binary files a/docs/source/tutorial/images/completed_id_query.png and /dev/null differ diff --git a/docs/source/tutorial/images/detail_loaded.png b/docs/source/tutorial/images/detail_loaded.png deleted file mode 100644 index e482ae965a..0000000000 Binary files a/docs/source/tutorial/images/detail_loaded.png and /dev/null differ diff --git a/docs/source/tutorial/images/detail_loaded_merlin_c.png b/docs/source/tutorial/images/detail_loaded_merlin_c.png deleted file mode 100644 index 3c987a0c66..0000000000 Binary files a/docs/source/tutorial/images/detail_loaded_merlin_c.png and /dev/null differ diff --git a/docs/source/tutorial/images/detail_view_launch_id.png b/docs/source/tutorial/images/detail_view_launch_id.png deleted file mode 100644 index fd5f10ac0e..0000000000 Binary files a/docs/source/tutorial/images/detail_view_launch_id.png and /dev/null differ diff --git a/docs/source/tutorial/images/dont_add_to_target.png b/docs/source/tutorial/images/dont_add_to_target.png deleted file mode 100644 index 9eaad7e409..0000000000 Binary files a/docs/source/tutorial/images/dont_add_to_target.png and /dev/null differ diff --git a/docs/source/tutorial/images/drag_run_script.png b/docs/source/tutorial/images/drag_run_script.png deleted file mode 100644 index 3bdff14656..0000000000 Binary files a/docs/source/tutorial/images/drag_run_script.png and /dev/null differ diff --git a/docs/source/tutorial/images/empty_file_template.png b/docs/source/tutorial/images/empty_file_template.png deleted file mode 100644 index 0995af3410..0000000000 Binary files a/docs/source/tutorial/images/empty_file_template.png and /dev/null differ diff --git a/docs/source/tutorial/images/explorer_add_auth_header.png b/docs/source/tutorial/images/explorer_add_auth_header.png deleted file mode 100644 index b1a2cefbfe..0000000000 Binary files a/docs/source/tutorial/images/explorer_add_auth_header.png and /dev/null differ diff --git a/docs/source/tutorial/images/explorer_add_launches_query.png b/docs/source/tutorial/images/explorer_add_launches_query.png deleted file mode 100644 index 52ae2d5ab4..0000000000 Binary files a/docs/source/tutorial/images/explorer_add_launches_query.png and /dev/null differ diff --git a/docs/source/tutorial/images/explorer_added_empty_tab.png b/docs/source/tutorial/images/explorer_added_empty_tab.png deleted file mode 100644 index a543eb3611..0000000000 Binary files a/docs/source/tutorial/images/explorer_added_empty_tab.png and /dev/null differ diff --git a/docs/source/tutorial/images/explorer_added_login_mutation.png b/docs/source/tutorial/images/explorer_added_login_mutation.png deleted file mode 100644 index efbd47db57..0000000000 Binary files a/docs/source/tutorial/images/explorer_added_login_mutation.png and /dev/null differ diff --git a/docs/source/tutorial/images/explorer_authentication_header.png b/docs/source/tutorial/images/explorer_authentication_header.png deleted file mode 100644 index 0d44cb1f5f..0000000000 Binary files a/docs/source/tutorial/images/explorer_authentication_header.png and /dev/null differ diff --git a/docs/source/tutorial/images/explorer_autocomplete.png b/docs/source/tutorial/images/explorer_autocomplete.png deleted file mode 100644 index 8b3f90af56..0000000000 Binary files a/docs/source/tutorial/images/explorer_autocomplete.png and /dev/null differ diff --git a/docs/source/tutorial/images/explorer_book_trips_starter.png b/docs/source/tutorial/images/explorer_book_trips_starter.png deleted file mode 100644 index 34b995bf17..0000000000 Binary files a/docs/source/tutorial/images/explorer_book_trips_starter.png and /dev/null differ diff --git a/docs/source/tutorial/images/explorer_book_with_trip_id_singular.png b/docs/source/tutorial/images/explorer_book_with_trip_id_singular.png deleted file mode 100644 index ceb33f9969..0000000000 Binary files a/docs/source/tutorial/images/explorer_book_with_trip_id_singular.png and /dev/null differ diff --git a/docs/source/tutorial/images/explorer_book_with_trip_ids.png b/docs/source/tutorial/images/explorer_book_with_trip_ids.png deleted file mode 100644 index e28ab4aeeb..0000000000 Binary files a/docs/source/tutorial/images/explorer_book_with_trip_ids.png and /dev/null differ diff --git a/docs/source/tutorial/images/explorer_cancel_trip_mutation.png b/docs/source/tutorial/images/explorer_cancel_trip_mutation.png deleted file mode 100644 index 6c9bd80cb1..0000000000 Binary files a/docs/source/tutorial/images/explorer_cancel_trip_mutation.png and /dev/null differ diff --git a/docs/source/tutorial/images/explorer_check_cursor.png b/docs/source/tutorial/images/explorer_check_cursor.png deleted file mode 100644 index 0db0c03f0f..0000000000 Binary files a/docs/source/tutorial/images/explorer_check_cursor.png and /dev/null differ diff --git a/docs/source/tutorial/images/explorer_details_renamed.png b/docs/source/tutorial/images/explorer_details_renamed.png deleted file mode 100644 index 2cee405dd0..0000000000 Binary files a/docs/source/tutorial/images/explorer_details_renamed.png and /dev/null differ diff --git a/docs/source/tutorial/images/explorer_initial_added_query.png b/docs/source/tutorial/images/explorer_initial_added_query.png deleted file mode 100644 index 5f5e574e72..0000000000 Binary files a/docs/source/tutorial/images/explorer_initial_added_query.png and /dev/null differ diff --git a/docs/source/tutorial/images/explorer_launch_detail_result.png b/docs/source/tutorial/images/explorer_launch_detail_result.png deleted file mode 100644 index 8f3ed84dd3..0000000000 Binary files a/docs/source/tutorial/images/explorer_launch_detail_result.png and /dev/null differ diff --git a/docs/source/tutorial/images/explorer_launch_list_initial_response.png b/docs/source/tutorial/images/explorer_launch_list_initial_response.png deleted file mode 100644 index d0ea9adefa..0000000000 Binary files a/docs/source/tutorial/images/explorer_launch_list_initial_response.png and /dev/null differ diff --git a/docs/source/tutorial/images/explorer_launch_list_rename.png b/docs/source/tutorial/images/explorer_launch_list_rename.png deleted file mode 100644 index 5d0a602e09..0000000000 Binary files a/docs/source/tutorial/images/explorer_launch_list_rename.png and /dev/null differ diff --git a/docs/source/tutorial/images/explorer_launch_query_start.png b/docs/source/tutorial/images/explorer_launch_query_start.png deleted file mode 100644 index 1e30702c0e..0000000000 Binary files a/docs/source/tutorial/images/explorer_launch_query_start.png and /dev/null differ diff --git a/docs/source/tutorial/images/explorer_launches_drill_in.png b/docs/source/tutorial/images/explorer_launches_drill_in.png deleted file mode 100644 index d07ea24ab3..0000000000 Binary files a/docs/source/tutorial/images/explorer_launches_drill_in.png and /dev/null differ diff --git a/docs/source/tutorial/images/explorer_login_email_added.png b/docs/source/tutorial/images/explorer_login_email_added.png deleted file mode 100644 index dd23eefab0..0000000000 Binary files a/docs/source/tutorial/images/explorer_login_email_added.png and /dev/null differ diff --git a/docs/source/tutorial/images/explorer_login_mutation_rename.png b/docs/source/tutorial/images/explorer_login_mutation_rename.png deleted file mode 100644 index 246c24ea60..0000000000 Binary files a/docs/source/tutorial/images/explorer_login_mutation_rename.png and /dev/null differ diff --git a/docs/source/tutorial/images/explorer_meatball_copy.png b/docs/source/tutorial/images/explorer_meatball_copy.png deleted file mode 100644 index 2aea9dc790..0000000000 Binary files a/docs/source/tutorial/images/explorer_meatball_copy.png and /dev/null differ diff --git a/docs/source/tutorial/images/explorer_new_tab.png b/docs/source/tutorial/images/explorer_new_tab.png deleted file mode 100644 index 531fe29668..0000000000 Binary files a/docs/source/tutorial/images/explorer_new_tab.png and /dev/null differ diff --git a/docs/source/tutorial/images/explorer_query_list.png b/docs/source/tutorial/images/explorer_query_list.png deleted file mode 100644 index ea68d9e93a..0000000000 Binary files a/docs/source/tutorial/images/explorer_query_list.png and /dev/null differ diff --git a/docs/source/tutorial/images/explorer_rocket_back.png b/docs/source/tutorial/images/explorer_rocket_back.png deleted file mode 100644 index 6a95fd0da5..0000000000 Binary files a/docs/source/tutorial/images/explorer_rocket_back.png and /dev/null differ diff --git a/docs/source/tutorial/images/explorer_rocket_drill_in.png b/docs/source/tutorial/images/explorer_rocket_drill_in.png deleted file mode 100644 index 9c7134d1b8..0000000000 Binary files a/docs/source/tutorial/images/explorer_rocket_drill_in.png and /dev/null differ diff --git a/docs/source/tutorial/images/explorer_sandbox_open.png b/docs/source/tutorial/images/explorer_sandbox_open.png deleted file mode 100644 index fc9bdff6fd..0000000000 Binary files a/docs/source/tutorial/images/explorer_sandbox_open.png and /dev/null differ diff --git a/docs/source/tutorial/images/explorer_submit_operation.png b/docs/source/tutorial/images/explorer_submit_operation.png deleted file mode 100644 index ab724262fb..0000000000 Binary files a/docs/source/tutorial/images/explorer_submit_operation.png and /dev/null differ diff --git a/docs/source/tutorial/images/explorer_subscription_success.png b/docs/source/tutorial/images/explorer_subscription_success.png deleted file mode 100644 index 977afd795d..0000000000 Binary files a/docs/source/tutorial/images/explorer_subscription_success.png and /dev/null differ diff --git a/docs/source/tutorial/images/explorer_subscriptions_listening.png b/docs/source/tutorial/images/explorer_subscriptions_listening.png deleted file mode 100644 index 0290c15f70..0000000000 Binary files a/docs/source/tutorial/images/explorer_subscriptions_listening.png and /dev/null differ diff --git a/docs/source/tutorial/images/explorer_trip_cancelled.png b/docs/source/tutorial/images/explorer_trip_cancelled.png deleted file mode 100644 index e245d44287..0000000000 Binary files a/docs/source/tutorial/images/explorer_trip_cancelled.png and /dev/null differ diff --git a/docs/source/tutorial/images/explorer_tripsbooked_initial.png b/docs/source/tutorial/images/explorer_tripsbooked_initial.png deleted file mode 100644 index 1a96d57f41..0000000000 Binary files a/docs/source/tutorial/images/explorer_tripsbooked_initial.png and /dev/null differ diff --git a/docs/source/tutorial/images/explorer_tripsbooked_renamed.png b/docs/source/tutorial/images/explorer_tripsbooked_renamed.png deleted file mode 100644 index 0cb482cee0..0000000000 Binary files a/docs/source/tutorial/images/explorer_tripsbooked_renamed.png and /dev/null differ diff --git a/docs/source/tutorial/images/installed_dependencies.png b/docs/source/tutorial/images/installed_dependencies.png deleted file mode 100644 index a437f5fc92..0000000000 Binary files a/docs/source/tutorial/images/installed_dependencies.png and /dev/null differ diff --git a/docs/source/tutorial/images/interceptor_breakpoint.png b/docs/source/tutorial/images/interceptor_breakpoint.png deleted file mode 100644 index 9047c92929..0000000000 Binary files a/docs/source/tutorial/images/interceptor_breakpoint.png and /dev/null differ diff --git a/docs/source/tutorial/images/launch_list_final.png b/docs/source/tutorial/images/launch_list_final.png deleted file mode 100644 index 98b4eb2a43..0000000000 Binary files a/docs/source/tutorial/images/launch_list_final.png and /dev/null differ diff --git a/docs/source/tutorial/images/launches_detail.png b/docs/source/tutorial/images/launches_detail.png deleted file mode 100644 index 8da6a63d98..0000000000 Binary files a/docs/source/tutorial/images/launches_detail.png and /dev/null differ diff --git a/docs/source/tutorial/images/list_all_loaded.png b/docs/source/tutorial/images/list_all_loaded.png deleted file mode 100644 index 87454187d3..0000000000 Binary files a/docs/source/tutorial/images/list_all_loaded.png and /dev/null differ diff --git a/docs/source/tutorial/images/list_sites_success.png b/docs/source/tutorial/images/list_sites_success.png deleted file mode 100644 index 132f6e2103..0000000000 Binary files a/docs/source/tutorial/images/list_sites_success.png and /dev/null differ diff --git a/docs/source/tutorial/images/login_mutation_email.png b/docs/source/tutorial/images/login_mutation_email.png deleted file mode 100644 index ef05d27baa..0000000000 Binary files a/docs/source/tutorial/images/login_mutation_email.png and /dev/null differ diff --git a/docs/source/tutorial/images/login_mutation_null.png b/docs/source/tutorial/images/login_mutation_null.png deleted file mode 100644 index 60f1623a7d..0000000000 Binary files a/docs/source/tutorial/images/login_mutation_null.png and /dev/null differ diff --git a/docs/source/tutorial/images/new_run_script_phase.png b/docs/source/tutorial/images/new_run_script_phase.png deleted file mode 100644 index 9f39140a28..0000000000 Binary files a/docs/source/tutorial/images/new_run_script_phase.png and /dev/null differ diff --git a/docs/source/tutorial/images/next_minor.png b/docs/source/tutorial/images/next_minor.png deleted file mode 100644 index f7966441fa..0000000000 Binary files a/docs/source/tutorial/images/next_minor.png and /dev/null differ diff --git a/docs/source/tutorial/images/open_in_explorer_launches.png b/docs/source/tutorial/images/open_in_explorer_launches.png deleted file mode 100644 index cedbafd1d8..0000000000 Binary files a/docs/source/tutorial/images/open_in_explorer_launches.png and /dev/null differ diff --git a/docs/source/tutorial/images/open_starter_project.png b/docs/source/tutorial/images/open_starter_project.png deleted file mode 100644 index 5a066ccb13..0000000000 Binary files a/docs/source/tutorial/images/open_starter_project.png and /dev/null differ diff --git a/docs/source/tutorial/images/placeholder_in_asset_catalog.png b/docs/source/tutorial/images/placeholder_in_asset_catalog.png deleted file mode 100644 index 23b19e65ed..0000000000 Binary files a/docs/source/tutorial/images/placeholder_in_asset_catalog.png and /dev/null differ diff --git a/docs/source/tutorial/images/placeholder_logo.png b/docs/source/tutorial/images/placeholder_logo.png deleted file mode 100644 index 3bade6d6d2..0000000000 Binary files a/docs/source/tutorial/images/placeholder_logo.png and /dev/null differ diff --git a/docs/source/tutorial/images/rename_run_script.png b/docs/source/tutorial/images/rename_run_script.png deleted file mode 100644 index 1dfe215821..0000000000 Binary files a/docs/source/tutorial/images/rename_run_script.png and /dev/null differ diff --git a/docs/source/tutorial/images/sandbox_green_dot.png b/docs/source/tutorial/images/sandbox_green_dot.png deleted file mode 100644 index 21db0c26a4..0000000000 Binary files a/docs/source/tutorial/images/sandbox_green_dot.png and /dev/null differ diff --git a/docs/source/tutorial/images/sandbox_landing.png b/docs/source/tutorial/images/sandbox_landing.png deleted file mode 100644 index 3fccf6e94a..0000000000 Binary files a/docs/source/tutorial/images/sandbox_landing.png and /dev/null differ diff --git a/docs/source/tutorial/images/sandbox_schema_book_trips.png b/docs/source/tutorial/images/sandbox_schema_book_trips.png deleted file mode 100644 index 1f0f16a02a..0000000000 Binary files a/docs/source/tutorial/images/sandbox_schema_book_trips.png and /dev/null differ diff --git a/docs/source/tutorial/images/sandbox_schema_mutations.png b/docs/source/tutorial/images/sandbox_schema_mutations.png deleted file mode 100644 index c43a0be03e..0000000000 Binary files a/docs/source/tutorial/images/sandbox_schema_mutations.png and /dev/null differ diff --git a/docs/source/tutorial/images/sandbox_schema_reference.png b/docs/source/tutorial/images/sandbox_schema_reference.png deleted file mode 100644 index 714cad3aec..0000000000 Binary files a/docs/source/tutorial/images/sandbox_schema_reference.png and /dev/null differ diff --git a/docs/source/tutorial/images/schema_icon.png b/docs/source/tutorial/images/schema_icon.png deleted file mode 100644 index a4697e535c..0000000000 Binary files a/docs/source/tutorial/images/schema_icon.png and /dev/null differ diff --git a/docs/source/tutorial/images/schema_login_definition.png b/docs/source/tutorial/images/schema_login_definition.png deleted file mode 100644 index 742777ffb6..0000000000 Binary files a/docs/source/tutorial/images/schema_login_definition.png and /dev/null differ diff --git a/docs/source/tutorial/images/schema_tripsBooked_definition.png b/docs/source/tutorial/images/schema_tripsBooked_definition.png deleted file mode 100644 index 6383070c53..0000000000 Binary files a/docs/source/tutorial/images/schema_tripsBooked_definition.png and /dev/null differ diff --git a/docs/source/tutorial/images/screenshot_trip_booked.png b/docs/source/tutorial/images/screenshot_trip_booked.png deleted file mode 100644 index 0ade3cf4d6..0000000000 Binary files a/docs/source/tutorial/images/screenshot_trip_booked.png and /dev/null differ diff --git a/docs/source/tutorial/images/select_libs.png b/docs/source/tutorial/images/select_libs.png deleted file mode 100644 index 7133c99287..0000000000 Binary files a/docs/source/tutorial/images/select_libs.png and /dev/null differ diff --git a/docs/source/tutorial/images/select_target.png b/docs/source/tutorial/images/select_target.png deleted file mode 100644 index bd59453efc..0000000000 Binary files a/docs/source/tutorial/images/select_target.png and /dev/null differ diff --git a/docs/source/tutorial/images/stock_detail_view.png b/docs/source/tutorial/images/stock_detail_view.png deleted file mode 100644 index 22eeec12f7..0000000000 Binary files a/docs/source/tutorial/images/stock_detail_view.png and /dev/null differ diff --git a/docs/source/tutorial/images/success_log_barf.png b/docs/source/tutorial/images/success_log_barf.png deleted file mode 100644 index 4af668b431..0000000000 Binary files a/docs/source/tutorial/images/success_log_barf.png and /dev/null differ diff --git a/docs/source/tutorial/images/tap_to_load_more.png b/docs/source/tutorial/images/tap_to_load_more.png deleted file mode 100644 index 464ed1dbcc..0000000000 Binary files a/docs/source/tutorial/images/tap_to_load_more.png and /dev/null differ diff --git a/docs/source/tutorial/tutorial-mutations.md b/docs/source/tutorial/tutorial-mutations.md deleted file mode 100644 index d73936f6f5..0000000000 --- a/docs/source/tutorial/tutorial-mutations.md +++ /dev/null @@ -1,352 +0,0 @@ ---- -title: 8. Define additional mutations ---- - -In this section, you'll learn how to build authenticated mutations and handle information returned from those mutations, enabling you to book and cancel trips in your app. - -## 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. - -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 **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 -import Apollo - -class TokenAddingInterceptor: ApolloInterceptor { - func interceptAsync( - chain: RequestChain, - request: HTTPRequest, - response: HTTPResponse?, - completion: @escaping (Result, Error>) -> Void) { - - // 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="TokenAddingInterceptor.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="TokenAddingInterceptor.swift" -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. - -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`: - -```swift title="NetworkInterceptorProvider.swift" -import Foundation -import Apollo - -class NetworkInterceptorProvider: DefaultInterceptorProvider { - override func interceptors(for operation: Operation) -> [ApolloInterceptor] { - var interceptors = super.interceptors(for: operation) - interceptors.insert(TokenAddingInterceptor(), 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. - -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 **TokenAddingInterceptor.swift**. -Click on the line numbers to add a breakpoint at the line where you're instantiating the `Keychain`: - -adding a breakpoint - -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! - -## Add the `BookTrip` mutation - -In [Sandbox](https://studio.apollographql.com/sandbox/explorer?endpoint=https%3A%2F%2Fapollo-fullstack-tutorial.herokuapp.com%2Fgraphql), open the Schema tab by clicking its icon, select the `Mutations`, and take a look at the `bookTrips` mutation: - -The docs for book trips - -Click the play button to the right to open this mutation in Explorer. Click the plus button to add the `bookTrips` mutation: - -The book trips operation immediately after adding it - -You can see in the left sidebar that this takes an argument of an array of IDs (which was added as `$bookTripsLaunchIds`), and the object returned from the operation has three properties: - -* A `success` boolean indicating whether the booking succeeded -* 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. - -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: - -```json title="(Sandbox Explorer)" -{"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: - -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. - -> 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! - -Explorer showing the result of booking a trip with an array of IDs - -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: - -```graphql title="(Sandbox Explorer)" -mutation BookTrip($id:ID!) { - bookTrips(launchIds:[$id]) { - success - message - } -} -``` - -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: - -```json title="(Sandbox Explorer)" -{"id": "25"} -``` - -Click the Submit Operation button to run your updated query. The response you get back should be identical to the one you got earlier: - -The result of booking a trip with a single identifier - -Now that you've fleshed out your operation, it's time to put it into the app. Go to **File > New > File... > Empty**, and name this file `BookTrip.graphql`. Paste in the final query from the Sandbox Explorer. - -Build the application to run the code generation. Then, in `DetailViewController.swift`, fill in the `bookTrip` method with the code to book your trip based on the flight's ID: - -```swift title="DetailViewController.swift" -private func bookTrip(with id: GraphQLID) { - Network.shared.apollo.perform(mutation: BookTripMutation(id: id)) { [weak self] result in - guard let self = self else { - return - } - switch result { - case .success(let graphQLResult): - if let bookingResult = graphQLResult.data?.bookTrips { - // TODO - } - - if let errors = graphQLResult.errors { - // From UIViewController+Alert.swift - self.showAlertForErrors(errors) - } - case .failure(let error): - self.showAlert(title: "Network Error", - message: error.localizedDescription) - } - } -} -``` - - -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) { - print("Cancel trip \(id)") - // TODO: Add code to cancel trip -} -``` - -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 { - self.cancelTrip(with: launch.id) -} else { - self.bookTrip(with: launch.id) -} -``` - -In `bookTrip`, replace the `TODO` with code to handle what comes back in the `success` property: - -```swift title="DetailViewController.swift" -if bookingResult.success { - self.showAlert(title: "Success!", - message: bookingResult.message ?? "Trip booked successfully") -} else { - self.showAlert(title: "Could not book trip", - message: bookingResult.message ?? "Unknown failure.") -} -``` - -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 - -The process for the `CancelTrip` mutation is similar to the one for `BookTrip`. Go back to the Sandbox's Schema tab, select Mutations,e and look at the `cancelTrip` mutation's documentation: - -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: - -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. - -Again, Explorer's gotten a little verbose here, so update your operation's name and variables to be a little shorter: - -```graphql title="(Sandbox Explorer)" -mutation CancelTrip($id: ID!) { - cancelTrip(launchId: $id) { - success - message - } -} -``` - -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"): - -```json title="(GraphiQL)" -{"id": "25"} -``` - -Make sure that in the Headers section, you add your authorization token again (the token added to the tab with `BookTrip` won't carry over to this new tab): - -The headers section - -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: - -```swift title="DetailViewController.swift" -Network.shared.apollo.perform(mutation: CancelTripMutation(id: id)) { [weak self] result in - guard let self = self else { - return - } - switch result { - case .success(let graphQLResult): - if let cancelResult = graphQLResult.data?.cancelTrip { - if cancelResult.success { - // TODO - } - } - - if let errors = graphQLResult.errors { - // From UIViewController+Alert.swift - self.showAlertForErrors(errors) - } - case .failure(let error): - self.showAlert(title: "Network Error", - message: error.localizedDescription) - } -} -``` - - -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", - message: cancelResult.message ?? "Your trip has been officially cancelled.") -} else { - 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. - -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`. - -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). - -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`: - -```swift title="DetailViewController.swift" -private func loadLaunchDetails(forceReload: Bool = false) { - guard - let launchID = self.launchID, - (forceReload || launchID != self.launch?.id) else { - // 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) - } -} -``` - -Next, add the following line to **both** the `bookingResult.success` and `cancelResult.success` branches in their respective methods before showing the alerts: - -```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. - -In the next section, you'll learn how to use [subscriptions](./tutorial-subscriptions/) with the Apollo client. diff --git a/docs/source/tutorial/tutorial-subscriptions.md b/docs/source/tutorial/tutorial-subscriptions.md deleted file mode 100644 index ec651a1b3f..0000000000 --- a/docs/source/tutorial/tutorial-subscriptions.md +++ /dev/null @@ -1,204 +0,0 @@ ---- -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/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%2Fgraphql) 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 - -This subscription doesn't take any argument and returns a single scalar named `tripsBooked`. Since you can book multiple trips at once, `tripsBooked` is an `Int`. It will contain the number of trips booked at once or -1 if a trip has been cancelled. - -Click the play button to the far right of `tripsBooked` to open the subscription in Explorer. Open a new tab, then check the `tripsBooked` button to have the subscription added: - -The initial definition of the TripsBooked subscription - -Again, rename your subscription so it's easier to find: - -The subscription after rename - -Click the Submit Operation button, and your subscription will start listening to events. You can tell it's up and running because a panel will pop up at the lower left where subscription data will come in: - -The UI showing that it's listening for subscription updates - -## Test your subscription - -Open a new tab in Explorer. In this new tab, add code to book a trip like on [step 8](tutorial-mutations), but with a hard-coded ID: - -```graphql -mutation BookTrip { - bookTrips(launchIds: ["93"]){ - message - } -} -``` - -Do not forget to include the authentication header. At the bottom of the Sandbox Explorer pane where you add operations, there's a `Headers` section: - -Adding a login token to explorer - -Click the Submit Operation button. If everything went well, you just booked a trip! At the top of the right panel, you'll see the success JSON for your your `BookTrip` mutation, and below it, updated JSON for the `TripsBooked` subscription: - -Subscription success in Explorer - -Continue booking and/or canceling trips, you will see events coming in the subscription panel in real time. After some time, the server might close the connection and you'll have to restart your subscription to keep receiving events. - -## Add the subscription to the project - -Now that your subscription is working, add it to your project. Create an empty file named `TripsBooked.graphql` next to your other GraphQL files and paste the contents of the subscription. The process is similar to what you've already done for queries and mutations: - -```graphql title="TripsBooked.graphql" -subscription TripsBooked { - tripsBooked -} -``` - -Build your project, and the subscription will be picked up and added to your `API.swift` file. - -## Configure your ApolloClient to use subscriptions - -> This tutorial uses the [`graphql-ws`](https://github.com/apollographql/subscriptions-transport-ws/blob/master/PROTOCOL.md) protocol, implemented by the [`subscriptions-transport-ws`](https://github.com/apollographql/subscriptions-transport-ws) library. **That library is no longer actively maintained.** We recommend using the [`graphql-ws`](https://www.npmjs.com/package/graphql-ws) library instead, which implements its own WebSocket subprotocol, `graphql-transport-ws`. Note that the two libraries do not use the same WebSocket subprotocol and you need to ensure that your servers, clients, and tools use the **same library and subprotocol**. For more information and examples, see [GraphQL over WebSocket protocols](../subscriptions#graphql-over-websocket-protocols). - -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: - -```swift title="Network.swift" -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: - -```swift title="Network.swift" -// 1 -let webSocket = WebSocket( - url: URL(string: "wss://apollo-fullstack-tutorial.herokuapp.com/graphql")!, - protocol: .graphql_ws -) - -// 2 -let webSocketTransport = WebSocketTransport(websocket: webSocket) - -// 3 -let splitTransport = SplitNetworkTransport( - uploadingNetworkTransport: transport, - webSocketNetworkTransport: webSocketTransport -) - -// 4 -return ApolloClient(networkTransport: splitTransport, store: store) -``` - -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`. - -Now, you're ready to actually use your subscription! - -## Display a view when a trip is booked/cancelled - -In `LaunchesViewController`, add a new variable just below `activeRequest` to hang on to a reference to your subscription so it doesn't get hammered by ARC as soon as it goes out of scope: - -```swift title="LaunchesViewController.swift" -private var activeSubscription: Cancellable? -``` - -Next, just above the code for handling Segues, add code for starting and handling the result of a subscription: - -```swift title="LaunchesViewController.swift" -// MARK: - Subscriptions - -private func startSubscription() { - activeSubscription = Network.shared.apollo.subscribe(subscription: TripsBookedSubscription()) { result in - switch result { - case .failure(let error): - self.showAlert(title: "NetworkError", - message: error.localizedDescription) - case .success(let graphQLResult): - if let errors = graphQLResult.errors { - self.showAlertForErrors(errors) - } else if let tripsBooked = graphQLResult.data?.tripsBooked { - self.handleTripsBooked(value: tripsBooked) - } else { - // There was no data and there were no errors, do nothing. - } - } - } -} - -private func handleTripsBooked(value: Int) { - print("Trips booked: \(value)") -} -``` - -Finally, add a line to `viewDidLoad` which actually starts the subscription: - -```swift title="LaunchesViewController.swift" -override func viewDidLoad() { - super.viewDidLoad() - self.startSubscription() - self.loadMoreLaunchesIfTheyExist() -} -``` - -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: - -``` -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: - -```swift title="LaunchesViewController.swift" -private func handleTripsBooked(value: Int) { - var message: String - switch value { - case 1: - message = "A new trip was booked! 🚀" - case -1: - message = "A trip was cancelled! 😭" - default: - self.showAlert(title: "Unexpected value", - 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: - -A new trip was booked (rocket) - -And you've done it! You've completed the tutorial. - -## More resources - -There are way more things you can do with the Apollo iOS SDK, and the rest of this documentation includes info on more advanced topics like: - -- Using [fragments](../fragments/) -- Working with [custom scalars](../fetching-queries/#notes-on-working-with-custom-scalars) -- [Caching](../caching/) - -Feel free to ask questions by either [opening an issue on our GitHub repo](https://github.com/apollographql/apollo-ios/issues), or [joining the community](http://community.apollographql.com/new-topic?category=Help&tags=mobile,client). - -And if you want dig more and see GraphQL in real-world apps, you can take a look at these open source projects using Apollo iOS: - -* https://github.com/GitHawkApp/GitHawk -* [open a PR if you have an example app that should be here!]