# Flutter Code Examples & Patterns This document contains all Dart code examples and patterns referenced in `CLAUDE.md`. Use these as templates when implementing features in the Worker app. --- ## Table of Contents - [Best Practices](#best-practices) - [UI/UX Components](#uiux-components) - [State Management](#state-management) - [Performance Optimization](#performance-optimization) - [Offline Strategy](#offline-strategy) - [Localization](#localization) - [Deployment](#deployment) --- ## Best Practices ### Hive Box Type Management **✅ CORRECT - Use Box with type filtering** ```dart Box get _box { return Hive.box(HiveBoxNames.favoriteBox); } Future> getAllFavorites(String userId) async { try { final favorites = _box.values .whereType() // Type-safe filtering .where((fav) => fav.userId == userId) .toList(); return favorites; } catch (e) { debugPrint('[DataSource] Error: $e'); rethrow; } } Future> getAllFavorites() async { return _box.values .whereType() // Type-safe! .where((fav) => fav.userId == userId) .toList(); } ``` **❌ INCORRECT - Will cause HiveError** ```dart Box get _box { return Hive.box(HiveBoxNames.favoriteBox); } ``` **Reason**: Hive boxes are opened as `Box` in the central HiveService. Re-opening with a specific type causes `HiveError: The box is already open and of type Box`. ### AppBar Standardization **Standard AppBar Pattern** (reference: `products_page.dart`): ```dart AppBar( leading: IconButton( icon: const Icon(Icons.arrow_back, color: Colors.black), onPressed: () => context.pop(), ), title: const Text('Page Title', style: TextStyle(color: Colors.black)), elevation: AppBarSpecs.elevation, backgroundColor: AppColors.white, foregroundColor: AppColors.grey900, centerTitle: false, actions: [ // Custom actions here const SizedBox(width: AppSpacing.sm), // Always end with spacing ], ) ``` **For SliverAppBar** (in CustomScrollView): ```dart SliverAppBar( pinned: true, backgroundColor: AppColors.white, foregroundColor: AppColors.grey900, elevation: AppBarSpecs.elevation, leading: IconButton( icon: const Icon(Icons.arrow_back, color: Colors.black), onPressed: () => context.pop(), ), title: const Text('Page Title', style: TextStyle(color: Colors.black)), centerTitle: false, actions: [ // Custom actions const SizedBox(width: AppSpacing.sm), ], ) ``` **Standard Pattern (Recent Implementation)**: ```dart AppBar( leading: IconButton( icon: const Icon(Icons.arrow_back, color: Colors.black), onPressed: () => context.pop(), ), title: const Text('Title', style: TextStyle(color: Colors.black)), elevation: AppBarSpecs.elevation, backgroundColor: AppColors.white, foregroundColor: AppColors.grey900, centerTitle: false, actions: [..., const SizedBox(width: AppSpacing.sm)], ) ``` --- ## UI/UX Components ### Color Palette ```dart // colors.dart class AppColors { // Primary static const primaryBlue = Color(0xFF005B9A); static const lightBlue = Color(0xFF38B6FF); static const accentCyan = Color(0xFF35C6F4); // Status static const success = Color(0xFF28a745); static const warning = Color(0xFFffc107); static const danger = Color(0xFFdc3545); static const info = Color(0xFF17a2b8); // Neutrals static const grey50 = Color(0xFFf8f9fa); static const grey100 = Color(0xFFe9ecef); static const grey500 = Color(0xFF6c757d); static const grey900 = Color(0xFF343a40); // Tier Gradients static const diamondGradient = LinearGradient( colors: [Color(0xFF4A00E0), Color(0xFF8E2DE2)], begin: Alignment.topLeft, end: Alignment.bottomRight, ); static const platinumGradient = LinearGradient( colors: [Color(0xFF7F8C8D), Color(0xFFBDC3C7)], begin: Alignment.topLeft, end: Alignment.bottomRight, ); static const goldGradient = LinearGradient( colors: [Color(0xFFf7b733), Color(0xFFfc4a1a)], begin: Alignment.topLeft, end: Alignment.bottomRight, ); } ``` ### Typography ```dart // typography.dart class AppTypography { static const fontFamily = 'Roboto'; static const displayLarge = TextStyle( fontSize: 32, fontWeight: FontWeight.bold, fontFamily: fontFamily, ); static const headlineLarge = TextStyle( fontSize: 24, fontWeight: FontWeight.w600, fontFamily: fontFamily, ); static const titleLarge = TextStyle( fontSize: 20, fontWeight: FontWeight.w500, fontFamily: fontFamily, ); static const bodyLarge = TextStyle( fontSize: 16, fontWeight: FontWeight.normal, fontFamily: fontFamily, ); static const labelSmall = TextStyle( fontSize: 12, fontWeight: FontWeight.normal, fontFamily: fontFamily, ); } ``` ### Member Card Design ```dart class MemberCardSpecs { static const double width = double.infinity; static const double height = 200; static const double borderRadius = 16; static const double elevation = 8; static const EdgeInsets padding = EdgeInsets.all(20); // QR Code static const double qrSize = 80; static const double qrBackgroundSize = 90; // Points Display static const double pointsFontSize = 28; static const FontWeight pointsFontWeight = FontWeight.bold; } ``` ### Status Badges ```dart class StatusBadge extends StatelessWidget { final String status; final Color color; static Color getColorForStatus(OrderStatus status) { switch (status) { case OrderStatus.pending: return AppColors.info; case OrderStatus.processing: return AppColors.warning; case OrderStatus.shipping: return AppColors.lightBlue; case OrderStatus.completed: return AppColors.success; case OrderStatus.cancelled: return AppColors.danger; } } } ``` ### Bottom Navigation ```dart class BottomNavSpecs { static const double height = 72; static const double iconSize = 24; static const double selectedIconSize = 28; static const double labelFontSize = 12; static const Color selectedColor = AppColors.primaryBlue; static const Color unselectedColor = AppColors.grey500; } ``` ### Floating Action Button ```dart class FABSpecs { static const double size = 56; static const double elevation = 6; static const Color backgroundColor = AppColors.accentCyan; static const Color iconColor = Colors.white; static const double iconSize = 24; static const Offset position = Offset(16, 16); // from bottom-right } ``` ### AppBar Specifications ```dart class AppBarSpecs { // From ui_constants.dart static const double elevation = 0.5; // Standard pattern for all pages static AppBar standard({ required String title, required VoidCallback onBack, List? actions, }) { return AppBar( leading: IconButton( icon: const Icon(Icons.arrow_back, color: Colors.black), onPressed: onBack, ), title: Text(title, style: const TextStyle(color: Colors.black)), elevation: elevation, backgroundColor: AppColors.white, foregroundColor: AppColors.grey900, centerTitle: false, actions: [ ...?actions, const SizedBox(width: AppSpacing.sm), ], ); } } ``` --- ## State Management ### Authentication Providers ```dart final authProvider = AsyncNotifierProvider final otpTimerProvider = StateNotifierProvider ``` ### Home Providers ```dart final memberCardProvider = Provider((ref) { final user = ref.watch(authProvider).user; return MemberCard( tier: user.memberTier, name: user.name, memberId: user.id, points: user.points, qrCode: generateQRCode(user.id), ); }); ``` ### Loyalty Providers ```dart final loyaltyPointsProvider = AsyncNotifierProvider ``` **Rewards Page Providers**: ```dart // Providers in lib/features/loyalty/presentation/providers/ @riverpod class LoyaltyPoints extends _$LoyaltyPoints { // Manages 9,750 available points, 1,200 expiring } @riverpod class Gifts extends _$Gifts { // 6 mock gifts matching HTML design } @riverpod List filteredGifts(ref) { // Filters by selected category } final selectedGiftCategoryProvider = StateNotifierProvider... final hasEnoughPointsProvider = Provider.family... ``` ### Referral Provider ```dart final referralProvider = AsyncNotifierProvider ``` ### Products Providers ```dart final productsProvider = AsyncNotifierProvider> final productSearchProvider = StateProvider final selectedCategoryProvider = StateProvider ``` ### Cart Providers ```dart final cartProvider = NotifierProvider> final cartTotalProvider = Provider ``` **Dynamic Cart Badge**: ```dart // Added provider in cart_provider.dart @riverpod int cartItemCount(CartItemCountRef ref) { final cartState = ref.watch(cartProvider); return cartState.items.fold(0, (sum, item) => sum + item.quantity); } // Used in home_page.dart and products_page.dart final cartItemCount = ref.watch(cartItemCountProvider); QuickAction( badge: cartItemCount > 0 ? '$cartItemCount' : null, ) ``` ### Orders Providers ```dart final ordersProvider = AsyncNotifierProvider> final orderFilterProvider = StateProvider final paymentsProvider = AsyncNotifierProvider> ``` ### Projects Providers ```dart final projectsProvider = AsyncNotifierProvider> final projectFormProvider = StateNotifierProvider ``` ### Chat Providers ```dart final chatProvider = AsyncNotifierProvider final messagesProvider = StreamProvider> final typingIndicatorProvider = StateProvider ``` ### Authentication State Implementation ```dart @riverpod class Auth extends _$Auth { @override Future build() async { final token = await _getStoredToken(); if (token != null) { final user = await _getUserFromToken(token); return AuthState.authenticated(user); } return const AuthState.unauthenticated(); } Future loginWithPhone(String phone) async { state = const AsyncValue.loading(); state = await AsyncValue.guard(() async { await ref.read(authRepositoryProvider).requestOTP(phone); return AuthState.otpSent(phone); }); } Future verifyOTP(String phone, String otp) async { state = const AsyncValue.loading(); state = await AsyncValue.guard(() async { final response = await ref.read(authRepositoryProvider).verifyOTP(phone, otp); await _storeToken(response.token); return AuthState.authenticated(response.user); }); } } ``` --- ## Performance Optimization ### Image Caching ```dart // Use cached_network_image for all remote images CachedNetworkImage( imageUrl: product.images.first, placeholder: (context, url) => const ShimmerPlaceholder(), errorWidget: (context, url, error) => const Icon(Icons.error), fit: BoxFit.cover, memCacheWidth: 400, // Optimize memory usage fadeInDuration: const Duration(milliseconds: 300), ) ``` ### List Performance ```dart // Use ListView.builder with RepaintBoundary for long lists ListView.builder( itemCount: items.length, itemBuilder: (context, index) { return RepaintBoundary( child: ProductCard(product: items[index]), ); }, cacheExtent: 1000, // Pre-render items ) // Use AutomaticKeepAliveClientMixin for expensive widgets class ProductCard extends StatefulWidget { @override State createState() => _ProductCardState(); } class _ProductCardState extends State with AutomaticKeepAliveClientMixin { @override bool get wantKeepAlive => true; @override Widget build(BuildContext context) { super.build(context); return Card(...); } } ``` ### State Optimization ```dart // Use .select() to avoid unnecessary rebuilds final userName = ref.watch(authProvider.select((state) => state.user?.name)); // Use family modifiers for parameterized providers @riverpod Future product(ProductRef ref, String id) async { return await ref.read(productRepositoryProvider).getProduct(id); } // Keep providers outside build method final productsProvider = ...; class ProductsPage extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final products = ref.watch(productsProvider); return ...; } } ``` --- ## Offline Strategy ### Data Sync Flow ```dart @riverpod class DataSync extends _$DataSync { @override Future build() async { // Listen to connectivity changes ref.listen(connectivityProvider, (previous, next) { if (next == ConnectivityStatus.connected) { syncData(); } }); return SyncStatus.idle; } Future syncData() async { state = const AsyncValue.loading(); state = await AsyncValue.guard(() async { // Sync in order of dependency await _syncUserData(); await _syncProducts(); await _syncOrders(); await _syncProjects(); await _syncLoyaltyData(); await ref.read(settingsRepositoryProvider).updateLastSyncTime(); return SyncStatus.success; }); } Future _syncUserData() async { final user = await ref.read(authRepositoryProvider).getCurrentUser(); await ref.read(authLocalDataSourceProvider).saveUser(user); } Future _syncProducts() async { final products = await ref.read(productRepositoryProvider).getAllProducts(); await ref.read(productLocalDataSourceProvider).saveProducts(products); } // ... other sync methods } ``` ### Offline Queue ```dart // Queue failed requests for retry when online class OfflineQueue { final HiveInterface hive; late Box _queueBox; Future init() async { _queueBox = await hive.openBox('offline_queue'); } Future addToQueue(ApiRequest request) async { await _queueBox.add({ 'endpoint': request.endpoint, 'method': request.method, 'body': request.body, 'timestamp': DateTime.now().toIso8601String(), }); } Future processQueue() async { final requests = _queueBox.values.toList(); for (var i = 0; i < requests.length; i++) { try { await _executeRequest(requests[i]); await _queueBox.deleteAt(i); } catch (e) { // Keep in queue for next retry } } } } ``` --- ## Localization ### Setup ```dart // l10n.yaml arb-dir: lib/l10n template-arb-file: app_en.arb output-localization-file: app_localizations.dart // lib/l10n/app_vi.arb (Vietnamese) { "@@locale": "vi", "appTitle": "Worker App", "login": "Đăng nhập", "phone": "Số điện thoại", "enterPhone": "Nhập số điện thoại", "continue": "Tiếp tục", "verifyOTP": "Xác thực OTP", "enterOTP": "Nhập mã OTP 6 số", "resendOTP": "Gửi lại mã", "home": "Trang chủ", "products": "Sản phẩm", "loyalty": "Hội viên", "account": "Tài khoản", "points": "Điểm", "cart": "Giỏ hàng", "checkout": "Thanh toán", "orders": "Đơn hàng", "projects": "Công trình", "quotes": "Báo giá", "myGifts": "Quà của tôi", "referral": "Giới thiệu bạn bè", "pointsHistory": "Lịch sử điểm" } // lib/l10n/app_en.arb (English) { "@@locale": "en", "appTitle": "Worker App", "login": "Login", "phone": "Phone Number", "enterPhone": "Enter phone number", "continue": "Continue", ... } ``` ### Usage ```dart class LoginPage extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final l10n = AppLocalizations.of(context)!; return Scaffold( appBar: AppBar( title: Text(l10n.login), ), body: Column( children: [ TextField( decoration: InputDecoration( labelText: l10n.phone, hintText: l10n.enterPhone, ), ), ElevatedButton( onPressed: () {}, child: Text(l10n.continue), ), ], ), ); } } ``` --- ## Deployment ### Android ```gradle // android/app/build.gradle android { compileSdkVersion 34 defaultConfig { applicationId "com.eurotile.worker" minSdkVersion 21 targetSdkVersion 34 versionCode 1 versionName "1.0.0" } signingConfigs { release { storeFile file(RELEASE_STORE_FILE) storePassword RELEASE_STORE_PASSWORD keyAlias RELEASE_KEY_ALIAS keyPassword RELEASE_KEY_PASSWORD } } buildTypes { release { signingConfig signingConfigs.release minifyEnabled true shrinkResources true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } ``` ### iOS ```ruby # ios/Podfile platform :ios, '13.0' post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) target.build_configurations.each do |config| config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0' end end end ``` --- ## Quick Reference ### Key Requirements for All Code - ✅ Black back arrow with explicit color - ✅ Black text title with TextStyle - ✅ Left-aligned title (`centerTitle: false`) - ✅ White background (`AppColors.white`) - ✅ Use `AppBarSpecs.elevation` (not hardcoded values) - ✅ Always add `SizedBox(width: AppSpacing.sm)` after actions - ✅ For SliverAppBar, add `pinned: true` property - ✅ Use `Box` for Hive boxes with `.whereType()` filtering - ✅ Clean architecture (data/domain/presentation) - ✅ Riverpod state management - ✅ Hive for local persistence - ✅ Material 3 design system - ✅ Vietnamese localization - ✅ CachedNetworkImage for all remote images - ✅ Proper error handling - ✅ Loading states (CustomLoadingIndicator) - ✅ Empty states with helpful messages