Bloomreach Discovery SDK for Flutter/Dart
A comprehensive Flutter/Dart SDK for Bloomreach Discovery APIs, providing seamless integration for product search, category browsing, autosuggest, visual search, recommendations, and pixel tracking capabilities in your e-commerce applications.
Features
🔍 Product Search - Powerful keyword-based product discovery
📂 Category Search - Browse products by categories
🤖 Autosuggest - Real-time search suggestions and autocomplete
👁️ Visual Search - AI-powered image-based product search
🎯 Recommendations & Pathways - Complete widget-based recommendation system
📊 Advanced Filtering - Faceted search with ranges and grouping
🎪 Personalization - User-specific recommendations and targeting
🏪 BOPIS Support - Buy Online, Pick-up In Store functionality
📈 Pixel Tracking - Comprehensive user behavior tracking and analytics
Installation
Add this to your package's pubspec.yaml file:
dependencies:
brdiscovery: ^0.5.0
Then run:
flutter pub get
Or install it from the command line:
flutter pub add brdiscovery
Quick Start
1. Import the Package
import 'package:brdiscovery/api.dart';
2. Initialize API Clients
// Product Search
final productSearchApi = ProductSearchApi();
// Autosuggest
final autoSuggestApi = AutoSuggestApi();
// Visual Search
final visualSearchApi = VisualSearchApi();
final visualUploadApi = VisualUploadApi();
// Recommendations & Pathways
final recommendationsApi = RecommendationsApi();
3. Initialize Pixel Tracking (New!)
// Initialize pixel tracker for analytics
final brPixel = BrPixel(
accountId: 'your_account_id',
uuid: 'your_device_uuid', // Android Advertising ID or similar
visitorType: VisitorType.newUser, // or VisitorType.returningUser
baseUrl: 'https://yourapp.com/',
domainKey: 'your_domain_key',
userId: 'user123', // Optional
debugMode: true, // Set to false in production
currency: 'USD',
pixelUrlByRegion: PixelRegion.na.url,
);
PixelTracker.init(brPixel);
4. Basic Product Search with Tracking
try {
// Perform product search
final response = await productSearchApi.searchProducts(
'your_br_uid_2', // Browser tracking ID
12345, // Account ID
'your_domain_key', // Domain key
'search query', // Search term
rows: 20, // Results per page
start: 0, // Starting offset
);
// Track the search event
PixelTracker.searchEventPixel(
'https://yourapp.com/home',
'Search Results',
'search query',
);
if (response?.response?.docs != null) {
for (final product in response!.response!.docs) {
print('Product: ${product.title} - \${product.price}');
}
}
} catch (e) {
print('Error: $e');
}
5. Basic Recommendations with Tracking
try {
// Get keyword-based recommendations
final recommendations = await recommendationsApi.getKeywordRecommendations(
'widget_id', // Widget ID from dashboard
12345, // Account ID
'your_br_uid_2', // Browser tracking ID
'your_domain_key', // Domain key
'laptops', // Query for recommendations
'request_id_123', // Request ID for tracking
'https://yoursite.com', // Current page URL
'https://yoursite.com', // Referrer URL
facet: true, // Enable facets
rows: 10, // Number of recommendations
);
// Track widget view
PixelTracker.widgetView(
'request_id_123',
'laptops',
'widget_id',
'keyword'
);
if (recommendations?.response?.docs != null) {
for (final product in recommendations!.response!.docs) {
print('Recommended: ${product.title} - \${product.price}');
}
}
} catch (e) {
print('Error: $e');
}
Pixel Tracking Usage
Page View Tracking
// Track different page types
PixelTracker.homePageViewPixel(
'https://yourapp.com/previous',
'Home Screen'
);
PixelTracker.productPageViewPixel(
'https://yourapp.com/category',
'Product Details',
'product123',
'Amazing Product',
sku: 'SKU123',
);
PixelTracker.categoryPageViewPixel(
'https://yourapp.com/home',
'Electronics Category',
'electronics',
'Home|Electronics'
);
PixelTracker.searchResultPageViewPixel(
'https://yourapp.com/home',
'Search Results',
'laptop'
);
Event Tracking
// Track user interactions
PixelTracker.addToCartEventPixel(
'https://yourapp.com/product/123',
'Product Details',
'product123',
'Amazing Product',
'SKU123',
);
PixelTracker.searchEventPixel(
'https://yourapp.com/search',
'Search Page',
'laptop computers',
);
PixelTracker.suggestionEventPixel(
'https://yourapp.com/search',
'Search Page',
'lap', // What user typed
'laptop', // What they clicked
);
Widget Tracking
// Track recommendation widget interactions
PixelTracker.widgetView(
'response_id_123',
'search_query',
'widget123',
'recommendation'
);
PixelTracker.widgetClick(
'response_id_123',
'search_query',
'widget123',
'recommendation',
'product456'
);
PixelTracker.widgetAddToCart(
'response_id_123',
'widget123',
'search_query',
'recommendation',
'product456',
widgetAtcDataSku: 'SKU456',
);
Conversion Tracking
// Track successful purchases
PixelTracker.conversionPageView(
'https://yourapp.com/cart',
'Order Complete',
true,
'299.99',
'order456',
[
PixelBasketItem(
prodId: 'product123',
name: 'Amazing Product',
sku: 'SKU123',
quantity: '1',
price: '299.99',
),
]
);
Usage Examples
Recommendations & Pathways
// Keyword-based recommendations with faceting
final keywordRecs = await recommendationsApi.getKeywordRecommendations(
'widget_id_123',
accountId,
brUid2,
domainKey,
'wireless headphones', // Search query
'request_id_456',
'https://yoursite.com',
'https://yoursite.com',
facet: true, // Enable facets for filtering
rows: 20,
filter: 'category:"Electronics"',
userId: 'user123',
);
// Item-based "More like this" recommendations
final itemRecs = await recommendationsApi.getItemBasedRecommendations(
'pdp_widget_id',
accountId,
brUid2,
domainKey,
'product_id_789', // Product ID for similar items
'request_id_789',
'https://yoursite.com',
'https://yoursite.com',
rows: 8,
contextPids: 'product_id_789', // Context for merchandising
userId: 'user123',
);
// Category browsing with recommendations
final categoryRecs = await recommendationsApi.getCategoryRecommendations(
'category_widget_id',
accountId,
brUid2,
domainKey,
'electronics_cat_id', // Category ID
'request_id_101',
'https://yoursite.com',
'https://yoursite.com',
facet: true,
rows: 24,
userId: 'user123',
);
// Personalized recommendations for logged-in users
final personalRecs = await recommendationsApi.getPersonalizedRecommendations(
'personal_widget_id',
accountId,
brUid2,
domainKey,
'recommended for you', // Query for personalization
'user123', // Required user ID
'request_id_202',
'https://yoursite.com',
'https://yoursite.com',
rows: 15,
);
// Global trending/popular products
final globalRecs = await recommendationsApi.getGlobalRecommendations(
'trending_widget_id',
accountId,
brUid2,
domainKey,
'request_id_303',
'https://yoursite.com',
'https://yoursite.com',
rows: 12,
filter: 'is_live:true',
);
// Recently viewed products
final recentRecs = await recommendationsApi.getRecentlyViewedRecommendations(
'recent_widget_id',
accountId,
brUid2,
domainKey,
'request_id_404',
'https://yoursite.com',
'https://yoursite.com',
userId: 'user123',
rows: 10,
);
Product Search with Advanced Features
final response = await productSearchApi.getProductSearchResults(
brUid2,
accountId,
domainKey,
'laptop', // Search query
SearchType.KEYWORD,
0, // Start index
20, // Number of results
'https://yoursite.com', // Current page URL
'https://yoursite.com', // Referrer URL
sort: 'price asc', // Sort by price ascending
fq: 'brand:"Apple"', // Filter by brand
facetVersion: 3.0, // Use modern facet format
ll: '37.7749,-122.4194', // Location for BOPIS
userId: 'user123', // User identifier
);
// Access products
final products = response?.response?.docs ?? [];
// Access facets for filtering UI
final facets = response?.facetCounts?.facets ?? [];
// Check for grouped results
if (response?.groupResponse != null) {
final groups = response!.groupResponse!.groups;
// Handle grouped product results
}
Category Browsing
final categoryResponse = await productSearchApi.searchCategory(
brUid2,
accountId,
domainKey,
'electronics', // Category ID
rows: 20,
start: 0,
sort: 'popularity desc',
fq: 'price:[50 TO 500]', // Price range filter
);
Autosuggest Implementation
final suggestions = await autoSuggestApi.getAutoSuggest(
accountId,
brUid2,
'product:default', // Catalog views
'lap', // Partial query
'https://yoursite.com', // Current URL
'https://yoursite.com', // Referrer URL
requestType: 'suggest',
);
// Process suggestions
if (suggestions?.suggestionGroups != null) {
for (final group in suggestions!.suggestionGroups) {
// Query suggestions (search terms)
for (final querySuggestion in group.querySuggestions) {
print('Suggested query: ${querySuggestion.displayText}');
// Track suggestion click when user selects
PixelTracker.suggestionEventPixel(
'https://yoursite.com/search',
'Search Page',
'lap', // what user typed
querySuggestion.query ?? '', // what they clicked
);
}
// Product suggestions
for (final productSuggestion in group.searchSuggestions) {
print('Suggested product: ${productSuggestion.title}');
}
// Attribute suggestions (brands, categories, etc.)
for (final attrSuggestion in group.attributeSuggestions) {
print('Suggested ${attrSuggestion.attributeType}: ${attrSuggestion.value}');
}
}
}
Visual Search
// 1. Upload an image
final imageFile = await MultipartFile.fromPath(
'image',
'/path/to/image.jpg',
);
final uploadResponse = await visualUploadApi.uploadAPI(
'widget_id',
accountId,
domainKey,
image: imageFile,
);
// 2. Search using the uploaded image
if (uploadResponse?.response?.imageId != null) {
final visualResults = await visualSearchApi.getProductSearchResults(
'widget_id',
accountId,
brUid2,
domainKey,
uploadResponse!.response!.imageId!,
'https://yoursite.com',
rows: 10,
filter: 'price:[0 TO 1000]', // Optional filters
);
// Process visual search results
final products = visualResults?.response?.docs ?? [];
}
Working with Dynamic Product Fields
The SDK uses a flexible SearchResponseDoc model that adapts to your product catalog:
final doc = response.response!.docs.first;
// Access common fields with convenience getters
print('ID: ${doc.pid}');
print('Title: ${doc.title}');
print('Price: ${doc.price}');
print('Sale Price: ${doc.salePrice}');
print('Brand: ${doc.brand}');
// Access any custom field dynamically
final customValue = doc.getField<String>('custom_attribute');
final numericValue = doc.getField<double>('rating');
// Get field with default value
final description = doc.getFieldWithDefault<String>('description', 'No description');
// Check if field exists
if (doc.hasField('inventory_count')) {
final inventory = doc.getField<int>('inventory_count');
}
// Get all available fields
final availableFields = doc.availableFields;
print('Available fields: $availableFields');
Error Handling
try {
final response = await productSearchApi.searchProducts(
brUid2,
accountId,
domainKey,
'search term',
);
// Handle successful response
if (response?.response?.numFound == 0) {
print('No products found');
}
} on ApiException catch (e) {
print('API Error: ${e.code} - ${e.message}');
// Handle specific API errors
} catch (e) {
print('General error: $e');
// Handle network or other errors
}
Configuration
Environment Setup
// Production environment (default)
final productSearchApi = ProductSearchApi(PscApiClient(env: Environment.PRODUCTION));
final recommendationsApi = RecommendationsApi(RecommendationsApiClient(env: Environment.PRODUCTION));
// Staging environment
final productSearchApi = ProductSearchApi(PscApiClient(env: Environment.STAGING));
final recommendationsApi = RecommendationsApi(RecommendationsApiClient(env: Environment.STAGING));
Custom Headers
final pscApiClient = PscApiClient();
pscApiClient.addDefaultHeader('Custom-Header', 'Value');
final productSearchApi = ProductSearchApi(pscApiClient);
final recsApiClient = RecommendationsApiClient();
recsApiClient.addDefaultHeader('Custom-Header', 'Value');
final recommendationsApi = RecommendationsApi(recsApiClient);
Pixel Tracking Configuration
// Configure pixel tracker with all options
final brPixel = BrPixel(
accountId: 'your_account_id',
uuid: 'your_device_uuid',
visitorType: VisitorType.returningUser,
baseUrl: 'https://yourapp.com/',
domainKey: 'your_domain_key',
userId: 'user123',
testData: false, // Set to true for testing
debugMode: false, // Set to true for development
currency: 'USD',
pixelUrlByRegion: PixelRegion.na.url, // or PixelRegion.eu.url
customerTier: 'premium',
customerCountry: 'US',
customerGeo: 'NA',
customerProfile: 'mobile_user',
viewId: 'mobile_view',
abTest: 'variant_a',
);
API Reference
Product Search API
| Method | Description |
|---|---|
getProductSearchResults() |
Full product search with all parameters |
searchProducts() |
Convenient method for keyword search |
searchCategory() |
Convenient method for category browsing |
Recommendations API
| Method | Description |
|---|---|
getKeywordRecommendations() |
Search-driven recommendations with faceting |
getItemBasedRecommendations() |
"More like this" and similar product recommendations |
getCategoryRecommendations() |
Category-based browsing with filtering |
getPersonalizedRecommendations() |
User-specific personalized recommendations |
getGlobalRecommendations() |
Site-wide trending and popular products |
getRecentlyViewedRecommendations() |
Recently viewed products for users |
Autosuggest API
| Method | Description |
|---|---|
getAutoSuggest() |
Get search suggestions and product recommendations |
Visual Search API
| Method | Description |
|---|---|
uploadAPI() |
Upload image for visual search |
getProductSearchResults() |
Search products using uploaded image |
Pixel Tracker API
| Method | Description |
|---|---|
init() |
Initialize pixel tracker with configuration |
homePageViewPixel() |
Track home page views |
productPageViewPixel() |
Track product page views |
categoryPageViewPixel() |
Track category page views |
searchResultPageViewPixel() |
Track search result page views |
conversionPageView() |
Track conversion/thank you pages |
addToCartEventPixel() |
Track add to cart events |
searchEventPixel() |
Track search events |
suggestionEventPixel() |
Track autosuggest clicks |
quickViewEventPixel() |
Track quick view events |
widgetView() |
Track widget view events |
widgetClick() |
Track widget click events |
widgetAddToCart() |
Track widget add to cart events |
customPageViewPixel() |
Track custom page views |
customEventPixel() |
Track custom events |
Key Parameters
- brUid2: Browser tracking cookie for analytics
- accountId: Your Bloomreach account identifier
- domainKey: Site domain identifier
- widgetId: Widget ID from Dashboard Widget Configurator (for recommendations)
- fl: Field list - comma-separated fields to return
- fq: Filter query for faceted search
- sort: Sort order (e.g., "price asc", "popularity desc")
- facetVersion: Use 3.0 for modern unified facet format
- groupby: Group results by field (e.g., "brand", "category")
- userId: User identifier for personalization
- contextPids: Context product IDs for enhanced recommendations
Advanced Features
Widget-Based Recommendations
The SDK supports all major recommendation widget types for building complete recommendation systems:
// Homepage trending products
final trending = await recommendationsApi.getGlobalRecommendations(
'homepage_widget', accountId, brUid2, domainKey, requestId, url, url,
rows: 12,
);
// Product detail page "More like this"
final similar = await recommendationsApi.getItemBasedRecommendations(
'pdp_widget', accountId, brUid2, domainKey, currentProductId, requestId, url, url,
rows: 8, contextPids: currentProductId,
);
// Search results with recommendations
final searchRecs = await recommendationsApi.getKeywordRecommendations(
'search_widget', accountId, brUid2, domainKey, searchQuery, requestId, url, url,
facet: true, rows: 20, userId: userId,
);
// Personalized recommendations for logged-in users
final personal = await recommendationsApi.getPersonalizedRecommendations(
'personal_widget', accountId, brUid2, domainKey, 'for you', userId, requestId, url, url,
rows: 15,
);
Faceted Search
// Using unified facet format (v3.0)
final response = await productSearchApi.searchProducts(
brUid2, accountId, domainKey, 'query',
facetVersion: 3.0,
);
// Process facets for filter UI
if (response?.facetCounts?.facets != null) {
for (final facet in response!.facetCounts!.facets!) {
print('Facet: ${facet.name} (${facet.type})');
for (final value in facet.value) {
if (value.name != null) {
// Text facet (brand, category, etc.)
print(' ${value.name}: ${value.count} products');
} else if (value.start != null && value.end != null) {
// Range facet (price, rating, etc.)
print(' ${value.start}-${value.end}: ${value.count} products');
}
}
}
}
Product Grouping
final response = await productSearchApi.getProductSearchResults(
brUid2, accountId, domainKey, 'query',
SearchType.KEYWORD, 0, 20, url, refUrl,
groupby: 'brand', // Group by brand
groupLimit: 5, // Max 5 products per group
);
// Access grouped results
if (response?.groupResponse != null) {
response!.groupResponse!.groups.forEach((groupKey, groupList) {
print('Brand: $groupKey (${groupList.matches} total matches)');
for (final group in groupList.groups) {
print(' Group value: ${group.groupValue}');
print(' Products in group: ${group.doclist?.docs.length}');
}
});
}
BOPIS (Buy Online, Pick-up In Store)
final response = await productSearchApi.searchProducts(
brUid2, accountId, domainKey, 'query',
ll: '37.7749,-122.4194', // Latitude,longitude
fq: 'store_inventory:true',
);
Complete User Journey with Tracking
// Example: Complete e-commerce user journey with pixel tracking
class EcommerceJourney {
final ProductSearchApi productSearchApi;
final RecommendationsApi recommendationsApi;
EcommerceJourney(this.productSearchApi, this.recommendationsApi);
Future<void> simulateUserJourney() async {
// 1. User lands on home page
PixelTracker.homePageViewPixel(
'https://app.com/external',
'Home Page'
);
// 2. User searches for products
final searchResponse = await productSearchApi.searchProducts(
brUid2, accountId, domainKey, 'laptop',
);
PixelTracker.searchEventPixel(
'https://app.com/home',
'Search Results',
'laptop'
);
// 3. User views product
final product = searchResponse?.response?.docs?.first;
if (product != null) {
PixelTracker.productPageViewPixel(
'https://app.com/search',
'Product Details',
product.pid ?? '',
product.title ?? '',
sku: product.getField<String>('sku'),
);
// 4. User adds to cart
PixelTracker.addToCartEventPixel(
'https://app.com/product/${product.pid}',
'Product Details',
product.pid ?? '',
product.title ?? '',
product.getField<String>('sku') ?? '',
);
// 5. User completes purchase
PixelTracker.conversionPageView(
'https://app.com/cart',
'Order Complete',
true,
'999.99',
'order_123',
[
PixelBasketItem(
prodId: product.pid ?? '',
name: product.title ?? '',
sku: product.getField<String>('sku'),
quantity: '1',
price: '999.99',
),
],
);
}
}
}
Testing
The package includes comprehensive tests. Run them with:
flutter test
Example test files:
test/product_search_api_test.dart- Basic product search teststest/autosuggest_api_test.dart- Autosuggest functionality teststest/visual_search_api_test.dart- Visual search teststest/pixel_tracker_test.dart- Pixel tracking teststest/recommendations_test.dart- Recommendations API tests
Requirements
- Dart:
>=2.12.0 <4.0.0 - Flutter:
>=2.0.0
Dependencies
collection: ^1.17.0http: ^1.1.0intl: ^0.20.2meta: ^1.1.8
Contributing
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Changelog
0.5.0
- NEW: Pixel Tracking - Complete user behavior tracking and analytics
- Page view tracking (home, product, category, search, content, conversion)
- Event tracking (add to cart, search, suggestions, quick view)
- Widget interaction tracking (view, click, add to cart)
- Custom pixel support with arbitrary parameters
- Automatic retry logic and error handling
- Debug mode with pixel validation
- Integration with existing Discovery APIs
- NEW: Recommendations & Pathways API - Complete widget-based recommendation system
- Keyword-based recommendations with faceting
- Item-based "More like this" recommendations
- Category browsing with advanced filtering
- Personalized user recommendations
- Global trending/popular products
- Recently viewed products tracking
- Enhanced faceting support across all recommendation widgets
- Context-based merchandising with
context_pidsparameter - Filter facets for maintaining UI state during filtering
0.4.0
- Added Autosuggest API support
- Enhanced product search with grouping
- Improved facet handling with unified v3.0 format
0.3.0
- Added Visual Search functionality
- Improved error handling
0.2.0
- Updated dependencies
- Enhanced API client stability
0.1.0
- Initial release with Product Search and Category API
Support
- Documentation: Bloomreach Discovery Documentation
- Issues: GitHub Issues
- Email: kenan.salic@bloomreach.com
License
This project is licensed under the Apache 2.0 License - see the LICENSE file for details.
Note: This SDK requires a valid Bloomreach Discovery API Account. Contact Bloomreach for account setup and API access.