
The Mobile Buy SDK makes it easy to create custom storefronts in your mobile app. The SDK connects to the Shopify platform using GraphQL, and supports a wide range of native storefront experiences.
Mobile Buy SDK for Android is represented by a runtime module that provides support to build and execute GraphQL queries.
As of the 2025.1.0 release, the Mobile Buy SDK now uses a modified CalVer versioning scheme. This was done to align with the quarterly Storefront GraphQL API releases and bring clarity to what version a library release corresponds to.
The format is yyyy.mm.patch, where the first two components match the API version, and the last component corresponds to non-breaking bug fixes within an API version cycle. In practice, this means that unlike SemVer, there may be breaking GraphQL schema changes between "minor" versions. Learn more about API release schedules at Shopify.
As of the 2025.1.0 release, the library is published both to Maven Central as well as GitHub Packages.
implementation("com.shopify.mobilebuysdk:buy3:2026.4.0")
<dependency>
<groupId>com.shopify.mobilebuysdk</groupId>
<artifactId>buy3</artifactId>
<version>2026.4.0</version>
</dependency>
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 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. 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 Date.
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
}
}
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.
Node protocolGraphQL 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 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.
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:
.myshopify.com domainOkHttpClient (optional), if you want to customize the configuration used for network requests or share your existing OkHttpClient with the GraphClientGraphClient.builder(this)
.shopDomain(BuildConfig.SHOP_DOMAIN)
.accessToken(BuildConfig.API_KEY)
.httpClient(httpClient) // optional
.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.
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.
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.
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:
RetryHandler.delay(long delay, TimeUnit timeUnit) or RetryHandler.exponentialBackoff(long delay, TimeUnit timeUnit, float multiplier).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.
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$ claude mcp add mobile-buy-sdk-android \
-- python -m otcore.mcp_server <graph>