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);


_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;


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);
}
}


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]);
},
),
);
}


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);


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.