Skip to content

Commit

Permalink
Add usage docs
Browse files Browse the repository at this point in the history
  • Loading branch information
martijnwalraven committed Oct 19, 2016
1 parent 8770cb2 commit 84d2870
Show file tree
Hide file tree
Showing 6 changed files with 355 additions and 0 deletions.
6 changes: 6 additions & 0 deletions docs/_config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ sidebar_categories:
null:
- index
- installation
Usage:
- downloading-schema
- initialization
- queries
- fragments
- mutations
github_repo: apollostack/ios-docs
content_root: source

Expand Down
17 changes: 17 additions & 0 deletions docs/source/downloading-schema.md
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>"
```
130 changes: 130 additions & 0 deletions docs/source/fragments.md
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.
23 changes: 23 additions & 0 deletions docs/source/initialization.md
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))
```
84 changes: 84 additions & 0 deletions docs/source/mutations.md
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.
95 changes: 95 additions & 0 deletions docs/source/queries.md
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.

0 comments on commit 84d2870

Please sign in to comment.