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

Shopify/mobile-buy-sdk-android: Shopify’s Mobile Buy SDK makes it simple to sel ...

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

开源软件名称(OpenSource Name):

Shopify/mobile-buy-sdk-android

开源软件地址(OpenSource Url):

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

开源编程语言(OpenSource Language):

Java 93.6%

开源软件介绍(OpenSource Introduction):

Mobile Buy SDK

Tests GitHub license GitHub release

Mobile Buy SDK

The Mobile Buy SDK makes it easy to create custom storefronts in your mobile app, where users can buy products using Google 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

Installation

Mobile Buy SDK for Android is represented by runtime module that provides support to build and execute GraphQL queries.

Gradle:
implementation 'com.shopify.mobilebuysdk:buy3:3.2.3'
or Maven:
<dependency>
  <groupId>com.shopify.mobilebuysdk</groupId>
  <artifactId>buy3</artifactId>
  <version>3.2.3</version>
</dependency>

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 SDK (version 2.0) is based on a REST API. With version 3.0, Shopify is migrating 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 backward 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 response. These classes are generated manually by running a custom Ruby script that relies on the GraphQL Java Generation library. Most of the generation functionality and supporting classes live inside the library. It works by downloading the GraphQL schema, generating Java 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 represented by interfaces with one method define that takes single argument, generated query builder. Every query starts with generated Storefront.QueryRootQueryDefinition interface that defines the root of your query.

Let's take a look at an example query for a shop's name:

QueryRootQuery query = Storefront.query(new Storefront.QueryRootQueryDefinition() {
    @Override public void define(final Storefront.QueryRootQuery rootQueryBuilder) {
      rootQueryBuilder.shop(new Storefront.ShopQueryDefinition() {
        @Override public void define(final Storefront.ShopQuery shopQueryBuilder) {
          shopQueryBuilder.name();
        }
      });
    }
})

In this example:

  • Storefront.query is the entry point for building GraphQL queries.
  • Storefront.QueryRootQueryDefinition represents the root of the query where we ask for the shop's rootQueryBuilder.shop.
  • Storefront.ShopQueryDefinition represents the subquery definition for shop field, where we request the shop's shopQueryBuilder.name.

Request models are generated in such way where lambda expressions can come in handy. We can use lambda expressions to make the initial query more concise:

QueryRootQuery query = Storefront.query(rootQueryBuilder ->
  rootQueryBuilder
    .shop(shopQueryBuilder ->
      shopQueryBuilder
        .name()
    )
)

The code example above produces the following GraphQL query (you can call query.toString() to see a built GraphQL query):

query {
  shop {
    name
  }
}

Response models

All generated response models are derived from the AbstractResponse type. This abstract class provides a similar key-value type interface to a Map for accessing field values in GraphQL responses. You should never use these accessors directly, and instead rely on typed, derived properties in generated subclasses.

Let's continue the example of accessing the result of a shop name query:

// The right way

Storefront.QueryRoot response = ...;

String name = response.getShop().getName();

Never use the abstract class directly:

// The wrong way (never do this)

AbstractResponse response = ...;

AbstractResponse shop = (AbstractResponse) response.get("shop");
String name = (String) shop.get("name");

Again, both of the approaches produce the same result, but the former case is safe and requires no casting since it already knows about the expected type.

The Node protocol

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:

ID id = new ID("NkZmFzZGZhc");
Storefront.query(rootQueryBuilder ->
  rootQueryBuilder
    .node(id, nodeQuery ->
      nodeQuery
        .onProduct(productQuery ->
          productQuery
            .title()
            ...
        )
    )
);

The Storefront.Order requires a cast:

Storefront.QueryRoot response = ...;

String title = ((Storefront.Product)response.getNode()).getTitle();

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:

Storefront.query(rootQueryBuilder ->
  rootQueryBuilder
    .node(new ID("NkZmFzZGZhc"), nodeQuery ->
      nodeQuery
      .onCollection(collectionQuery ->
        collectionQuery
          .withAlias("collection")
          .title()
          .description()
          ...
      )
    )
    .node(new ID("GZhc2Rm"), nodeQuery ->
      nodeQuery
        .onProduct(productQuery ->
          productQuery
            .withAlias("product")
            .title()
            .description()
            ...
        )
    )
);

Accessing the aliased nodes is similar to a plain node:

Storefront.QueryRoot response = ...;

Storefront.Collection collection = (Storefront.Collection) response.withAlias("collection").getNode();
Storefront.Product product = (Storefront.Product) response.withAlias("product").getNode();

Learn more about GraphQL aliases.

GraphClient

The GraphClient is a network layer built on top of Square's OkHttp client that prepares GraphCall to execute query and mutation requests. It also simplifies polling and retrying requests. To get started with GraphClient, you need the following:

  • Your shop's .myshopify.com domain
  • Your API key, which you can find in your shop's admin
  • OkHttpClient (optional), if you want to customize the configuration used for network requests or share your existing OkHttpClient with the GraphClient
  • Settings for HTTP cache (optional), like the path to the cache folder and maximum allowed size in bytes
  • HTTP cache policy (optional), to be used as default for all GraphQL query operations (it be ignored for mutation operations, which aren't supported). By default, the HTTP cache policy is set to NETWORK_ONLY.
GraphClient.builder(this)
  .shopDomain(BuildConfig.SHOP_DOMAIN)
  .accessToken(BuildConfig.API_KEY)
  .httpClient(httpClient) // optional
  .httpCache(new File(getApplicationContext().getCacheDir(), "/http"), 10 * 1024 * 1024) // 10mb for http cache
  .defaultHttpCachePolicy(HttpCachePolicy.CACHE_FIRST.expireAfter(5, TimeUnit.MINUTES)) // cached response valid by default for 5 minutes
  .build()

GraphQL specifies two types of operations: queries and mutations. The GraphClient exposes these as two type-safe operations, while also offering some conveniences for retrying and polling in each.

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 GraphClient, you can perform a query operation using:

GraphClient graphClient = ...;
Storefront.QueryRootQuery query = ...;

QueryGraphCall call = graphClient.queryGraph(query);

For example, let's take a look at how we can query for a shop's name:

GraphClient graphClient = ...;

Storefront.QueryRootQuery query = Storefront.query(rootQuery ->
  rootQuery
    .shop(shopQuery ->
      shopQuery
        .name()
    )
);

QueryGraphCall call = graphClient.queryGraph(query);

call.enqueue(new GraphCall.Callback<Storefront.QueryRoot>() {

  @Override public void onResponse(@NonNull GraphResponse<Storefront.QueryRoot> response) {
    String name = response.data().getShop().getName();
  }

  @Override public void onFailure(@NonNull GraphError error) {
    Log.e(TAG, "Failed to execute query", error);
  }
});

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 GraphClient, you can perform a mutation operation using:

GraphClient graphClient = ...;
Storefront.MutationQuery query = ...;

MutationGraphCall call = graphClient.mutateGraph(query);

For example, let's take a look at how we can reset a customer's password using a recovery token:

GraphClient graphClient = ...;

Storefront.CustomerResetInput input = new Storefront.CustomerResetInput("c29tZSB0b2tlbiB2YWx1ZQ", "abc123");

Storefront.MutationQuery query = Storefront.mutation(rootQuery ->
  rootQuery
    .customerReset(new ID("YSBjdXN0b21lciBpZA"), input, payloadQuery ->
      payloadQuery
        .customer(customerQuery ->
          customerQuery
            .firstName()
            .lastName()
        )
        .userErrors(userErrorQuery ->
          userErrorQuery
            .field()
            .message()
        )
    )
);

MutationGraphCall call = graphClient.mutateGraph(query);

call.enqueue(new GraphCall.Callback<Storefront.Mutation>() {

  @Override public void onResponse(@NonNull final GraphResponse<Storefront.Mutation> response) {
    if (response.data().getCustomerReset().getUserErrors().isEmpty()) {
      String firstName = response.data().getCustomerReset().getCustomer().getFirstName();
      String lastName = response.data().getCustomerReset().getCustomer().getLastName();
    } else {
      Log.e(TAG, "Failed to reset customer");
    }
  }

  @Override public void onFailure(@NonNull final GraphError error) {
    Log.e(TAG, "Failed to execute query", error);
  }
});

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 QueryGraphCall and MutationGraphCall have an enqueue function that accepts RetryHandler. This object encapsulates the retry state and customization parameters for how the GraphCall will retry subsequent requests (such as after a delay, or a number of retries).

To enable retry or polling:

  1. Create a handler with a condition from one of two factory methods: RetryHandler.delay(long delay, TimeUnit timeUnit) or RetryHandler.exponentialBackoff(long delay, TimeUnit timeUnit, float multiplier).
  2. Provide an optional retry condition for response whenResponse(Condition<GraphResponse<T>> retryCondition) or for error whenError(Condition<GraphError> retryCondition).

If the retryCondition evaluates to true, then the GraphCall will continue to execute the request:

GraphClient graphClient = ...;
Storefront.QueryRootQuery shopNameQuery = ...;

QueryGraphCall call = graphClient.queryGraph(shopNameQuery);

call.enqueue(new GraphCall.Callback<Storefront.QueryRoot>() {

  @Override public void onResponse(GraphResponse<Storefront.QueryRoot> response) {
    ...
  }

  @Override public void onFailure(GraphError error) {
    ...
  }
}, null, RetryHandler.delay(1, TimeUnit.SECONDS)
  .maxCount(5)
  .<Storefront.QueryRoot>whenResponse(response -> response.data().getShop().getName().equals("Empty"))
      .build());
}

The retry handler is generic, and can handle both QueryGraphCall and MutationGraphCall 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 OkHttp. For this reason, the GraphClient 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.

There are four available cache policies HttpCachePolicy:

  • CACHE_ONLY - Fetch a response from the cache only, ignoring the network. If the cached response doesn't exist or is expired, then return an error.
  • NETWORK_ONLY - Fetch a response from the network only, ignoring any cached responses.
  • CACHE_FIRST - Fetch a response from the cache first. If the response doesn't exist or is expired, then fetch a response from the network.
  • NETWORK_FIRST - Fetch a response from the network first. If the network fails and the cached response isn't expired, then return cached data instead.

For CACHE_ONLY, CACHE_FIRST and NETWORK_FIRST policies you can define the timeout after what cached response is treated as expired and will be evicted from the http cache, expireAfter(expireTimeout, timeUnit).

Enable client-wide caching

You can enable client-wide caching by providing a default defaultHttpCachePolicy for any instance of GraphClient. 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 defaultHttpCachePolicy property to CACHE_FIRST :

GraphClient.Builder builder = ...;
builder.defaultHttpCachePolicy(HttpCachePolicy.CACHE_FIRST)

Now, all calls to queryGraph will yield a QueryGraphCall with a CACHE_FIRST 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 QueryGraphCall:

GraphClient client = ...;
QueryGraphCall queryCall = client.queryGraph(query)
  .cachePolicy(HttpCachePolicy.NETWORK_FIRST.expireAfter(5, TimeUnit.MINUTES))

In this example, the queryCall cache policy changes to NETWORK_FIRST, which means that the cached response will be valid for 5 minutes from the time the response is received.

Errors

There are two types of errors that you need to handle in the response callback:

  • Error returns a list of errors inside GraphResponse, which represent errors related to GraphQL query itself. These should be used for debugging purposes only.
  • GraphError represents more critical errors related to the GraphQL query execution and processing response.

GraphQL Error

The GraphResponse class represents a GraphQL response for either a QueryGraphCall or MutationGraphCall request. It can also contain a value for Error, which represents the current error state of the GraphQL query.

It's important to note that errors and data are NOT mutually exclusive. That is to say that it's perfectly valid to have a non-null error and data. Error 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 debug purposes only.

GraphClient graphClient = ...;

QueryRootQuery query = Storefront.query(rootQueryBuilder ->
  rootQueryBuilder
    .shop(shopQueryBuilder ->
      shopQueryBuilder
        .name()
    )
);

QueryGraphCall call = graphClient.queryGraph(query);
call.enqueue(new GraphCall.Callback<Storefront.QueryRoot>() {

  @Override public void onResponse(GraphResponse<Storefront.QueryRoot> response) {
  	if (response.hasErrors()) {
     String errorMessage = response.formatErrorMessage();
  	}
  }

  @Override public void onFailure(GraphError error) {

  }
});

The following example shows a GraphQL error response:

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

Learn more about GraphQL errors

GraphError

Errors for either a QueryGraphCall or MutationGraphCall request are defined by the hierarchy of the GraphError abstraction, which represents critical errors for query execution. These errors appear in the GraphCall.Callback#onFailure callback call. The error codes include:

  • GraphError: GraphCall can't be executed due to an unknown reason
  • GraphCallCanceledError: GraphCall has been canceled
  • GraphHttpError: GraphCall executed but the HTTP response status code is not from the 200 series
  • GraphNetworkError: GraphCall can't be executed due to network issues, such as a timeout or the network being offline
  • GraphParseError: GraphCall executed but parsing JSON response has failed

To handle


鲜花

握手

雷人

路过

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

请发表评论

全部评论

专题导读
热门推荐
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

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

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

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