• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    迪恩网络公众号

Shopify/mobile-buy-sdk-ios: Shopify’s Mobile Buy SDK makes it simple to sell ph ...

原作者: [db:作者] 来自: 网络 收藏 邀请

开源软件名称(OpenSource Name):

Shopify/mobile-buy-sdk-ios

开源软件地址(OpenSource Url):

https://github.com/Shopify/mobile-buy-sdk-ios

开源编程语言(OpenSource Language):

Swift 99.4%

开源软件介绍(OpenSource Introduction):

Mobile Buy SDK

Tests GitHub Release Carthage compatible Swift Package Manager compatible GitHub license

Mobile Buy SDK

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.

Table of contents

Documentation

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.

The documentation is generated using Jazzy.

Installation

Download the latest version

Swift Package Manager

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.

Dynamic Framework Installation

  1. Add Buy as a git submodule by running:
git submodule add [email protected]:Shopify/mobile-buy-sdk-ios.git
  1. Ensure that all submodules of Buy SDK have also been updated by running:
git submodule update --init --recursive
  1. Drag the Buy.xcodeproj into your application project.
  2. Add Buy.framework target as a dependency:
    1. Navigate to Build Phases > Target Dependencies.
    2. Add Buy.framework.
  3. Link Buy.framework:
    1. Navigate to Build Phases > Link Binary With Libraries.
    2. Add Buy.framework.
  4. Make sure that the framework is copied into the bundle:
    1. Navigate to Build Phases > New Copy Files Phase.
    2. From the Destination dropdown, select Frameworks.
    3. Add Buy.framework.
  5. Import into your project files using import Buy.

See the Storefront sample app for an example of how to add the Buy target a dependency.

Carthage

  1. Add the following line to your Cartfile:
github "Shopify/mobile-buy-sdk-ios"
  1. Run carthage update.
  2. Follow the steps to link the dynamic framework that Carthage produced.
  3. Import the SDK module:
import Buy

CocoaPods

  1. Add the following line to your podfile:
pod "Mobile-Buy-SDK"
  1. Run pod install.
  2. Import the SDK module:
import MobileBuySDK

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

Getting started

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.

Migration from SDK v2.0

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.

Code Generation

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.

Request Models

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:

let query = Storefront.buildQuery { $0
    .shop { $0
        .name()
    }
}

Never use the abstract class directly:

// Never do this

let 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.

query {
  shop {
    name
  }
}

Response models

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:

// response: Storefront.QueryRoot

let name: String = response.shop.name

Never use the abstract class directly:

// Never do this

let response: GraphQL.AbstractResponse

let shop = response.field("shop") as! GraphQL.AbstractResponse
let 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 Node protocol

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.QueryRoot

let order = response.node as! Storefront.Order

Aliases

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:

let query = Storefront.buildQuery { $0
    .node(aliasSuffix: "collection", id: GraphQL.ID(rawValue: "NkZmFzZGZhc")) { $0
        .onCollection { $0
            // fields for Collection
        }
    }
    .node(aliasSuffix: "product", id: GraphQL.ID(rawValue: "GZhc2Rm")) { $0
        .onProduct { $0
            // fields for Product
        }
    }
}

Accessing the aliased nodes is similar to a plain node:

// response: Storefront.QueryRoot

let collection = response.aliasedNode(aliasSuffix: "collection") as! Storefront.Collection
let product    = response.aliasedNode(aliasSuffix: "product")    as! Storefront.Product

Learn more about GraphQL aliases.

Graph.Client

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 content
let 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.

Queries

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:

public func queryGraphWith(_ query: Storefront.QueryRootQuery, retryHandler: RetryHandler<Storefront.QueryRoot>? = default, completionHandler: QueryCompletion) -> Task

The following example shows how you can query for a shop's name:

let query = Storefront.buildQuery { $0
    .shop { $0
        .name()
    }
}

let task = client.queryGraphWith(query) { response, error in
    if let response = response {
        let name = response.shop.name
    } else {
        print("Query failed: \(error)")
    }
}
task.resume()

Learn more about GraphQL queries.

Mutations

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:

public func mutateGraphWith(_ mutation: Storefront.MutationQuery, retryHandler: RetryHandler<Storefront.Mutation>? = default, completionHandler: MutationCompletion) -> Task

The following example shows how you can reset a customer's password using a recovery token:

let customerID = GraphQL.ID(rawValue: "YSBjdXN0b21lciBpZA")
let input      = Storefront.CustomerResetInput.create(resetToken: "c29tZSB0b2tlbiB2YWx1ZQ", password: "abc123")
let mutation   = Storefront.buildMutation { $0
    .customerReset(id: customerID, input: input) { $0
        .customer { $0
            .id()
            .firstName()
            .lastName()
        }
        .userErrors { $0
            .field()
            .message()
        }
    }
}

let task = client.mutateGraphWith(mutation) { response, error in
    if let mutation = response?.customerReset {

        if let customer = mutation.customer, !mutation.userErrors.isEmpty {
            let firstName = customer.firstName
            let lastName = customer.lastName
        } else {

            print("Failed to reset password. Encountered invalid fields:")
            mutation.userErrors.forEach {
                let fieldPath = $0.field?.joined() ?? ""
                print("  \(fieldPath): \($0.message)")
            }
        }

    } else {
        print("Failed to reset password: \(error)")
    }
}
task.resume()

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.

Learn more about GraphQL mutations.

Retry

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) -> Bool in
    if myCondition {
        return true // will retry
    }
    return false // will complete the request, either succeed or fail
}

The retry handler is generic, and can handle both query and mutation requests equally well.

Caching

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:

let task = client.queryGraphWith(query, cachePolicy: .networkFirst(expireIn: 20)) { query, error in
    // ...
}

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.

Errors

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:

let task = client.queryGraphWith(query) { response, error in
    if let response = response {
        // Do something
    } else {

        if let error = error, case .http(let statusCode) = error {
            print("Query failed. HTTP error code: \(statusCode)")
        }
    }
}
task.resume()

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:

{
  "errors": [
    {
      "message": "Field 'Shop' doesn't exist on type 'QueryRoot'",
      "locations": [
        {
          "line": 2,
          "column": 90
        }
      ],
      "fields": ["query CollectionsWithProducts", "Shop"]
    }
  ]
}

Learn more about GraphQL errors.

Search

鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

热门推荐
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap