Shopify Mobile Buy SDK for Android

repository·main·Indexed 42 days ago

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

A native Android SDK for building custom Shopify storefront experiences using GraphQL. It provides a strongly-typed Java wrapper for query generation and response parsing, featuring a GraphClient for executing queries and mutations, support for the Node protocol, and advanced search capabilities including boolean and comparison operators. Version 2026.4.0 uses a CalVer versioning scheme tied to the Storefront GraphQL API.

Tokens
11.6K
Snippets
34
Records
36
Agent score
54%

What's inside Shopify Mobile Buy SDK for Android

  1. Handle GraphQL errors vs GraphErrors

    main

    When working with GraphClient, you must distinguish between two types of errors:

    1. GraphQL Errors (Internal Query Errors)

    These are returned inside the GraphResponse object. They represent issues with the GraphQL query itself (e.g., a field doesn't exist).

    • Note: errors and data are not mutually exclusive; a response can contain both.
    • Usage: These are for debugging purposes only and should not be shown to end-users.
    • Check for them using response.hasErrors() and retrieve details via response.formatErrorMessage().

    2. GraphError (Execution/Network Errors)

    These represent critical failures in executing or processing the request and are delivered via the onFailure callback.

    Common GraphError types include:

    • GraphCallCanceledError: The call was canceled.
    • GraphHttpError: The HTTP response status code was not in the 200 series.
    • GraphNetworkError: Network issues like timeouts or being offline.
    • GraphParseError: Failed to parse the JSON response.
    • GraphError: An unknown reason prevented execution.
    // Handling GraphError in onFailure
    @Override public void onFailure(GraphError error) {
      if (error instanceof GraphCallCanceledError) {
        Log.e(TAG, "Call has been canceled", error);
      } else if (error instanceof GraphHttpError) {
        Log.e(TAG, "Http request failed: " + ((GraphHttpError) error).formatMessage(), error);
      } else if (error instanceof GraphNetworkError) {
        Log.e(TAG, "Network is not available", error);
      } else if (error instanceof GraphParseError) {
        Log.e(TAG, "Failed to parse GraphQL response", error);
      } else {
        Log.e(TAG, "Failed to execute GraphQL request", error);
      }
    }
  2. How pagination works with edges and nodes

    main

    The Buy SDK inherits GraphQL's pagination model using edges, nodes, cursors, and pageInfo.

    To paginate through a list (e.g., products in a collection):

    1. Request a specific number of items using .first(n).
    2. Use .after(cursor) to fetch the next set of results.
    3. Include .pageInfo() in your query to check .hasNextPage().
    4. Retrieve the .cursor() from an edge to use as the starting point for the next request.
    GraphClient client = ...;
    
    Storefront.QueryRootQuery query = Storefront.query(rootQuery -> rootQuery
      .node(new ID("IjoxNDg4MTc3MzEsImxhc3R"), nodeQuery -> nodeQuery
        .onCollection(collectionQuery -> collectionQuery
          .products(
            args -> args
              .first(10)
              .after(productPageCursor),
            productConnectionQuery -> productConnectionQuery
              .pageInfo(pageInfoQuery -> pageInfoQuery
                .hasNextPage()
              )
              .edges(productEdgeQuery -> productEdgeQuery
                .cursor()
                .node(productQuery -> productQuery
                  .title()
                  .productType()
                  .description()
                )
              )
          )
        )
      )
    );
    
    client.queryGraph(query).enqueue(new GraphCall.Callback<Storefront.QueryRoot>() {
    
      @Override public void onResponse(@NonNull GraphResponse<Storefront.QueryRoot> response) {
        Storefront.Collection collection = (Storefront.Collection) response.data().getNode();
        boolean hasNextProductPage = collection.getProducts().getPageInfo().getHasNextPage();
        List<Storefront.Product> products = new ArrayList<>();
        for (Storefront.ProductEdge productEdge : collection.getProducts().getEdges()) {
          String productPageCursor = productEdge.getCursor();
          products.add(productEdge.getNode());
        }
      }
    
      ...
    });
  3. Update customer information using Input<T>

    main

    When updating customer information (e.g., using Storefront.CustomerUpdateInput), the SDK uses Input<T> to distinguish between providing a new value, providing a null value, or leaving a field undefined.

    • To update a field: Use Input.value(newValue).
    • To remove a field (set to null): Use Input.value(null).
    • To leave a field unchanged: Do not call the setter for that field. Fields default to Input.undefined(), meaning they are omitted from the resulting GraphQL mutation.

    Example: Updating a phone number or removing it entirely.

    // Update to a new number
    Storefront.CustomerUpdateInput input = new Storefront.CustomerUpdateInput()
      .setPhone(Input.value("1-123-456-7890"));
    
    // Remove the phone number (set to null)
    Storefront.CustomerUpdateInput input = new Storefront.CustomerUpdateInput()
      .setPhone(Input.value(null));
  4. Understand Mobile Buy SDK versioning

    main

    As of version 2025.1.0, the SDK uses a modified CalVer versioning scheme: yyyy.mm.patch.

    • The first two components (yyyy.mm) match the Shopify Storefront GraphQL API version.
    • The third component (patch) corresponds to non-breaking bug fixes within an API version cycle.

    Note: Unlike Semantic Versioning (SemVer), breaking GraphQL schema changes may occur between "minor" versions because they are tied to the API release cycle.

  5. Query objects using the Node protocol

    main

    The GraphQL schema defines a Node interface that includes an id field. The SDK allows you to query for any object in the schema using its ID.

    When retrieving a node, you must cast the result to the specific expected type (e.g., Storefront.Product). Casting to an incorrect type will result in a runtime exception.

    Querying a Node

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

    Accessing a Node

    Storefront.QueryRoot response = ...;
    String title = ((Storefront.Product)response.getNode()).getTitle();
    ID id = new ID("NkZmFzZGZhc");
    Storefront.query(rootQueryBuilder ->
      rootQueryBuilder
        .node(id, nodeQuery ->
          nodeQuery
            .onProduct(productQuery ->
              productQuery
                .title()
            )
        )
    );
  6. Install the Mobile Buy SDK for Android

    main

    The Mobile Buy SDK is a runtime module that provides support for building and executing GraphQL queries. You can include it in your project using Gradle or Maven.

    Gradle

    Add the following dependency to your build.gradle file:

    implementation("com.shopify.mobilebuysdk:buy3:2026.4.0")

    Maven

    Add the following dependency to your pom.xml:

    <dependency>
      <groupId>com.shopify.mobilebuysdk</groupId>
      <artifactId>buy3</artifactId>
      <version>2026.4.0</version>
    </dependency>
    implementation("com.shopify.mobilebuysdk:buy3:2026.4.0")
  7. Use GraphQL Aliases in queries

    main

    If you need to request multiple fields with the same name at the same nesting level, use GraphQL aliases. The SDK provides .withAlias("alias_name") to define the alias in the query and .withAlias("alias_name") on the response to retrieve the aliased node.

    Defining Aliases in a Query

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

    Accessing Aliased Nodes

    Storefront.QueryRoot response = ...;
    Storefront.Collection collection = (Storefront.Collection) response.withAlias("collection").getNode();
    Storefront.Product product = (Storefront.Product) response.withAlias("product").getNode();
    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()
                ...
            )
        )
    );
  8. Negate field matches in searches

    main

    You can exclude objects that match a specific field by prefixing the field name with a minus sign (-). For example, to find all collections that are not of the type runners, use -field:search_term.

    .collections(arg -> arg.query("-collection_type:runners"), ...)
  9. Access data using Response Models

    main

    All generated response models derive from AbstractResponse. While AbstractResponse provides a key-value interface similar to a Map, you should never use these accessors directly. Instead, always use the typed, derived properties provided by the generated subclasses to ensure type safety and avoid manual casting.

    The Correct Way (Typed Accessors)

    // The right way
    Storefront.QueryRoot response = ...;
    String name = response.getShop().getName();

    The Incorrect Way (Manual Map Access)

    // The wrong way (never do this)
    AbstractResponse response = ...;
    AbstractResponse shop = (AbstractResponse) response.get("shop");
    String name = (String) shop.get("name");
    // The right way
    Storefront.QueryRoot response = ...;
    
    String name = response.getShop().getName();
  10. Use boolean operators in search queries

    main

    You can construct complex queries using boolean operators similar to SQL. Supported operators include AND and OR. You can also use parentheses () to group search terms and control the order of operations.

    // Search for products tagged 'blue' AND of type 'sneaker'
    .products(arg -> arg.query("tag:blue AND product_type:sneaker"), ...)
    
    // Grouping terms: (blue AND sneaker) OR red
    .products(arg -> arg.query("(tag:blue AND product_type:sneaker) OR tag:red"), ...)
  11. Initialize the GraphClient

    main

    The GraphClient is the network layer used to execute GraphQL query and mutation requests. It is built on top of OkHttp and handles request preparation, polling, and retrying.

    To initialize a GraphClient, you need:

    • Your shop's .myshopify.com domain.
    • Your API key (found in the Shopify admin).
    • An optional OkHttpClient if you wish to customize network configuration or share an existing client.

    Use the GraphClient.builder() to construct the instance.

    GraphClient.builder(this)
      .shopDomain(BuildConfig.SHOP_DOMAIN)
      .accessToken(BuildConfig.API_KEY)
      .httpClient(httpClient) // optional
      .build()
  12. Check for null or empty values using the Exists operator

    main

    To check if a field is null or empty, use the * (exists) operator. To find objects where a field is empty or null, combine the * operator with negation (-).

    // Find products that don't have any tags
    .products(arg -> arg.query("-tag:*"), ...)