Skip to main content

Channel API

The Channel API provides access to your product catalog, categories, and purchase conditions. It's the main interface for browsing and retrieving ecommerce content.

Overview

The channel module provides methods to:

  • 📦 Products - Get products with filtering and caching
  • 🏷️ Categories - Browse product categories and hierarchies
  • 📋 Purchase Conditions - Access terms, policies, and conditions
  • 🔍 Search & Filter - Advanced product discovery

Products

Get Products

Retrieve products with optional filtering, caching, and market-specific data.

Method

Get Products
Future<List<Product>> get({
String? currency,
String? market,
bool useCache = false,
Map<String, dynamic>? filters,
})

Example

lib/services/product_service.dart
import 'package:your_app/services/sdk_service.dart';

class ProductService {
static Future<List<Product>> getProducts({
String currency = 'USD',
bool useCache = true,
}) async {
try {
final products = await SdkService().sdk.channel.product.get(
currency: currency,
useCache: useCache,
);

print('Loaded ${products.length} products');

for (final product in products.take(3)) {
print('Product: ${product.title}');
print('Price: ${product.price} $currency');
print('Available: ${product.isAvailable}');
}

return products;
} catch (e) {
print('Error fetching products: $e');
return [];
}
}
}

Product Model

Product Model
class Product {
final String id;
final String title;
final String description;
final double price;
final String currency;
final List<String> images;
final List<ProductVariant> variants;
final List<String> categories;
final Map<String, dynamic> attributes;
final bool isAvailable;
final int stockQuantity;
final String? brand;
final double? weight;
final ProductDimensions? dimensions;

// Convert to JSON
Map<String, dynamic> toJson();
}

class ProductVariant {
final String id;
final String title;
final double price;
final String? sku;
final Map<String, String> options; // {'color': 'red', 'size': 'M'}
final bool isAvailable;
final int stockQuantity;
}

class ProductDimensions {
final double length;
final double width;
final double height;
final String unit; // 'cm', 'in'
}

Product List Widget

lib/widgets/product_grid.dart
class ProductGrid extends StatefulWidget {
final String currency;

const ProductGrid({super.key, required this.currency});


_ProductGridState createState() => _ProductGridState();
}

class _ProductGridState extends State<ProductGrid> {
List<Product> _products = [];
bool _isLoading = true;


void initState() {
super.initState();
_loadProducts();
}

Future<void> _loadProducts() async {
try {
final products = await SdkService().sdk.channel.product.get(
currency: widget.currency,
useCache: true,
);

setState(() {
_products = products;
_isLoading = false;
});
} catch (e) {
setState(() {
_isLoading = false;
});
print('Error loading products: $e');
}
}


Widget build(BuildContext context) {
if (_isLoading) {
return const Center(child: CircularProgressIndicator());
}

return GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: 0.8,
crossAxisSpacing: 16,
mainAxisSpacing: 16,
),
itemCount: _products.length,
itemBuilder: (context, index) {
final product = _products[index];
return ProductCard(product: product);
},
);
}
}

class ProductCard extends StatelessWidget {
final Product product;

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


Widget build(BuildContext context) {
return Card(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Product image
Expanded(
child: Container(
width: double.infinity,
decoration: BoxDecoration(
image: product.images.isNotEmpty
? DecorationImage(
image: NetworkImage(product.images.first),
fit: BoxFit.cover,
)
: null,
color: Colors.grey[200],
),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
product.title,
style: const TextStyle(fontWeight: FontWeight.bold),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 4),
Text(
'${product.currency} ${product.price.toStringAsFixed(2)}',
style: TextStyle(
color: Theme.of(context).primaryColor,
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
],
),
),
],
),
);
}
}

Categories

Get Categories

Retrieve all product categories with hierarchical structure.

Method

Get Categories
Future<List<Category>> get()

Example

lib/services/category_service.dart
class CategoryService {
static Future<List<Category>> getCategories() async {
try {
final categories = await SdkService().sdk.channel.category.get();

print('Loaded ${categories.length} categories');

for (final category in categories) {
print('Category: ${category.name}');
print('Products: ${category.productCount}');
if (category.children.isNotEmpty) {
print('Subcategories: ${category.children.length}');
}
}

return categories;
} catch (e) {
print('Error fetching categories: $e');
return [];
}
}

static Future<List<Product>> getProductsByCategory(String categoryId) async {
try {
final products = await SdkService().sdk.channel.product.get(
filters: {'category_id': categoryId},
);

return products;
} catch (e) {
print('Error fetching products for category: $e');
return [];
}
}
}

Category Model

Category Model
class Category {
final String id;
final String name;
final String? description;
final String? image;
final int productCount;
final Category? parent;
final List<Category> children;
final Map<String, dynamic> attributes;
final bool isActive;
final int sortOrder;

// Convert to JSON
Map<String, dynamic> toJson();
}

Category Navigation Widget

lib/widgets/category_navigation.dart
class CategoryNavigation extends StatefulWidget {
final Function(Category) onCategorySelected;

const CategoryNavigation({super.key, required this.onCategorySelected});


_CategoryNavigationState createState() => _CategoryNavigationState();
}

class _CategoryNavigationState extends State<CategoryNavigation> {
List<Category> _categories = [];
bool _isLoading = true;


void initState() {
super.initState();
_loadCategories();
}

Future<void> _loadCategories() async {
try {
final categories = await SdkService().sdk.channel.category.get();

setState(() {
_categories = categories;
_isLoading = false;
});
} catch (e) {
setState(() {
_isLoading = false;
});
}
}


Widget build(BuildContext context) {
if (_isLoading) {
return const CircularProgressIndicator();
}

return ListView.builder(
itemCount: _categories.length,
itemBuilder: (context, index) {
final category = _categories[index];
return CategoryTile(
category: category,
onTap: () => widget.onCategorySelected(category),
);
},
);
}
}

class CategoryTile extends StatelessWidget {
final Category category;
final VoidCallback onTap;

const CategoryTile({
super.key,
required this.category,
required this.onTap,
});


Widget build(BuildContext context) {
return ListTile(
leading: category.image != null
? CircleAvatar(backgroundImage: NetworkImage(category.image!))
: const CircleAvatar(child: Icon(Icons.category)),
title: Text(category.name),
subtitle: Text('${category.productCount} products'),
trailing: category.children.isNotEmpty
? const Icon(Icons.arrow_forward_ios)
: null,
onTap: onTap,
);
}
}

Purchase Conditions

Get Purchase Conditions

Access terms of service, return policies, and other purchase conditions.

Method

Get Purchase Conditions
Future<PurchaseConditions> get()

Example

lib/services/conditions_service.dart
class ConditionsService {
static Future<PurchaseConditions> getPurchaseConditions() async {
try {
final conditions = await SdkService().sdk.channel.purchaseConditions.get();

print('Terms updated: ${conditions.lastUpdated}');
print('Return policy: ${conditions.returnPolicy.daysAllowed} days');

return conditions;
} catch (e) {
print('Error fetching purchase conditions: $e');
throw e;
}
}
}

Purchase Conditions Model

Purchase Conditions Model
class PurchaseConditions {
final String termsOfService;
final String privacyPolicy;
final ReturnPolicy returnPolicy;
final ShippingPolicy shippingPolicy;
final List<PaymentMethod> acceptedPayments;
final DateTime lastUpdated;
final String version;

Map<String, dynamic> toJson();
}

class ReturnPolicy {
final int daysAllowed;
final String conditions;
final List<String> excludedItems;
final bool requiresOriginalPackaging;
}

class ShippingPolicy {
final Map<String, double> shippingRates; // zone -> rate
final int processingDays;
final String freeShippingThreshold;
}

Terms and Conditions Widget

lib/widgets/terms_conditions.dart
class TermsConditionsDialog extends StatefulWidget {
final Function(bool) onAccepted;

const TermsConditionsDialog({super.key, required this.onAccepted});


_TermsConditionsDialogState createState() => _TermsConditionsDialogState();
}

class _TermsConditionsDialogState extends State<TermsConditionsDialog> {
PurchaseConditions? _conditions;
bool _isLoading = true;
bool _accepted = false;


void initState() {
super.initState();
_loadConditions();
}

Future<void> _loadConditions() async {
try {
final conditions = await SdkService().sdk.channel.purchaseConditions.get();

setState(() {
_conditions = conditions;
_isLoading = false;
});
} catch (e) {
setState(() {
_isLoading = false;
});
}
}


Widget build(BuildContext context) {
return AlertDialog(
title: const Text('Terms & Conditions'),
content: _isLoading
? const CircularProgressIndicator()
: SizedBox(
width: double.maxFinite,
height: 400,
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Terms of Service',
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 8),
Text(_conditions?.termsOfService ?? ''),
const SizedBox(height: 16),
Text(
'Return Policy',
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 8),
Text('Returns accepted within ${_conditions?.returnPolicy.daysAllowed ?? 0} days'),
Text(_conditions?.returnPolicy.conditions ?? ''),
],
),
),
),
actions: [
Row(
children: [
Checkbox(
value: _accepted,
onChanged: (value) {
setState(() {
_accepted = value ?? false;
});
},
),
const Expanded(child: Text('I accept the terms and conditions')),
],
),
TextButton(
onPressed: () {
Navigator.of(context).pop();
widget.onAccepted(false);
},
child: const Text('Cancel'),
),
TextButton(
onPressed: _accepted
? () {
Navigator.of(context).pop();
widget.onAccepted(true);
}
: null,
child: const Text('Accept'),
),
],
);
}
}

Search Products

Search for products with filters and sorting.

lib/services/search_service.dart
class SearchService {
static Future<List<Product>> searchProducts({
required String query,
List<String>? categories,
double? minPrice,
double? maxPrice,
String? sortBy,
String currency = 'USD',
}) async {
try {
final filters = <String, dynamic>{
'search': query,
if (categories != null) 'categories': categories,
if (minPrice != null) 'min_price': minPrice,
if (maxPrice != null) 'max_price': maxPrice,
if (sortBy != null) 'sort': sortBy,
};

final products = await SdkService().sdk.channel.product.get(
currency: currency,
filters: filters,
);

return products;
} catch (e) {
print('Error searching products: $e');
return [];
}
}
}

Search Widget

lib/widgets/product_search.dart
class ProductSearch extends StatefulWidget {
final Function(List<Product>) onResults;

const ProductSearch({super.key, required this.onResults});


_ProductSearchState createState() => _ProductSearchState();
}

class _ProductSearchState extends State<ProductSearch> {
final _searchController = TextEditingController();
Timer? _debounceTimer;


void initState() {
super.initState();
_searchController.addListener(_onSearchChanged);
}

void _onSearchChanged() {
_debounceTimer?.cancel();
_debounceTimer = Timer(const Duration(milliseconds: 500), () {
_performSearch(_searchController.text);
});
}

Future<void> _performSearch(String query) async {
if (query.length >= 2) {
final results = await SearchService.searchProducts(query: query);
widget.onResults(results);
}
}


Widget build(BuildContext context) {
return TextField(
controller: _searchController,
decoration: const InputDecoration(
labelText: 'Search Products',
prefixIcon: Icon(Icons.search),
border: OutlineInputBorder(),
),
);
}


void dispose() {
_debounceTimer?.cancel();
_searchController.dispose();
super.dispose();
}
}

Error Handling

Handle channel-related errors:

Channel Error Handling
Future<void> handleChannelErrors() async {
try {
final products = await SdkService().sdk.channel.product.get();
// Process products...
} on ChannelException catch (e) {
switch (e.type) {
case ChannelErrorType.productsNotFound:
print('No products found for the current filters');
break;
case ChannelErrorType.categoryNotFound:
print('Requested category does not exist');
break;
case ChannelErrorType.currencyNotSupported:
print('Currency not supported for this channel');
break;
default:
print('Channel error: ${e.message}');
}
} catch (e) {
print('General error: $e');
}
}

Best Practices

1. Product Caching

Product Caching Strategy
class ProductCache {
static final Map<String, List<Product>> _cache = {};
static final Map<String, DateTime> _cacheTimestamps = {};

static Future<List<Product>> getProducts({
required String currency,
Duration cacheTimeout = const Duration(minutes: 15),
}) async {
final cacheKey = 'products_$currency';
final now = DateTime.now();

// Check if cache is valid
if (_cache.containsKey(cacheKey) &&
_cacheTimestamps.containsKey(cacheKey)) {
final timestamp = _cacheTimestamps[cacheKey]!;
if (now.difference(timestamp) < cacheTimeout) {
return _cache[cacheKey]!;
}
}

// Fetch new data
final products = await SdkService().sdk.channel.product.get(
currency: currency,
useCache: false,
);

// Update cache
_cache[cacheKey] = products;
_cacheTimestamps[cacheKey] = now;

return products;
}
}

2. Lazy Loading

Lazy Loading Products
class ProductList extends StatefulWidget {

_ProductListState createState() => _ProductListState();
}

class _ProductListState extends State<ProductList> {
final List<Product> _products = [];
bool _isLoading = false;
bool _hasMore = true;
int _page = 1;

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

setState(() {
_isLoading = true;
});

try {
final newProducts = await SdkService().sdk.channel.product.get(
filters: {'page': _page, 'limit': 20},
);

setState(() {
_products.addAll(newProducts);
_hasMore = newProducts.length == 20;
_page++;
_isLoading = false;
});
} catch (e) {
setState(() {
_isLoading = false;
});
}
}


Widget build(BuildContext context) {
return ListView.builder(
itemCount: _products.length + (_hasMore ? 1 : 0),
itemBuilder: (context, index) {
if (index == _products.length) {
_loadMore();
return const CircularProgressIndicator();
}
return ProductTile(product: _products[index]);
},
);
}
}


Troubleshooting

Common Issues

  1. "No products found"

    • Check your channel configuration
    • Verify market and currency settings
    • Ensure products are published
  2. "Category not found"

    • Verify category IDs are correct
    • Check if categories are active
    • Ensure proper category hierarchy
  3. "Cache issues"

    • Clear cache and try again
    • Disable caching during development
    • Check cache timeout settings
  4. "Search not working"

    • Verify search filters are correct
    • Check minimum query length requirements
    • Ensure products have searchable content