The Mobile Buy SDK makes it easy to create custom storefronts in your mobile app, where users can buy products using Apple Pay or their credit card. The SDK connects to the Shopify platform using GraphQL, and supports a wide range of native storefront experiences.
You can generate a complete HTML and .docset documentation by running the Documentation scheme. You can then use a documentation browser like Dash to access the .docset artifact, or browse the HTML directly in the Docs/Buy and Docs/Pay directories.
This is the recommended approach of integration the SDK with your app. You can follow Apple's guide for adding a package dependency to your app for a thorough walkthrough.
Note: If you've forked this repo and are attempting to install from your own git destination, commit, or branch, be sure to include "submodules: true" in the line of your Podfile
The Buy SDK is built on GraphQL. The SDK handles all the query generation and response parsing, exposing only typed models and compile-time checked query structures. It doesn't require you to write stringed queries, or parse JSON responses.
You don't need to be an expert in GraphQL to start using it with the Buy SDK (but it helps if you've used it before). The sections below provide a brief introduction to this system, and some examples of how you can use it to build secure custom storefronts.
The previous version of the Mobile Buy SDK (version 2.0) is based on a REST API. With version 3.0, Shopify is migrating the SDK from REST to GraphQL.
Unfortunately, the specifics of generation GraphQL models make it almost impossible to create a migration path from v2.0 to v3.0 (domains models are not backwards compatible). However, the main concepts are the same across the two versions, such as collections, products, checkouts, and orders.
The Buy SDK is built on a hierarchy of generated classes that construct and parse GraphQL queries and responses. These classes are generated manually by running a custom Ruby script that relies on the GraphQL Swift Generation library. Most of the generation functionality and supporting classes live inside the library. It works by downloading the GraphQL schema, generating Swift class hierarchy, and saving the generated files to the specified folder path. In addition, it provides overrides for custom GraphQL scalar types like DateTime.
All generated request models are derived from the GraphQL.AbstractQuery type. Although this abstract type contains enough functionality to build a query, you should never use it directly. Instead, rely on the typed methods provided in the generated subclasses.
The following example shows a sample query for a shop's name:
// Never do thislet shopQuery = GraphQL.AbstractQuery()
shopQuery.addField(field: "name")
let query = GraphQL.AbstractQuery()
query.addField(field: "shop", subfields: shopQuery)
Both of the above queries produce identical GraphQL queries (see below), but the former approach provides auto-completion and compile-time validation against the GraphQL schema. It will surface an error if a requested field doesn't exist, isn't the correct type, or is deprecated. You also might have noticed that the former approach resembles the GraphQL query language structure (this is intentional). The query is both easier to write and much more legible.
All generated response models are derived from the GraphQL.AbstractResponse type. This abstract type provides a similar key-value type interface to a Dictionary for accessing field values in GraphQL responses. Just like GraphQL.AbstractQuery, you should never use these accessors directly, and instead rely on typed, derived properties in generated subclasses.
The following example builds on the earlier example of accessing the result of a shop name query:
// Never do thislet response: GraphQL.AbstractResponse
let shop = response.field("shop") as! GraphQL.AbstractResponselet name = shop.field("name") as!String
Again, both of the approaches produce the same result, but the former case is preferred: it requires no casting since it already knows about the expected type.
The GraphQL schema defines a Node interface that declares an id field on any conforming type. This makes it convenient to query for any object in the schema given only its id. The concept is carried across to the Buy SDK as well, but requires a cast to the correct type. You need to make sure that the Node type is of the correct type, otherwise casting to an incorrect type will return a runtime exception.
Given this query:
let id = GraphQL.ID(rawValue: "NkZmFzZGZhc")
let query = Storefront.buildQuery { $0
.node(id: id) { $0
.onOrder { $0
.id()
.createdAt()
}
}
}
The Storefront.Order requires a cast:
// response: Storefront.QueryRootlet order = response.nodeas! Storefront.Order
Aliases are useful when a single query requests multiple fields with the same names at the same nesting level, since GraphQL allows only unique field names. Multiple nodes can be queried by using a unique alias for each one:
The Graph.Client is a network layer built on top of URLSession that executes query and mutation requests. It also simplifies polling and retrying requests. To get started with Graph.Client, you need the following:
Your shop's .myshopify.com domain
Your API key, which you can find in your shop's admin page
A URLSession (optional), if you want to customize the configuration used for network requests or share your existing URLSession with the Graph.Client
(optional) The buyer's current locale. Supported values are limited to locales available to your shop.
let client = Graph.Client(
shopDomain: "shoes.myshopify.com",
apiKey: "dGhpcyBpcyBhIHByaXZhdGUgYXBpIGtleQ"
)
If your store supports multiple languages, then the Storefront API can return translated resource types and fields. Learn more about translating content.
// Initializing a client to return translated contentlet client = Graph.Client(
shopDomain: "shoes.myshopify.com",
apiKey: "dGhpcyBpcyBhIHByaXZhdGUgYXBpIGtleQ",
locale: Locale.current
)
GraphQL specifies two types of operations: queries and mutations. The Client exposes these as two type-safe operations, although it also offers some conveniences for retrying and polling in each one.
Semantically, a GraphQL query operation is equivalent to a GET RESTful call. It guarantees that no resources will be mutated on the server. With Graph.Client, you can perform a query operation using:
Semantically a GraphQL mutation operation is equivalent to a PUT, POST or DELETE RESTful call. A mutation is almost always accompanied by an input that represents values to be updated and a query to fetch fields of the updated resource. You can think of a mutation as a two-step operation where the resource is first modified, and then queried using the provided query. The second half of the operation is identical to a regular query request.
With Graph.Client you can perform a mutation operation using:
A mutation will often rely on some kind of user input. Although you should always validate user input before posting a mutation, there are never guarantees when it comes to dynamic data. For this reason, you should always request the userErrors field on mutations (where available) to provide useful feedback in your UI regarding any issues that were encountered in the mutation query. These errors can include anything from Invalid email address to Password is too short.
Both queryGraphWith and mutateGraphWith accept an optional RetryHandler<R: GraphQL.AbstractResponse>. This object encapsulates the retry state and customization parameters for how the Client will retry subsequent requests (such as after a set delay, or a number of retries). By default, the retryHandler is nil and no retry behavior will be provided. To enable retry or polling, create a handler with a condition. If the handler.condition and handler.canRetry evaluate to true, then the Client will continue executing the request:
let handler = Graph.RetryHandler<Storefront.QueryRoot>() { (query, error) ->Boolinif myCondition {
returntrue// will retry
}
returnfalse// will complete the request, either succeed or fail
}
The retry handler is generic, and can handle both query and mutation requests equally well.
Network queries and mutations can be both slow and expensive. For resources that change infrequently, you might want to use caching to help reduce both bandwidth and latency. Since GraphQL relies on POST requests, we can't easily take advantage of the HTTP caching that's available in URLSession. For this reason, the Graph.Client is equipped with an opt-in caching layer that can be enabled client-wide or on a per-request basis.
IMPORTANT: Caching is provided only for query operations. It isn't available for mutation operations or for any other requests that provide a retryHandler.
There are four available cache policies:
.cacheOnly - Fetch a response from the cache only, ignoring the network. If the cached response doesn't exist, then return an error.
.networkOnly - Fetch a response from the network only, ignoring any cached responses.
.cacheFirst(expireIn: Int) - Fetch a response from the cache first. If the response doesn't exist or is older than expireIn, then fetch a response from the network
.networkFirst(expireIn: Int) - Fetch a response from the network first. If the network fails and the cached response isn't older than expireIn, then return cached data instead.
Enable client-wide caching
You can enable client-wide caching by providing a default cachePolicy for any instance of Graph.Client. This sets all query operations to use your default cache policy, unless you specify an alternate policy for an individual request.
In this example, we set the client's cachePolicy property to cacheFirst:
let client = Graph.Client(shopDomain: "...", apiKey: "...")
client.cachePolicy= .cacheFirst
Now, all calls to queryGraphWith will yield a task with a .cacheFirst cache policy.
If you want to override a client-wide cache policy for an individual request, then specify an alternate cache policy as a parameter of queryGraphWith:
In this example, the task cache policy changes to .networkFirst(expireIn: 20), which means that the cached response will be valid for 20 seconds from the time the response is received.
The completion for either a query or mutation request will always contain an optional Graph.QueryError that represents the current error state of the request. It's important to note that error and response are NOT mutually exclusive. It is perfectly valid to have a non-nil error and response. The presence of an error can represent both a network error (such as a network error, or invalid JSON) or a GraphQL error (such as invalid query syntax, or a missing parameter). The Graph.QueryError is an enum, so checking the type of error is trivial:
If the error is of type .invalidQuery, then an array of Reason objects is returned. These will provide more in-depth information about the query error. Keep in mind that these errors are not meant to be displayed to the end-user. They are for debugging purposes only.
The following example shows a GraphQL error response for an invalid query:
请发表评论