forked from apollographql/apollo-ios
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
8770cb2
commit 84d2870
Showing
6 changed files
with
355 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
--- | ||
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 an introspection query. Conventionally this file is called `schema.json`, and you store it next to the `.graphql` files in your target. | ||
|
||
You can use `apollo-codegen` to download a GraphQL schema by sending an introspection query to the server: | ||
|
||
```sh | ||
apollo-codegen download-schema http://localhost:8080/graphql --output schema.json | ||
``` | ||
|
||
If needed, you can use the `header` option to add additional HTTP headers to the request. For example, to include an authentication token, use `--header "Authorization: Bearer <token>"`: | ||
|
||
```sh | ||
apollo-codegen download-schema https://api.github.com/graphql --output schema.json --header "Authorization: Bearer <token>" | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
--- | ||
title: Fragments | ||
--- | ||
|
||
In GraphQL, [fragments](http://graphql.org/learn/queries/#fragments) define pieces of data you may want to reuse in multiple places: | ||
|
||
```graphql | ||
query HeroAndFriends($episode: Episode) { | ||
hero(episode: $episode) { | ||
name | ||
...HeroDetails | ||
friends { | ||
...HeroDetails | ||
} | ||
} | ||
} | ||
|
||
fragment HeroDetails on Character { | ||
name | ||
appearsIn | ||
} | ||
``` | ||
|
||
Apollo iOS generates separate result types for fragments, which means they are a great way of keeping UI components or utility functions independent of specific queries. | ||
|
||
One common pattern is to define a fragment for a child view (like a `UITableViewCell`), and include the fragment in a query defined at a parent level (like a `UITableViewController`). This way, the child view can easily be reused and only depends on the specific data it needs: | ||
|
||
```swift | ||
func configure(with heroDetails: HeroDetails?) { | ||
textLabel?.text = heroDetails?.name | ||
} | ||
``` | ||
|
||
This also works the other way around. The parent view controller only has to know the fragment name, but doesn't need to know anything about the fields it specifies. You can make changes to the fragment definition without affecting the parent. | ||
|
||
In fact, this is the main reason fields included through fragments are not exposed directly, but require you to access the data through the fragment explicitly: | ||
|
||
```swift | ||
apollo.fetch(query: HeroAndFriendsQuery(episode: .empire)) { (result, error) in | ||
guard let data = result?.data else { return } | ||
print(data.hero?.name) // Luke Skywalker | ||
print(data.hero?.appearsIn) // WON'T WORK | ||
print(data.hero?.fragments.heroDetails.appearsIn) // [.newhope, .empire, .jedi] | ||
print(data.hero?.friends?.flatMap { $0?.fragments.heroDetails.name }.joined(separator: ", ")) // Han Solo, Leia Organa, C-3PO, R2-D2 | ||
} | ||
``` | ||
|
||
In most cases, you'll simply pass the whole fragment to a child view without needing to know anything about the data it specifies: | ||
|
||
```swift | ||
cell.configure(with: hero?.fragments.heroDetails) | ||
``` | ||
|
||
<h2 id="type-conditions">Type conditions</h2> | ||
|
||
The GraphQL type system includes interfaces and unions as abstract types that object types can conform to. In the Star Wars example schema for example, both `Human`s and `Droid`s implement the `Character` interface. If we query for a hero, the result can be either a human or a droid, and if we want to access any type-specific properties we will have to use a fragment with a type condition: | ||
|
||
```graphql | ||
query HeroAndFriends($episode: Episode) { | ||
hero(episode: $episode) { | ||
name | ||
...DroidDetails | ||
} | ||
} | ||
|
||
fragment DroidDetails on Droid { | ||
name | ||
primaryFunction | ||
} | ||
``` | ||
|
||
You can access named fragments with type conditions the same way you access other fragments, but their type will be optional to reflect the fact that their fields will only be available if the object type matches: | ||
|
||
```swift | ||
apollo.fetch(query: HeroAndFriendsQuery(episode: .empire)) { (result, error) in | ||
data.hero?.fragments.droidDetails?.primaryFunction | ||
} | ||
``` | ||
|
||
Alternatively, you can use [inline fragments](http://graphql.org/learn/queries/#inline-fragments) with type conditions to query for type-specific fields: | ||
|
||
```graphql | ||
query HeroAndFriends($episode: Episode) { | ||
hero(episode: $episode) { | ||
name | ||
... on Droid { | ||
primaryFunction | ||
} | ||
} | ||
} | ||
``` | ||
|
||
And results from inline fragments with type conditions will be made available through specially generated `as<Type>` properties: | ||
|
||
```swift | ||
apollo.fetch(query: HeroAndFriendsQuery(episode: .empire)) { (result, error) in | ||
guard let data = result?.data else { return } | ||
data.hero?.asDroid?.primaryFunction | ||
} | ||
``` | ||
|
||
You can also use inline fragments inside named fragments: | ||
|
||
```graphql | ||
query HeroAndFriends($episode: Episode) { | ||
hero(episode: $episode) { | ||
name | ||
...HeroDetails | ||
friends { | ||
...HeroDetails | ||
} | ||
} | ||
} | ||
|
||
fragment HeroDetails on Character { | ||
name | ||
... on Droid { | ||
primaryFunction | ||
} | ||
} | ||
``` | ||
|
||
```swift | ||
apollo.fetch(query: HeroAndFriendsQuery(episode: .empire)) { (result, error) in | ||
guard let data = result?.data else { return } | ||
data.hero?.fragments.heroDetails.asDroid?.primaryFunction | ||
} | ||
``` | ||
|
||
Apollo iOS automaticaly adds a `__typename` field to selection sets for abstract types, and generates a constant `__typename` property for concrete types. This is used primarily to support conditional fragments, but it means a `__typename` property is always defined and can be used to differentiate between object types manually if needed. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
--- | ||
title: Creating a client | ||
--- | ||
|
||
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 define a global variable in `AppDelegate.swift`: | ||
|
||
```swift | ||
let apollo = ApolloClient(url: URL(string: "http://localhost:8080/graphql")!) | ||
``` | ||
|
||
<h2 id="adding-headers">Adding additional headers</h2> | ||
|
||
If you need to add additional headers to requests, to include authentication details for example, you can create your own `URLSessionConfiguration` and use this to configure an `HTTPNetworkTransport`. | ||
|
||
```swift | ||
let configuration = URLSessionConfiguration.default | ||
// Add additional headers as needed | ||
configuration.httpAdditionalHeaders = ["Authorization": "Bearer <token>"] | ||
|
||
let url = URL(string: "http://localhost:8080/graphql")! | ||
|
||
let apollo = ApolloClient(networkTransport: HTTPNetworkTransport(url: url, configuration: configuration)) | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
--- | ||
title: Mutations | ||
--- | ||
|
||
In addition to fetching data using queries, Apollo iOS also handles GraphQL mutations. Mutations are identical to queries in syntax, the only difference being that you use the keyword `mutation` instead of `query` to indicate that the root fields on this query are going to be performing writes to the backend. | ||
|
||
```graphql | ||
mutation UpvotePost($postId: Int!) { | ||
upvotePost(postId: $postId) { | ||
votes | ||
} | ||
} | ||
|
||
``` | ||
|
||
GraphQL mutations represent two things in one query string: | ||
|
||
1. The mutation field name with arguments, `upvotePost`, which represents the actual operation to be done on the server | ||
2. The fields you want back from the result of the mutation to update the client: `{ votes }` | ||
|
||
The above mutation will upvote a post on the server. The result might be: | ||
|
||
``` | ||
{ | ||
"data": { | ||
"upvotePost": { | ||
"id": "123", | ||
"votes": 5 | ||
} | ||
} | ||
} | ||
``` | ||
|
||
Similar to queries, mutations are represented by instances of generated classes, conforming to the `GraphQLMutation` protocol. Constructor arguments are used to define mutation variables. You pass a mutation object to `ApolloClient#perform(mutation:)` to send the mutation to the server, execute it, and receive typed results: | ||
|
||
```swift | ||
apollo.perform(mutation: UpvotePostMutation(postId: postId)) { (result, error) in | ||
print(result?.data?.upvotePost?.votes) | ||
} | ||
``` | ||
|
||
<h2 id="fragments-in-mutation-results">Using fragments in mutation results</h2> | ||
|
||
In many cases, you'll want to use mutation results to update your UI. Fragments can be a great way of sharing result handling between queries and mutations: | ||
|
||
```graphql | ||
mutation UpvotePost($postId: Int!) { | ||
upvotePost(postId: $postId) { | ||
...PostDetails | ||
} | ||
} | ||
``` | ||
|
||
```swift | ||
apollo.perform(mutation: UpvotePostMutation(postId: postId)) { (result, error) in | ||
self.configure(with: result?.data?.upvotePost?.fragments.postDetails) | ||
} | ||
``` | ||
|
||
<h2 id="input-objects">Passing input objects</h2> | ||
|
||
The GraphQL type system includes [input objects](http://graphql.org/learn/schema/#input-types) as a way to pass complex values to fields. Input objects are often defined as mutation variables, because they give you a compact way to pass in objects to be created: | ||
|
||
```graphql | ||
mutation CreateReviewForEpisode($episode: Episode!, $review: ReviewInput!) { | ||
createReview(episode: $episode, review: $review) { | ||
stars | ||
commentary | ||
} | ||
} | ||
``` | ||
|
||
```swift | ||
let review = ReviewInput(stars: 5, commentary: "This is a great movie!") | ||
apollo.perform(mutation: CreateReviewForEpisodeMutation(episode: .jedi, review: review)) | ||
``` | ||
|
||
<h2 id="designing-mutation-results">Designing mutation results</h2> | ||
|
||
When people talk about GraphQL, they often focus on the data fetching side of things, because that's where GraphQL brings the most value. Mutations can be pretty nice if done well, but the principles of designing good mutations, and especially good mutation result types, are not yet well-understood in the open source community. So when you are working with mutations it might often feel like you need to make a lot of application-specific decisions. | ||
|
||
In GraphQL, mutations can return any type, and that type can be queried just like a regular GraphQL query. So the question is - what type should a particular mutation return? | ||
|
||
In most cases, the data available from a mutation result should be the server developer's best guess of the data a client would need to understand what happened on the server. For example, a mutation that creates a new comment on a blog post might return the comment itself. A mutation that reorders an array might need to return the whole array. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
--- | ||
title: Queries | ||
--- | ||
|
||
On this page, you can learn how to use Apollo iOS to fetch and access GraphQL query results. You can read about GraphQL queries themselves in detail at [graphql.org](http://graphql.org/docs/queries/). | ||
|
||
Note that when using Apollo iOS, you don't have to learn anything special about the query syntax, since everything is just standard GraphQL. Anything you can type into the GraphiQL query explorer, you can also put into `.graphql` files in your project. | ||
|
||
Apollo iOS takes a schema and a set of `.graphql` files and uses these to generate code you can use to execute queries and access typed results. | ||
|
||
> All `.graphql` files in your project (or the subset you specify as input to `apollo-codegen` if you customize the script you define as the code generation build phase) will be combined and treated as one big GraphQL document. That means fragments defined in one `.graphql` file are available to all other `.graphql` files for example, but it also means operation names and fragment names have to be unique and you will receive validation errors if they are not. | ||
<h2 id="fetching-queries">Fetching queries</h2> | ||
|
||
Queries are represented as instances of generated classes conforming to the `GraphQLQuery` protocol. Constructor arguments can be used to define query variables if needed. You pass a query object to `ApolloClient#fetch(query:)` to send the query to the server, execute it, and receive typed results. | ||
|
||
For example, if you define a query called `HeroName`: | ||
|
||
```graphql | ||
query HeroName($episode: Episode) { | ||
hero(episode: $episode) { | ||
name | ||
} | ||
} | ||
``` | ||
|
||
Apollo iOS will generate a `HeroNameQuery` class that you can construct (with variables) and pass to `ApolloClient#fetch(query:)`: | ||
|
||
```swift | ||
apollo.fetch(query: HeroNameQuery(episode: .empire)) { (result, error) in | ||
print(data?.hero?.name) // Luke Skywalker | ||
} | ||
``` | ||
|
||
The `error` parameter to the completion handler signals network or response format errors (such as invalid JSON). | ||
|
||
In addition to an optional `data` property, `result` contains an optional `errors` array with GraphQL errors (for more on this, see the sections on [error handling](https://facebook.github.io/graphql/#sec-Error-handling) and the [response format](https://facebook.github.io/graphql/#sec-Response-Format) in the GraphQL specification). | ||
|
||
<h2 id="typed-query-results">Typed query results</h2> | ||
|
||
Query results are defined as nested immutable structs that at each level only contain the properties defined in the corresponding part of the query definition. This means the type system won't allow you to access fields that are not actually fetched by the query, even if they *are* part of the schema. | ||
|
||
For example, given the following schema: | ||
|
||
```graphql | ||
enum Episode { NEWHOPE, EMPIRE, JEDI } | ||
|
||
interface Character { | ||
id: String! | ||
name: String! | ||
friends: [Character] | ||
appearsIn: [Episode]! | ||
} | ||
|
||
type Human implements Character { | ||
id: String! | ||
name: String! | ||
friends: [Character] | ||
appearsIn: [Episode]! | ||
height(unit: LengthUnit = METER): Float | ||
} | ||
|
||
type Droid implements Character { | ||
id: String! | ||
name: String! | ||
friends: [Character] | ||
appearsIn: [Episode]! | ||
primaryFunction: String | ||
} | ||
``` | ||
|
||
And the following query: | ||
|
||
```graphql | ||
query HeroAndFriendsNames($episode: Episode) { | ||
hero(episode: $episode) { | ||
name | ||
friends { | ||
name | ||
} | ||
} | ||
} | ||
``` | ||
|
||
You can fetch results and access data using the following code: | ||
|
||
```swift | ||
apollo.fetch(query: HeroAndFriendsNamesQuery(episode: .empire)) { (result, error) in | ||
guard let data = result?.data else { return } | ||
print(data.hero?.name) // Luke Skywalker | ||
print(data.hero?.friends?.flatMap { $0?.name }.joined(separator: ", ")) // Han Solo, Leia Organa, C-3PO, R2-D2 | ||
} | ||
``` | ||
|
||
Because the above query won't fetch `appearsIn`, this property is not part of the returned result type and cannot be accessed here. |