From 7dc66d80fc71cd7d3d0d1395fe110dd044c21356 Mon Sep 17 00:00:00 2001 From: renolation Date: Thu, 16 Oct 2025 17:22:27 +0700 Subject: [PATCH] fix --- claude.md | 90 +++++++------ lib/core/providers/dio_client_provider.dart | 10 -- lib/core/providers/dio_client_provider.g.dart | 55 -------- lib/core/providers/providers.dart | 1 - lib/core/widgets/empty_state.dart | 2 +- .../category_local_datasource.dart | 6 + .../category_repository_impl.dart | 51 +++++++- .../providers/categories_provider.dart | 19 +-- .../providers/categories_provider.g.dart | 59 --------- .../widgets/product_selector.dart | 3 +- .../products/data/models/product_model.dart | 6 +- .../repositories/product_repository_impl.dart | 121 +++++++++++++++--- .../pages/product_detail_page.dart | 4 +- .../providers/filtered_products_provider.dart | 2 +- .../filtered_products_provider.g.dart | 2 +- .../providers/products_provider.dart | 4 +- .../widgets/product_list_item.dart | 4 +- 17 files changed, 222 insertions(+), 217 deletions(-) delete mode 100644 lib/core/providers/dio_client_provider.dart delete mode 100644 lib/core/providers/dio_client_provider.g.dart diff --git a/claude.md b/claude.md index 1fcd064..e2ff4a9 100644 --- a/claude.md +++ b/claude.md @@ -64,8 +64,8 @@ You have access to these expert subagents - USE THEM PROACTIVELY: ## Flutter Best Practices - Use Flutter 3.x features and Material 3 design - Implement clean architecture with Riverpod for state management -- Use Hive CE for local database and offline-first functionality -- Follow proper dependency injection with GetIt +- Use Hive CE for local database with **online-first** strategy (API first, cache fallback) +- Follow proper dependency injection with Riverpod providers - Implement proper error handling and user feedback - Follow platform-specific design guidelines - Use proper localization for multi-language support @@ -453,7 +453,7 @@ A comprehensive Flutter-based Point of Sale (POS) application designed for retai - Supplier state **Data Requirements**: -- Product list from Hive (offline-first) +- Product list from API (online-first with Hive cache fallback) - Product images (cached with variants) - Product search indexing - Category relationships @@ -969,45 +969,63 @@ GridView.builder( - Debounce search queries - Optimize cart calculations -## Offline-First Strategy +## Online-First Strategy ### Data Flow -1. **Read**: Always read from Hive first (instant UI) -2. **Sync**: Background sync with API when online -3. **Update**: Update Hive and UI when sync completes -4. **Conflict**: Handle conflicts with last-write-wins strategy +1. **Check Connection**: Check if device is online +2. **Try API First**: If online, fetch fresh data from API +3. **Update Cache**: Save API response to Hive for offline access +4. **Fallback to Cache**: If API fails or offline, load from Hive +5. **Show Data**: Display data to user (from API or cache) -### Sync Logic +### Implementation Pattern ```dart @riverpod -class DataSync extends _$DataSync { +class Products extends _$Products { @override - Future build() async { - return await _performSync(); + Future> build() async { + // Online-first: Try to load from API first + final repository = ref.watch(productRepositoryProvider); + final networkInfo = ref.watch(networkInfoProvider); + + // Check if online + final isConnected = await networkInfo.isConnected; + + if (isConnected) { + // Try API first + try { + final syncResult = await repository.syncProducts(); + return syncResult.fold( + (failure) { + // API failed, fallback to cache + print('API failed, falling back to cache: ${failure.message}'); + return _loadFromCache(); + }, + (products) => products, + ); + } catch (e) { + // API error, fallback to cache + print('API error, falling back to cache: $e'); + return _loadFromCache(); + } + } else { + // Offline, load from cache + print('Offline, loading from cache'); + return _loadFromCache(); + } } - - Future _performSync() async { - if (!await ref.read(networkInfoProvider).isConnected) { - return SyncStatus.offline; - } - - try { - // Sync categories first - await ref.read(categoriesProvider.notifier).syncCategories(); - - // Then sync products and variants - await ref.read(productsProvider.notifier).syncProducts(); - - // Sync suppliers - await ref.read(suppliersProvider.notifier).syncSuppliers(); - - // Update last sync time - await ref.read(settingsProvider.notifier).updateLastSync(); - - return SyncStatus.success; - } catch (e) { - return SyncStatus.failed; - } + + Future> _loadFromCache() async { + final repository = ref.read(productRepositoryProvider); + final result = await repository.getAllProducts(); + + return result.fold( + (failure) { + print('Cache load failed: ${failure.message}'); + return []; + }, + (products) => products, + ); } } ``` @@ -1134,7 +1152,7 @@ class DataSync extends _$DataSync { ### Code Review Checklist - [ ] Follows clean architecture principles - [ ] Proper error handling implemented -- [ ] Offline-first approach maintained +- [ ] **Online-first approach maintained** (API first, cache fallback) - [ ] Performance optimizations applied - [ ] Proper state management with Riverpod - [ ] Hive models and adapters properly defined diff --git a/lib/core/providers/dio_client_provider.dart b/lib/core/providers/dio_client_provider.dart deleted file mode 100644 index f59a274..0000000 --- a/lib/core/providers/dio_client_provider.dart +++ /dev/null @@ -1,10 +0,0 @@ -import 'package:riverpod_annotation/riverpod_annotation.dart'; -import '../network/dio_client.dart'; - -part 'dio_client_provider.g.dart'; - -/// Provider for DioClient singleton -@Riverpod(keepAlive: true) -DioClient dioClient(Ref ref) { - return DioClient(); -} diff --git a/lib/core/providers/dio_client_provider.g.dart b/lib/core/providers/dio_client_provider.g.dart deleted file mode 100644 index 05074a4..0000000 --- a/lib/core/providers/dio_client_provider.g.dart +++ /dev/null @@ -1,55 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'dio_client_provider.dart'; - -// ************************************************************************** -// RiverpodGenerator -// ************************************************************************** - -// GENERATED CODE - DO NOT MODIFY BY HAND -// ignore_for_file: type=lint, type=warning -/// Provider for DioClient singleton - -@ProviderFor(dioClient) -const dioClientProvider = DioClientProvider._(); - -/// Provider for DioClient singleton - -final class DioClientProvider - extends $FunctionalProvider - with $Provider { - /// Provider for DioClient singleton - const DioClientProvider._() - : super( - from: null, - argument: null, - retry: null, - name: r'dioClientProvider', - isAutoDispose: false, - dependencies: null, - $allTransitiveDependencies: null, - ); - - @override - String debugGetCreateSourceHash() => _$dioClientHash(); - - @$internal - @override - $ProviderElement $createElement($ProviderPointer pointer) => - $ProviderElement(pointer); - - @override - DioClient create(Ref ref) { - return dioClient(ref); - } - - /// {@macro riverpod.override_with_value} - Override overrideWithValue(DioClient value) { - return $ProviderOverride( - origin: this, - providerOverride: $SyncValueProvider(value), - ); - } -} - -String _$dioClientHash() => r'895f0dc2f8d5eab562ad65390e5c6d4a1f722b0d'; diff --git a/lib/core/providers/providers.dart b/lib/core/providers/providers.dart index e9ff420..186da4f 100644 --- a/lib/core/providers/providers.dart +++ b/lib/core/providers/providers.dart @@ -2,4 +2,3 @@ export 'core_providers.dart'; export 'network_info_provider.dart'; export 'sync_status_provider.dart'; -export 'dio_client_provider.dart'; diff --git a/lib/core/widgets/empty_state.dart b/lib/core/widgets/empty_state.dart index 14cb011..5290694 100644 --- a/lib/core/widgets/empty_state.dart +++ b/lib/core/widgets/empty_state.dart @@ -27,7 +27,7 @@ class EmptyState extends StatelessWidget { children: [ Icon( icon ?? Icons.inbox_outlined, - size: 50, + size: 48, color: Theme.of(context).colorScheme.outline, ), const SizedBox(height: 24), diff --git a/lib/features/categories/data/datasources/category_local_datasource.dart b/lib/features/categories/data/datasources/category_local_datasource.dart index 1618cac..e2ca640 100644 --- a/lib/features/categories/data/datasources/category_local_datasource.dart +++ b/lib/features/categories/data/datasources/category_local_datasource.dart @@ -6,6 +6,7 @@ abstract class CategoryLocalDataSource { Future> getAllCategories(); Future getCategoryById(String id); Future cacheCategories(List categories); + Future updateCategory(CategoryModel category); Future clearCategories(); } @@ -30,6 +31,11 @@ class CategoryLocalDataSourceImpl implements CategoryLocalDataSource { await box.putAll(categoryMap); } + @override + Future updateCategory(CategoryModel category) async { + await box.put(category.id, category); + } + @override Future clearCategories() async { await box.clear(); diff --git a/lib/features/categories/data/repositories/category_repository_impl.dart b/lib/features/categories/data/repositories/category_repository_impl.dart index b1593c8..a64fd54 100644 --- a/lib/features/categories/data/repositories/category_repository_impl.dart +++ b/lib/features/categories/data/repositories/category_repository_impl.dart @@ -18,8 +18,27 @@ class CategoryRepositoryImpl implements CategoryRepository { @override Future>> getAllCategories() async { try { - final categories = await localDataSource.getAllCategories(); + // Try remote first (online-first) + final categories = await remoteDataSource.getAllCategories(); + // Cache the results + await localDataSource.cacheCategories(categories); return Right(categories.map((model) => model.toEntity()).toList()); + } on ServerException catch (e) { + // Remote failed, try local cache + try { + final cachedCategories = await localDataSource.getAllCategories(); + return Right(cachedCategories.map((model) => model.toEntity()).toList()); + } on CacheException catch (cacheError) { + return Left(ServerFailure('${e.message}. Cache also unavailable.')); + } + } on NetworkException catch (e) { + // Network failed, try local cache + try { + final cachedCategories = await localDataSource.getAllCategories(); + return Right(cachedCategories.map((model) => model.toEntity()).toList()); + } on CacheException catch (cacheError) { + return Left(NetworkFailure('${e.message}. Cache also unavailable.')); + } } on CacheException catch (e) { return Left(CacheFailure(e.message)); } @@ -28,11 +47,33 @@ class CategoryRepositoryImpl implements CategoryRepository { @override Future> getCategoryById(String id) async { try { - final category = await localDataSource.getCategoryById(id); - if (category == null) { - return Left(NotFoundFailure('Category not found')); - } + // Try remote first (online-first) + final category = await remoteDataSource.getCategoryById(id); + // Cache the result + await localDataSource.updateCategory(category); return Right(category.toEntity()); + } on ServerException catch (e) { + // Remote failed, try local cache + try { + final cachedCategory = await localDataSource.getCategoryById(id); + if (cachedCategory == null) { + return Left(NotFoundFailure('Category not found in cache')); + } + return Right(cachedCategory.toEntity()); + } on CacheException catch (cacheError) { + return Left(ServerFailure('${e.message}. Cache also unavailable.')); + } + } on NetworkException catch (e) { + // Network failed, try local cache + try { + final cachedCategory = await localDataSource.getCategoryById(id); + if (cachedCategory == null) { + return Left(NotFoundFailure('Category not found in cache')); + } + return Right(cachedCategory.toEntity()); + } on CacheException catch (cacheError) { + return Left(NetworkFailure('${e.message}. Cache also unavailable.')); + } } on CacheException catch (e) { return Left(CacheFailure(e.message)); } diff --git a/lib/features/categories/presentation/providers/categories_provider.dart b/lib/features/categories/presentation/providers/categories_provider.dart index 454c1bd..e0b7f41 100644 --- a/lib/features/categories/presentation/providers/categories_provider.dart +++ b/lib/features/categories/presentation/providers/categories_provider.dart @@ -5,12 +5,12 @@ import '../../../../core/providers/providers.dart'; part 'categories_provider.g.dart'; -/// Provider for categories list with API-first approach +/// Provider for categories list with online-first approach @riverpod class Categories extends _$Categories { @override Future> build() async { - // API-first: Try to load from API first + // Online-first: Try to load from API first final repository = ref.watch(categoryRepositoryProvider); final networkInfo = ref.watch(networkInfoProvider); @@ -90,18 +90,3 @@ class Categories extends _$Categories { }); } } - -/// Provider for selected category -@riverpod -class SelectedCategory extends _$SelectedCategory { - @override - String? build() => null; - - void select(String? categoryId) { - state = categoryId; - } - - void clear() { - state = null; - } -} diff --git a/lib/features/categories/presentation/providers/categories_provider.g.dart b/lib/features/categories/presentation/providers/categories_provider.g.dart index 8d1fc19..adb91e4 100644 --- a/lib/features/categories/presentation/providers/categories_provider.g.dart +++ b/lib/features/categories/presentation/providers/categories_provider.g.dart @@ -58,62 +58,3 @@ abstract class _$Categories extends $AsyncNotifier> { element.handleValue(ref, created); } } - -/// Provider for selected category - -@ProviderFor(SelectedCategory) -const selectedCategoryProvider = SelectedCategoryProvider._(); - -/// Provider for selected category -final class SelectedCategoryProvider - extends $NotifierProvider { - /// Provider for selected category - const SelectedCategoryProvider._() - : super( - from: null, - argument: null, - retry: null, - name: r'selectedCategoryProvider', - isAutoDispose: true, - dependencies: null, - $allTransitiveDependencies: null, - ); - - @override - String debugGetCreateSourceHash() => _$selectedCategoryHash(); - - @$internal - @override - SelectedCategory create() => SelectedCategory(); - - /// {@macro riverpod.override_with_value} - Override overrideWithValue(String? value) { - return $ProviderOverride( - origin: this, - providerOverride: $SyncValueProvider(value), - ); - } -} - -String _$selectedCategoryHash() => r'a47cd2de07ad285d4b73b2294ba954cb1cdd8e4c'; - -/// Provider for selected category - -abstract class _$SelectedCategory extends $Notifier { - String? build(); - @$mustCallSuper - @override - void runBuild() { - final created = build(); - final ref = this.ref as $Ref; - final element = - ref.element - as $ClassProviderElement< - AnyNotifier, - String?, - Object?, - Object? - >; - element.handleValue(ref, created); - } -} diff --git a/lib/features/home/presentation/widgets/product_selector.dart b/lib/features/home/presentation/widgets/product_selector.dart index 3fd59e7..3661d51 100644 --- a/lib/features/home/presentation/widgets/product_selector.dart +++ b/lib/features/home/presentation/widgets/product_selector.dart @@ -39,8 +39,7 @@ class ProductSelector extends ConsumerWidget { message: error.toString(), onRetry: () => ref.refresh(productsProvider), ), - data: (paginationState) { - final products = paginationState.products; + data: (products) { if (products.isEmpty) { return const EmptyState( diff --git a/lib/features/products/data/models/product_model.dart b/lib/features/products/data/models/product_model.dart index f1f24a4..508d6c5 100644 --- a/lib/features/products/data/models/product_model.dart +++ b/lib/features/products/data/models/product_model.dart @@ -13,7 +13,7 @@ class ProductModel extends HiveObject { final String name; @HiveField(2) - final String description; + final String? description; @HiveField(3) final double price; @@ -39,7 +39,7 @@ class ProductModel extends HiveObject { ProductModel({ required this.id, required this.name, - required this.description, + this.description, required this.price, this.imageUrl, required this.categoryId, @@ -86,7 +86,7 @@ class ProductModel extends HiveObject { return ProductModel( id: json['id'] as String, name: json['name'] as String, - description: json['description'] as String? ?? '', + description: json['description'] as String?, price: (json['price'] as num).toDouble(), imageUrl: json['imageUrl'] as String?, categoryId: json['categoryId'] as String, diff --git a/lib/features/products/data/repositories/product_repository_impl.dart b/lib/features/products/data/repositories/product_repository_impl.dart index 713c9e7..172241d 100644 --- a/lib/features/products/data/repositories/product_repository_impl.dart +++ b/lib/features/products/data/repositories/product_repository_impl.dart @@ -19,8 +19,27 @@ class ProductRepositoryImpl implements ProductRepository { @override Future>> getAllProducts() async { try { - final products = await localDataSource.getAllProducts(); + // Try remote first (online-first) + final products = await remoteDataSource.getAllProducts(); + // Cache the results + await localDataSource.cacheProducts(products); return Right(products.map((model) => model.toEntity()).toList()); + } on ServerException catch (e) { + // Remote failed, try local cache + try { + final cachedProducts = await localDataSource.getAllProducts(); + return Right(cachedProducts.map((model) => model.toEntity()).toList()); + } on CacheException catch (cacheError) { + return Left(ServerFailure('${e.message}. Cache also unavailable.')); + } + } on NetworkException catch (e) { + // Network failed, try local cache + try { + final cachedProducts = await localDataSource.getAllProducts(); + return Right(cachedProducts.map((model) => model.toEntity()).toList()); + } on CacheException catch (cacheError) { + return Left(NetworkFailure('${e.message}. Cache also unavailable.')); + } } on CacheException catch (e) { return Left(CacheFailure(e.message)); } @@ -29,9 +48,29 @@ class ProductRepositoryImpl implements ProductRepository { @override Future>> getProductsByCategory(String categoryId) async { try { - final allProducts = await localDataSource.getAllProducts(); - final filtered = allProducts.where((p) => p.categoryId == categoryId).toList(); - return Right(filtered.map((model) => model.toEntity()).toList()); + // Try remote first (online-first) + final allProducts = await remoteDataSource.getAllProducts(categoryId: categoryId); + // Cache the results + await localDataSource.cacheProducts(allProducts); + return Right(allProducts.map((model) => model.toEntity()).toList()); + } on ServerException catch (e) { + // Remote failed, try local cache + try { + final cachedProducts = await localDataSource.getAllProducts(); + final filtered = cachedProducts.where((p) => p.categoryId == categoryId).toList(); + return Right(filtered.map((model) => model.toEntity()).toList()); + } on CacheException catch (cacheError) { + return Left(ServerFailure('${e.message}. Cache also unavailable.')); + } + } on NetworkException catch (e) { + // Network failed, try local cache + try { + final cachedProducts = await localDataSource.getAllProducts(); + final filtered = cachedProducts.where((p) => p.categoryId == categoryId).toList(); + return Right(filtered.map((model) => model.toEntity()).toList()); + } on CacheException catch (cacheError) { + return Left(NetworkFailure('${e.message}. Cache also unavailable.')); + } } on CacheException catch (e) { return Left(CacheFailure(e.message)); } @@ -40,13 +79,37 @@ class ProductRepositoryImpl implements ProductRepository { @override Future>> searchProducts(String query) async { try { - final allProducts = await localDataSource.getAllProducts(); - final filtered = allProducts.where((p) { - final nameMatch = p.name.toLowerCase().contains(query.toLowerCase()); - final descMatch = p.description?.toLowerCase().contains(query.toLowerCase()) ?? false; - return nameMatch || descMatch; - }).toList(); - return Right(filtered.map((model) => model.toEntity()).toList()); + // Try remote first (online-first) + final searchResults = await remoteDataSource.getAllProducts(search: query); + // Cache the results + await localDataSource.cacheProducts(searchResults); + return Right(searchResults.map((model) => model.toEntity()).toList()); + } on ServerException catch (e) { + // Remote failed, search in local cache + try { + final cachedProducts = await localDataSource.getAllProducts(); + final filtered = cachedProducts.where((p) { + final nameMatch = p.name.toLowerCase().contains(query.toLowerCase()); + final descMatch = p.description?.toLowerCase().contains(query.toLowerCase()) ?? false; + return nameMatch || descMatch; + }).toList(); + return Right(filtered.map((model) => model.toEntity()).toList()); + } on CacheException catch (cacheError) { + return Left(ServerFailure('${e.message}. Cache also unavailable.')); + } + } on NetworkException catch (e) { + // Network failed, search in local cache + try { + final cachedProducts = await localDataSource.getAllProducts(); + final filtered = cachedProducts.where((p) { + final nameMatch = p.name.toLowerCase().contains(query.toLowerCase()); + final descMatch = p.description?.toLowerCase().contains(query.toLowerCase()) ?? false; + return nameMatch || descMatch; + }).toList(); + return Right(filtered.map((model) => model.toEntity()).toList()); + } on CacheException catch (cacheError) { + return Left(NetworkFailure('${e.message}. Cache also unavailable.')); + } } on CacheException catch (e) { return Left(CacheFailure(e.message)); } @@ -55,11 +118,33 @@ class ProductRepositoryImpl implements ProductRepository { @override Future> getProductById(String id) async { try { - final product = await localDataSource.getProductById(id); - if (product == null) { - return Left(NotFoundFailure('Product not found')); - } + // Try remote first (online-first) + final product = await remoteDataSource.getProductById(id); + // Cache the result + await localDataSource.updateProduct(product); return Right(product.toEntity()); + } on ServerException catch (e) { + // Remote failed, try local cache + try { + final cachedProduct = await localDataSource.getProductById(id); + if (cachedProduct == null) { + return Left(NotFoundFailure('Product not found in cache')); + } + return Right(cachedProduct.toEntity()); + } on CacheException catch (cacheError) { + return Left(ServerFailure('${e.message}. Cache also unavailable.')); + } + } on NetworkException catch (e) { + // Network failed, try local cache + try { + final cachedProduct = await localDataSource.getProductById(id); + if (cachedProduct == null) { + return Left(NotFoundFailure('Product not found in cache')); + } + return Right(cachedProduct.toEntity()); + } on CacheException catch (cacheError) { + return Left(NetworkFailure('${e.message}. Cache also unavailable.')); + } } on CacheException catch (e) { return Left(CacheFailure(e.message)); } @@ -68,11 +153,7 @@ class ProductRepositoryImpl implements ProductRepository { @override Future>> syncProducts() async { try { - final response = await remoteDataSource.getAllProducts(); - final productsData = response['data'] as List; - final products = productsData - .map((json) => ProductModel.fromJson(json as Map)) - .toList(); + final products = await remoteDataSource.getAllProducts(); await localDataSource.cacheProducts(products); final entities = products.map((model) => model.toEntity()).toList(); return Right(entities); diff --git a/lib/features/products/presentation/pages/product_detail_page.dart b/lib/features/products/presentation/pages/product_detail_page.dart index 33c7a7c..9d5fa70 100644 --- a/lib/features/products/presentation/pages/product_detail_page.dart +++ b/lib/features/products/presentation/pages/product_detail_page.dart @@ -246,7 +246,7 @@ class ProductDetailPage extends ConsumerWidget { /// Build description section Widget _buildDescriptionSection(BuildContext context) { - if (product.description.isEmpty) { + if (product.description == null || product.description!.isEmpty) { return const SizedBox.shrink(); } @@ -261,7 +261,7 @@ class ProductDetailPage extends ConsumerWidget { ), const SizedBox(height: 8), Text( - product.description, + product.description!, style: Theme.of(context).textTheme.bodyLarge, ), ], diff --git a/lib/features/products/presentation/providers/filtered_products_provider.dart b/lib/features/products/presentation/providers/filtered_products_provider.dart index 4950310..4c2b113 100644 --- a/lib/features/products/presentation/providers/filtered_products_provider.dart +++ b/lib/features/products/presentation/providers/filtered_products_provider.dart @@ -15,7 +15,7 @@ class FilteredProducts extends _$FilteredProducts { // Watch products state final productsAsync = ref.watch(productsProvider); final products = productsAsync.when( - data: (data) => data.products, + data: (data) => data, loading: () => [], error: (_, __) => [], ); diff --git a/lib/features/products/presentation/providers/filtered_products_provider.g.dart b/lib/features/products/presentation/providers/filtered_products_provider.g.dart index fd54431..a5ce7b9 100644 --- a/lib/features/products/presentation/providers/filtered_products_provider.g.dart +++ b/lib/features/products/presentation/providers/filtered_products_provider.g.dart @@ -50,7 +50,7 @@ final class FilteredProductsProvider } } -String _$filteredProductsHash() => r'd8ca6d80a71bf354e3afe6c38335996a8bfc74b7'; +String _$filteredProductsHash() => r'97fb09ade4bc65f92f3d4844b059bb2b0660d3df'; /// Filtered products provider /// Combines products, search query, and category filter to provide filtered results diff --git a/lib/features/products/presentation/providers/products_provider.dart b/lib/features/products/presentation/providers/products_provider.dart index ebfd8a7..795caad 100644 --- a/lib/features/products/presentation/providers/products_provider.dart +++ b/lib/features/products/presentation/providers/products_provider.dart @@ -5,12 +5,12 @@ import '../../../../core/providers/providers.dart'; part 'products_provider.g.dart'; -/// Provider for products list with API-first approach +/// Provider for products list with online-first approach @riverpod class Products extends _$Products { @override Future> build() async { - // API-first: Try to load from API first + // Online-first: Try to load from API first final repository = ref.watch(productRepositoryProvider); final networkInfo = ref.watch(networkInfoProvider); diff --git a/lib/features/products/presentation/widgets/product_list_item.dart b/lib/features/products/presentation/widgets/product_list_item.dart index a8580eb..2f95e6d 100644 --- a/lib/features/products/presentation/widgets/product_list_item.dart +++ b/lib/features/products/presentation/widgets/product_list_item.dart @@ -85,9 +85,9 @@ class ProductListItem extends StatelessWidget { overflow: TextOverflow.ellipsis, ), const SizedBox(height: 4), - if (product.description.isNotEmpty) + if (product.description != null && product.description!.isNotEmpty) Text( - product.description, + product.description!, style: Theme.of(context).textTheme.bodySmall?.copyWith( color: Theme.of(context).colorScheme.onSurfaceVariant, ),