Skip to main content

Products API Reference

The Products API allows you to fetch, search, and manage products in your Flutter app using GraphQL queries and mutations.

Queries

Get All Products

Fetch a list of products with pagination support.

Query
const String GET_PRODUCTS = r'''
query GetProducts($limit: Int, $offset: Int, $categoryId: ID) {
products(limit: $limit, offset: $offset, categoryId: $categoryId) {
id
title
description
supplier
sku
quantity
optionsEnabled
price {
amount
currencyCode
}
images {
url
order
}
options {
id
name
order
values
}
variants {
id
title
sku
price
quantity
originalPrice {
amount
currencyCode
}
}
}
}
''';

Usage
import 'package:graphql_flutter/graphql_flutter.dart';

class ProductService {
static Future<List<Product>> getProducts({
int limit = 20,
int offset = 0,
String? categoryId,
}) async {
final QueryOptions options = QueryOptions(
document: gql(GET_PRODUCTS),
variables: {
'limit': limit,
'offset': offset,
if (categoryId != null) 'categoryId': categoryId,
},
);

final QueryResult result = await GraphQLConfig.client.query(options);

if (result.hasException) {
throw Exception('Failed to fetch products: ${result.exception}');
}

final List productsData = result.data?['products'] ?? [];
return productsData.map((data) => Product.fromJson(data)).toList();
}
}

Get Product by ID

Fetch a specific product with all its details.

Query
const String GET_PRODUCT_BY_ID = r'''
query GetProductById($id: ID!) {
product(id: $id) {
id
title
description
supplier
sku
quantity
optionsEnabled
price {
amount
currencyCode
}
images {
url
order
}
options {
id
name
order
values
}
variants {
id
title
sku
price
quantity
barcode
originalPrice {
amount
currencyCode
}
}
}
}
''';

Usage
static Future<Product?> getProductById(String productId) async {
final QueryOptions options = QueryOptions(
document: gql(GET_PRODUCT_BY_ID),
variables: {'id': productId},
);

final QueryResult result = await GraphQLConfig.client.query(options);

if (result.hasException) {
throw Exception('Failed to fetch product: ${result.exception}');
}

final productData = result.data?['product'];
return productData != null ? Product.fromJson(productData) : null;
}

Search Products

Search products by title, description, or other criteria.

Query
const String SEARCH_PRODUCTS = r'''
query SearchProducts($query: String!, $limit: Int, $offset: Int) {
searchProducts(query: $query, limit: $limit, offset: $offset) {
id
title
description
supplier
price {
amount
currencyCode
}
images {
url
order
}
quantity
}
}
''';

Usage
static Future<List<Product>> searchProducts(
String searchQuery, {
int limit = 20,
int offset = 0,
}) async {
final QueryOptions options = QueryOptions(
document: gql(SEARCH_PRODUCTS),
variables: {
'query': searchQuery,
'limit': limit,
'offset': offset,
},
);

final QueryResult result = await GraphQLConfig.client.query(options);

if (result.hasException) {
throw Exception('Failed to search products: ${result.exception}');
}

final List productsData = result.data?['searchProducts'] ?? [];
return productsData.map((data) => Product.fromJson(data)).toList();
}

Product Model

lib/models/product.dart
class Product {
final String id;
final String title;
final String description;
final String supplier;
final String sku;
final int quantity;
final bool optionsEnabled;
final Price price;
final List<ProductImage> images;
final List<ProductOption> options;
final List<ProductVariant> variants;

Product({
required this.id,
required this.title,
required this.description,
required this.supplier,
required this.sku,
required this.quantity,
required this.optionsEnabled,
required this.price,
required this.images,
required this.options,
required this.variants,
});

factory Product.fromJson(Map<String, dynamic> json) {
return Product(
id: json['id'].toString(),
title: json['title'] ?? '',
description: json['description'] ?? '',
supplier: json['supplier'] ?? '',
sku: json['sku'] ?? '',
quantity: json['quantity'] ?? 0,
optionsEnabled: json['optionsEnabled'] ?? false,
price: Price.fromJson(json['price']),
images: (json['images'] as List?)
?.map((img) => ProductImage.fromJson(img))
.toList() ?? [],
options: (json['options'] as List?)
?.map((opt) => ProductOption.fromJson(opt))
.toList() ?? [],
variants: (json['variants'] as List?)
?.map((variant) => ProductVariant.fromJson(variant))
.toList() ?? [],
);
}
}

class Price {
final String amount;
final String currencyCode;

Price({required this.amount, required this.currencyCode});

factory Price.fromJson(Map<String, dynamic> json) {
return Price(
amount: json['amount'].toString(),
currencyCode: json['currencyCode'] ?? 'USD',
);
}

double get numericAmount => double.tryParse(amount) ?? 0.0;
}

class ProductImage {
final String url;
final int order;

ProductImage({required this.url, required this.order});

factory ProductImage.fromJson(Map<String, dynamic> json) {
return ProductImage(
url: json['url'] ?? '',
order: json['order'] ?? 0,
);
}
}

class ProductOption {
final String id;
final String name;
final int order;
final String values;

ProductOption({
required this.id,
required this.name,
required this.order,
required this.values,
});

factory ProductOption.fromJson(Map<String, dynamic> json) {
return ProductOption(
id: json['id'].toString(),
name: json['name'] ?? '',
order: json['order'] ?? 0,
values: json['values'] ?? '',
);
}

List<String> get valuesList => values.split(',').map((v) => v.trim()).toList();
}

class ProductVariant {
final String id;
final String title;
final String sku;
final String price;
final int quantity;
final String? barcode;
final Price originalPrice;

ProductVariant({
required this.id,
required this.title,
required this.sku,
required this.price,
required this.quantity,
this.barcode,
required this.originalPrice,
});

factory ProductVariant.fromJson(Map<String, dynamic> json) {
return ProductVariant(
id: json['id'].toString(),
title: json['title'] ?? '',
sku: json['sku'] ?? '',
price: json['price'].toString(),
quantity: json['quantity'] ?? 0,
barcode: json['barcode'],
originalPrice: Price.fromJson(json['originalPrice']),
);
}
}

Flutter Widget Examples

Product List Widget

widgets/product_list.dart
class ProductList extends StatefulWidget {
final String? categoryId;
final String? searchQuery;

const ProductList({Key? key, this.categoryId, this.searchQuery}) : super(key: key);

@override
_ProductListState createState() => _ProductListState();
}

class _ProductListState extends State<ProductList> {
final ScrollController _scrollController = ScrollController();
final List<Product> _products = [];
bool _isLoading = false;
bool _hasMore = true;
int _offset = 0;
static const int _limit = 20;

@override
void initState() {
super.initState();
_loadProducts();
_scrollController.addListener(_onScroll);
}

void _onScroll() {
if (_scrollController.position.pixels >=
_scrollController.position.maxScrollExtent - 200) {
_loadMoreProducts();
}
}

Future<void> _loadProducts() async {
if (_isLoading) return;

setState(() => _isLoading = true);

try {
List<Product> products;

if (widget.searchQuery != null) {
products = await ProductService.searchProducts(
widget.searchQuery!,
limit: _limit,
offset: 0,
);
} else {
products = await ProductService.getProducts(
limit: _limit,
offset: 0,
categoryId: widget.categoryId,
);
}

setState(() {
_products.clear();
_products.addAll(products);
_offset = _limit;
_hasMore = products.length == _limit;
});
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error loading products: $e')),
);
} finally {
setState(() => _isLoading = false);
}
}

Future<void> _loadMoreProducts() async {
if (_isLoading || !_hasMore) return;

setState(() => _isLoading = true);

try {
List<Product> products;

if (widget.searchQuery != null) {
products = await ProductService.searchProducts(
widget.searchQuery!,
limit: _limit,
offset: _offset,
);
} else {
products = await ProductService.getProducts(
limit: _limit,
offset: _offset,
categoryId: widget.categoryId,
);
}

setState(() {
_products.addAll(products);
_offset += _limit;
_hasMore = products.length == _limit;
});
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error loading more products: $e')),
);
} finally {
setState(() => _isLoading = false);
}
}

@override
Widget build(BuildContext context) {
return RefreshIndicator(
onRefresh: _loadProducts,
child: GridView.builder(
controller: _scrollController,
padding: EdgeInsets.all(8.0),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: 0.75,
crossAxisSpacing: 8.0,
mainAxisSpacing: 8.0,
),
itemCount: _products.length + (_hasMore ? 1 : 0),
itemBuilder: (context, index) {
if (index == _products.length) {
return Center(child: CircularProgressIndicator());
}

return ProductCard(product: _products[index]);
},
),
);
}

@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
}

Product Card Widget

widgets/product_card.dart
class ProductCard extends StatelessWidget {
final Product product;

const ProductCard({Key? key, required this.product}) : super(key: key);

@override
Widget build(BuildContext context) {
return Card(
elevation: 2,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
flex: 3,
child: Container(
width: double.infinity,
child: product.images.isNotEmpty
? Image.network(
product.images.first.url,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
return Icon(Icons.image_not_supported, size: 50);
},
)
: Container(
color: Colors.grey[200],
child: Icon(Icons.shopping_bag, size: 50),
),
),
),
Expanded(
flex: 2,
child: Padding(
padding: EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
product.title,
style: Theme.of(context).textTheme.subtitle2,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
SizedBox(height: 4),
Text(
'${product.price.amount} ${product.price.currencyCode}',
style: Theme.of(context).textTheme.headline6?.copyWith(
color: Theme.of(context).primaryColor,
fontWeight: FontWeight.bold,
),
),
Spacer(),
SizedBox(
width: double.infinity,
child: ElevatedButton.icon(
onPressed: () => _addToCart(context),
icon: Icon(Icons.add_shopping_cart, size: 16),
label: Text('Add'),
style: ElevatedButton.styleFrom(
padding: EdgeInsets.symmetric(vertical: 4),
),
),
),
],
),
),
),
],
),
);
}

void _addToCart(BuildContext context) {
// Implementation in Cart API section
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('${product.title} added to cart!')),
);
}
}


Next: Learn about Cart Management to add shopping cart functionality to your app.