From a7ba6230058d5c3ca5e2d9bd3e5be8ff3796564d Mon Sep 17 00:00:00 2001 From: lenka369 Date: Fri, 31 Oct 2025 10:36:34 +0100 Subject: [PATCH 1/5] fix: Save ingredient to cache on selection --- lib/providers/nutrition.dart | 50 +++++++++++++++++++++--------- lib/widgets/nutrition/widgets.dart | 5 ++- 2 files changed, 40 insertions(+), 15 deletions(-) diff --git a/lib/providers/nutrition.dart b/lib/providers/nutrition.dart index 3fea337c..ab2b9ad7 100644 --- a/lib/providers/nutrition.dart +++ b/lib/providers/nutrition.dart @@ -302,6 +302,27 @@ class NutritionPlansProvider with ChangeNotifier { await database.deleteEverything(); } + + + /// Saves an ingredient to the cache + Future cacheIngredient(Ingredient ingredient, {IngredientDatabase? database}) async { + database ??= this.database; + + ingredients.add(ingredient); + + final data = ingredient.toJson(); + await database + .into(database.ingredients) + .insert( + IngredientsCompanion.insert( + id: ingredient.id, + data: jsonEncode(data), + lastFetched: DateTime.now(), + ), + ); + _logger.finer("Saved ingredient '${ingredient.name}' to db cache"); + } + /// Fetch and return an ingredient /// /// If the ingredient is not known locally, it is fetched from the server @@ -319,7 +340,7 @@ class NutritionPlansProvider with ChangeNotifier { // Try to fetch from local db if (ingredientDb != null) { ingredient = Ingredient.fromJson(jsonDecode(ingredientDb.data)); - ingredients.add(ingredient); + ingredients.add(ingredient); _logger.info("Loaded ingredient '${ingredient.name}' from db cache"); // Prune old entries @@ -329,22 +350,14 @@ class NutritionPlansProvider with ChangeNotifier { (database.delete(database.ingredients)..where((i) => i.id.equals(ingredientId))).go(); } } else { + print("Fetching ingredient ID $ingredientId from server"); final data = await baseProvider.fetch( baseProvider.makeUrl(_ingredientInfoPath, id: ingredientId), ); ingredient = Ingredient.fromJson(data); - ingredients.add(ingredient); - database - .into(database.ingredients) - .insert( - IngredientsCompanion.insert( - id: ingredientId, - data: jsonEncode(data), - lastFetched: DateTime.now(), - ), - ); - _logger.finer("Saved ingredient '${ingredient.name}' to db cache"); + // Cache the ingredient + await cacheIngredient(ingredient, database: database); } } @@ -376,6 +389,7 @@ class NutritionPlansProvider with ChangeNotifier { } // Send the request + print("Fetching ingredient from server"); final response = await baseProvider.fetch( baseProvider.makeUrl( _ingredientInfoPath, @@ -406,8 +420,16 @@ class NutritionPlansProvider with ChangeNotifier { if (data['count'] == 0) { return null; } - // TODO we should probably add it to ingredient cache. - return Ingredient.fromJson(data['results'][0]); + final ingredient = Ingredient.fromJson(data['results'][0]); + + // Cache the ingredient + try { + await cacheIngredient(ingredient); + } catch (e) { + _logger.warning("Could not cache ingredient ${ingredient.id} from barcode search: $e"); + } + + return ingredient; } /// Log meal to nutrition diary diff --git a/lib/widgets/nutrition/widgets.dart b/lib/widgets/nutrition/widgets.dart index c44b2bd3..f2fc364f 100644 --- a/lib/widgets/nutrition/widgets.dart +++ b/lib/widgets/nutrition/widgets.dart @@ -184,7 +184,10 @@ class _IngredientTypeaheadState extends State { opacity: CurvedAnimation(parent: animation, curve: Curves.fastOutSlowIn), child: child, ), - onSelected: (suggestion) { + onSelected: (suggestion) async { + // Cache selected ingredient + final provider = Provider.of(context, listen: false); + await provider.cacheIngredient(suggestion); widget.selectIngredient(suggestion.id, suggestion.name, null); }, ), From ed938cd3d25822565de74cb0bcd4d00e26ca50fc Mon Sep 17 00:00:00 2001 From: lenka369 Date: Mon, 3 Nov 2025 10:02:40 +0100 Subject: [PATCH 2/5] Replaced print statements with logger --- lib/providers/nutrition.dart | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/lib/providers/nutrition.dart b/lib/providers/nutrition.dart index ab2b9ad7..dad470b6 100644 --- a/lib/providers/nutrition.dart +++ b/lib/providers/nutrition.dart @@ -48,6 +48,10 @@ class NutritionPlansProvider with ChangeNotifier { late IngredientDatabase database; List _plans = []; List ingredients = []; + + // Track current search to prevent multiple concurrent requests + String? _currentSearchQuery; + int _searchCounter = 0; NutritionPlansProvider( this.baseProvider, @@ -350,7 +354,7 @@ class NutritionPlansProvider with ChangeNotifier { (database.delete(database.ingredients)..where((i) => i.id.equals(ingredientId))).go(); } } else { - print("Fetching ingredient ID $ingredientId from server"); + _logger.info("Fetching ingredient ID $ingredientId from server"); final data = await baseProvider.fetch( baseProvider.makeUrl(_ingredientInfoPath, id: ingredientId), ); @@ -389,7 +393,7 @@ class NutritionPlansProvider with ChangeNotifier { } // Send the request - print("Fetching ingredient from server"); + _logger.info("Fetching ingredients from server"); final response = await baseProvider.fetch( baseProvider.makeUrl( _ingredientInfoPath, @@ -420,16 +424,10 @@ class NutritionPlansProvider with ChangeNotifier { if (data['count'] == 0) { return null; } - final ingredient = Ingredient.fromJson(data['results'][0]); - // Cache the ingredient - try { - await cacheIngredient(ingredient); - } catch (e) { - _logger.warning("Could not cache ingredient ${ingredient.id} from barcode search: $e"); - } - - return ingredient; + // TODO we should probably add it to ingredient cache. + return Ingredient.fromJson(data['results'][0]); + } /// Log meal to nutrition diary From fc48c707e8f8c181bdc0ecb64598404bb752cdae Mon Sep 17 00:00:00 2001 From: lenka369 Date: Tue, 4 Nov 2025 05:21:18 +0100 Subject: [PATCH 3/5] Added tests --- lib/providers/nutrition.dart | 9 +++---- test/nutrition/nutrition_provider_test.dart | 15 +++++++++++ .../nutritional_meal_item_form_test.dart | 26 +++++++++++++++++++ 3 files changed, 44 insertions(+), 6 deletions(-) diff --git a/lib/providers/nutrition.dart b/lib/providers/nutrition.dart index dad470b6..a2f9abb2 100644 --- a/lib/providers/nutrition.dart +++ b/lib/providers/nutrition.dart @@ -48,7 +48,7 @@ class NutritionPlansProvider with ChangeNotifier { late IngredientDatabase database; List _plans = []; List ingredients = []; - + // Track current search to prevent multiple concurrent requests String? _currentSearchQuery; int _searchCounter = 0; @@ -306,9 +306,7 @@ class NutritionPlansProvider with ChangeNotifier { await database.deleteEverything(); } - - - /// Saves an ingredient to the cache + /// Saves an ingredient to the cache Future cacheIngredient(Ingredient ingredient, {IngredientDatabase? database}) async { database ??= this.database; @@ -344,7 +342,7 @@ class NutritionPlansProvider with ChangeNotifier { // Try to fetch from local db if (ingredientDb != null) { ingredient = Ingredient.fromJson(jsonDecode(ingredientDb.data)); - ingredients.add(ingredient); + ingredients.add(ingredient); _logger.info("Loaded ingredient '${ingredient.name}' from db cache"); // Prune old entries @@ -427,7 +425,6 @@ class NutritionPlansProvider with ChangeNotifier { // TODO we should probably add it to ingredient cache. return Ingredient.fromJson(data['results'][0]); - } /// Log meal to nutrition diary diff --git a/test/nutrition/nutrition_provider_test.dart b/test/nutrition/nutrition_provider_test.dart index 81a03bec..e7a1a75b 100644 --- a/test/nutrition/nutrition_provider_test.dart +++ b/test/nutrition/nutrition_provider_test.dart @@ -207,11 +207,13 @@ void main() { description: 'Old active plan', startDate: now.subtract(const Duration(days: 10)), endDate: now.add(const Duration(days: 10)), + creationDate: now.subtract(const Duration(days: 10)), ); final newerPlan = NutritionalPlan( description: 'Newer active plan', startDate: now.subtract(const Duration(days: 5)), endDate: now.add(const Duration(days: 5)), + creationDate: now.subtract(const Duration(days: 1)), ); nutritionProvider = NutritionPlansProvider(mockWgerBaseProvider, [ olderPlan, @@ -222,6 +224,19 @@ void main() { }); group('Ingredient cache DB', () { + test('cacheIngredient saves to both in-memory and database cache', () async { + nutritionProvider.ingredients = []; + final ingredient = Ingredient.fromJson(ingredient59887Response); + + await nutritionProvider.cacheIngredient(ingredient, database: database); + + expect(nutritionProvider.ingredients.length, 1); + expect(nutritionProvider.ingredients.first.id, 59887); + + final rows = await database.select(database.ingredients).get(); + expect(rows.length, 1); + expect(rows.first.id, ingredient.id); + }); test('that if there is already valid data in the DB, the API is not hit', () async { // Arrange nutritionProvider.ingredients = []; diff --git a/test/nutrition/nutritional_meal_item_form_test.dart b/test/nutrition/nutritional_meal_item_form_test.dart index 9b4a0100..eb28f0b0 100644 --- a/test/nutrition/nutritional_meal_item_form_test.dart +++ b/test/nutrition/nutritional_meal_item_form_test.dart @@ -331,5 +331,31 @@ void main() { verify(mockNutrition.addMealItem(any, meal1)); }, ); + + testWidgets('selecting ingredient from autocomplete calls cacheIngredient', ( + WidgetTester tester, + ) async { + await tester.pumpWidget(createMealItemFormScreen(meal1, '', true)); + await tester.pumpAndSettle(); + + clearInteractions(mockNutrition); + + when( + mockNutrition.searchIngredient( + any, + languageCode: anyNamed('languageCode'), + searchEnglish: anyNamed('searchEnglish'), + ), + ).thenAnswer((_) => Future.value([ingredient1])); + + await tester.enterText(find.byType(TextFormField).first, 'Water'); + await tester.pumpAndSettle(const Duration(milliseconds: 600)); + await tester.pumpAndSettle(); + + await tester.tap(find.byType(ListTile).first); + await tester.pumpAndSettle(); + + verify(mockNutrition.cacheIngredient(ingredient1)).called(1); + }); }); } From aaa91a45919e0bbb638ab9e194927ce5718a2bd1 Mon Sep 17 00:00:00 2001 From: lenka369 Date: Sun, 16 Nov 2025 08:53:18 +0100 Subject: [PATCH 4/5] Update mock files --- lib/providers/nutrition.dart | 4 - .../nutritional_meal_form_test.mocks.dart | 142 ++++++++++++++---- .../nutritional_plan_form_test.mocks.dart | 142 ++++++++++++++---- 3 files changed, 220 insertions(+), 68 deletions(-) diff --git a/lib/providers/nutrition.dart b/lib/providers/nutrition.dart index a2f9abb2..d1603119 100644 --- a/lib/providers/nutrition.dart +++ b/lib/providers/nutrition.dart @@ -49,10 +49,6 @@ class NutritionPlansProvider with ChangeNotifier { List _plans = []; List ingredients = []; - // Track current search to prevent multiple concurrent requests - String? _currentSearchQuery; - int _searchCounter = 0; - NutritionPlansProvider( this.baseProvider, List entries, { diff --git a/test/nutrition/nutritional_meal_form_test.mocks.dart b/test/nutrition/nutritional_meal_form_test.mocks.dart index 9b9252c2..7762be98 100644 --- a/test/nutrition/nutritional_meal_form_test.mocks.dart +++ b/test/nutrition/nutritional_meal_form_test.mocks.dart @@ -30,37 +30,44 @@ import 'package:wger/providers/nutrition.dart' as _i8; // ignore_for_file: subtype_of_sealed_class // ignore_for_file: invalid_use_of_internal_member -class _FakeWgerBaseProvider_0 extends _i1.SmartFake implements _i2.WgerBaseProvider { +class _FakeWgerBaseProvider_0 extends _i1.SmartFake + implements _i2.WgerBaseProvider { _FakeWgerBaseProvider_0(Object parent, Invocation parentInvocation) : super(parent, parentInvocation); } -class _FakeIngredientDatabase_1 extends _i1.SmartFake implements _i3.IngredientDatabase { +class _FakeIngredientDatabase_1 extends _i1.SmartFake + implements _i3.IngredientDatabase { _FakeIngredientDatabase_1(Object parent, Invocation parentInvocation) : super(parent, parentInvocation); } -class _FakeNutritionalPlan_2 extends _i1.SmartFake implements _i4.NutritionalPlan { +class _FakeNutritionalPlan_2 extends _i1.SmartFake + implements _i4.NutritionalPlan { _FakeNutritionalPlan_2(Object parent, Invocation parentInvocation) : super(parent, parentInvocation); } class _FakeMeal_3 extends _i1.SmartFake implements _i5.Meal { - _FakeMeal_3(Object parent, Invocation parentInvocation) : super(parent, parentInvocation); + _FakeMeal_3(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); } class _FakeMealItem_4 extends _i1.SmartFake implements _i6.MealItem { - _FakeMealItem_4(Object parent, Invocation parentInvocation) : super(parent, parentInvocation); + _FakeMealItem_4(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); } class _FakeIngredient_5 extends _i1.SmartFake implements _i7.Ingredient { - _FakeIngredient_5(Object parent, Invocation parentInvocation) : super(parent, parentInvocation); + _FakeIngredient_5(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); } /// A class which mocks [NutritionPlansProvider]. /// /// See the documentation for Mockito's code generation for more information. -class MockNutritionPlansProvider extends _i1.Mock implements _i8.NutritionPlansProvider { +class MockNutritionPlansProvider extends _i1.Mock + implements _i8.NutritionPlansProvider { MockNutritionPlansProvider() { _i1.throwOnMissingStub(this); } @@ -69,7 +76,10 @@ class MockNutritionPlansProvider extends _i1.Mock implements _i8.NutritionPlansP _i2.WgerBaseProvider get baseProvider => (super.noSuchMethod( Invocation.getter(#baseProvider), - returnValue: _FakeWgerBaseProvider_0(this, Invocation.getter(#baseProvider)), + returnValue: _FakeWgerBaseProvider_0( + this, + Invocation.getter(#baseProvider), + ), ) as _i2.WgerBaseProvider); @@ -77,41 +87,60 @@ class MockNutritionPlansProvider extends _i1.Mock implements _i8.NutritionPlansP _i3.IngredientDatabase get database => (super.noSuchMethod( Invocation.getter(#database), - returnValue: _FakeIngredientDatabase_1(this, Invocation.getter(#database)), + returnValue: _FakeIngredientDatabase_1( + this, + Invocation.getter(#database), + ), ) as _i3.IngredientDatabase); @override List<_i7.Ingredient> get ingredients => - (super.noSuchMethod(Invocation.getter(#ingredients), returnValue: <_i7.Ingredient>[]) + (super.noSuchMethod( + Invocation.getter(#ingredients), + returnValue: <_i7.Ingredient>[], + ) as List<_i7.Ingredient>); @override List<_i4.NutritionalPlan> get items => - (super.noSuchMethod(Invocation.getter(#items), returnValue: <_i4.NutritionalPlan>[]) + (super.noSuchMethod( + Invocation.getter(#items), + returnValue: <_i4.NutritionalPlan>[], + ) as List<_i4.NutritionalPlan>); @override - set database(_i3.IngredientDatabase? value) => - super.noSuchMethod(Invocation.setter(#database, value), returnValueForMissingStub: null); + set database(_i3.IngredientDatabase? value) => super.noSuchMethod( + Invocation.setter(#database, value), + returnValueForMissingStub: null, + ); @override - set ingredients(List<_i7.Ingredient>? value) => - super.noSuchMethod(Invocation.setter(#ingredients, value), returnValueForMissingStub: null); + set ingredients(List<_i7.Ingredient>? value) => super.noSuchMethod( + Invocation.setter(#ingredients, value), + returnValueForMissingStub: null, + ); @override bool get hasListeners => - (super.noSuchMethod(Invocation.getter(#hasListeners), returnValue: false) as bool); + (super.noSuchMethod(Invocation.getter(#hasListeners), returnValue: false) + as bool); @override - void clear() => - super.noSuchMethod(Invocation.method(#clear, []), returnValueForMissingStub: null); + void clear() => super.noSuchMethod( + Invocation.method(#clear, []), + returnValueForMissingStub: null, + ); @override _i4.NutritionalPlan findById(int? id) => (super.noSuchMethod( Invocation.method(#findById, [id]), - returnValue: _FakeNutritionalPlan_2(this, Invocation.method(#findById, [id])), + returnValue: _FakeNutritionalPlan_2( + this, + Invocation.method(#findById, [id]), + ), ) as _i4.NutritionalPlan); @@ -142,7 +171,10 @@ class MockNutritionPlansProvider extends _i1.Mock implements _i8.NutritionPlansP (super.noSuchMethod( Invocation.method(#fetchAndSetPlanSparse, [planId]), returnValue: _i9.Future<_i4.NutritionalPlan>.value( - _FakeNutritionalPlan_2(this, Invocation.method(#fetchAndSetPlanSparse, [planId])), + _FakeNutritionalPlan_2( + this, + Invocation.method(#fetchAndSetPlanSparse, [planId]), + ), ), ) as _i9.Future<_i4.NutritionalPlan>); @@ -152,7 +184,10 @@ class MockNutritionPlansProvider extends _i1.Mock implements _i8.NutritionPlansP (super.noSuchMethod( Invocation.method(#fetchAndSetPlanFull, [planId]), returnValue: _i9.Future<_i4.NutritionalPlan>.value( - _FakeNutritionalPlan_2(this, Invocation.method(#fetchAndSetPlanFull, [planId])), + _FakeNutritionalPlan_2( + this, + Invocation.method(#fetchAndSetPlanFull, [planId]), + ), ), ) as _i9.Future<_i4.NutritionalPlan>); @@ -162,7 +197,10 @@ class MockNutritionPlansProvider extends _i1.Mock implements _i8.NutritionPlansP (super.noSuchMethod( Invocation.method(#addPlan, [planData]), returnValue: _i9.Future<_i4.NutritionalPlan>.value( - _FakeNutritionalPlan_2(this, Invocation.method(#addPlan, [planData])), + _FakeNutritionalPlan_2( + this, + Invocation.method(#addPlan, [planData]), + ), ), ) as _i9.Future<_i4.NutritionalPlan>); @@ -215,11 +253,17 @@ class MockNutritionPlansProvider extends _i1.Mock implements _i8.NutritionPlansP as _i9.Future); @override - _i9.Future<_i6.MealItem> addMealItem(_i6.MealItem? mealItem, _i5.Meal? meal) => + _i9.Future<_i6.MealItem> addMealItem( + _i6.MealItem? mealItem, + _i5.Meal? meal, + ) => (super.noSuchMethod( Invocation.method(#addMealItem, [mealItem, meal]), returnValue: _i9.Future<_i6.MealItem>.value( - _FakeMealItem_4(this, Invocation.method(#addMealItem, [mealItem, meal])), + _FakeMealItem_4( + this, + Invocation.method(#addMealItem, [mealItem, meal]), + ), ), ) as _i9.Future<_i6.MealItem>); @@ -242,17 +286,41 @@ class MockNutritionPlansProvider extends _i1.Mock implements _i8.NutritionPlansP ) as _i9.Future); + @override + _i9.Future cacheIngredient( + _i7.Ingredient? ingredient, { + _i3.IngredientDatabase? database, + }) => + (super.noSuchMethod( + Invocation.method( + #cacheIngredient, + [ingredient], + {#database: database}, + ), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) + as _i9.Future); + @override _i9.Future<_i7.Ingredient> fetchIngredient( int? ingredientId, { _i3.IngredientDatabase? database, }) => (super.noSuchMethod( - Invocation.method(#fetchIngredient, [ingredientId], {#database: database}), + Invocation.method( + #fetchIngredient, + [ingredientId], + {#database: database}, + ), returnValue: _i9.Future<_i7.Ingredient>.value( _FakeIngredient_5( this, - Invocation.method(#fetchIngredient, [ingredientId], {#database: database}), + Invocation.method( + #fetchIngredient, + [ingredientId], + {#database: database}, + ), ), ), ) @@ -279,7 +347,9 @@ class MockNutritionPlansProvider extends _i1.Mock implements _i8.NutritionPlansP [name], {#languageCode: languageCode, #searchEnglish: searchEnglish}, ), - returnValue: _i9.Future>.value(<_i7.Ingredient>[]), + returnValue: _i9.Future>.value( + <_i7.Ingredient>[], + ), ) as _i9.Future>); @@ -307,7 +377,11 @@ class MockNutritionPlansProvider extends _i1.Mock implements _i8.NutritionPlansP DateTime? dateTime, ]) => (super.noSuchMethod( - Invocation.method(#logIngredientToDiary, [mealItem, planId, dateTime]), + Invocation.method(#logIngredientToDiary, [ + mealItem, + planId, + dateTime, + ]), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) @@ -344,10 +418,14 @@ class MockNutritionPlansProvider extends _i1.Mock implements _i8.NutritionPlansP ); @override - void dispose() => - super.noSuchMethod(Invocation.method(#dispose, []), returnValueForMissingStub: null); + void dispose() => super.noSuchMethod( + Invocation.method(#dispose, []), + returnValueForMissingStub: null, + ); @override - void notifyListeners() => - super.noSuchMethod(Invocation.method(#notifyListeners, []), returnValueForMissingStub: null); + void notifyListeners() => super.noSuchMethod( + Invocation.method(#notifyListeners, []), + returnValueForMissingStub: null, + ); } diff --git a/test/nutrition/nutritional_plan_form_test.mocks.dart b/test/nutrition/nutritional_plan_form_test.mocks.dart index 801f58c3..41196501 100644 --- a/test/nutrition/nutritional_plan_form_test.mocks.dart +++ b/test/nutrition/nutritional_plan_form_test.mocks.dart @@ -30,37 +30,44 @@ import 'package:wger/providers/nutrition.dart' as _i8; // ignore_for_file: subtype_of_sealed_class // ignore_for_file: invalid_use_of_internal_member -class _FakeWgerBaseProvider_0 extends _i1.SmartFake implements _i2.WgerBaseProvider { +class _FakeWgerBaseProvider_0 extends _i1.SmartFake + implements _i2.WgerBaseProvider { _FakeWgerBaseProvider_0(Object parent, Invocation parentInvocation) : super(parent, parentInvocation); } -class _FakeIngredientDatabase_1 extends _i1.SmartFake implements _i3.IngredientDatabase { +class _FakeIngredientDatabase_1 extends _i1.SmartFake + implements _i3.IngredientDatabase { _FakeIngredientDatabase_1(Object parent, Invocation parentInvocation) : super(parent, parentInvocation); } -class _FakeNutritionalPlan_2 extends _i1.SmartFake implements _i4.NutritionalPlan { +class _FakeNutritionalPlan_2 extends _i1.SmartFake + implements _i4.NutritionalPlan { _FakeNutritionalPlan_2(Object parent, Invocation parentInvocation) : super(parent, parentInvocation); } class _FakeMeal_3 extends _i1.SmartFake implements _i5.Meal { - _FakeMeal_3(Object parent, Invocation parentInvocation) : super(parent, parentInvocation); + _FakeMeal_3(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); } class _FakeMealItem_4 extends _i1.SmartFake implements _i6.MealItem { - _FakeMealItem_4(Object parent, Invocation parentInvocation) : super(parent, parentInvocation); + _FakeMealItem_4(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); } class _FakeIngredient_5 extends _i1.SmartFake implements _i7.Ingredient { - _FakeIngredient_5(Object parent, Invocation parentInvocation) : super(parent, parentInvocation); + _FakeIngredient_5(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); } /// A class which mocks [NutritionPlansProvider]. /// /// See the documentation for Mockito's code generation for more information. -class MockNutritionPlansProvider extends _i1.Mock implements _i8.NutritionPlansProvider { +class MockNutritionPlansProvider extends _i1.Mock + implements _i8.NutritionPlansProvider { MockNutritionPlansProvider() { _i1.throwOnMissingStub(this); } @@ -69,7 +76,10 @@ class MockNutritionPlansProvider extends _i1.Mock implements _i8.NutritionPlansP _i2.WgerBaseProvider get baseProvider => (super.noSuchMethod( Invocation.getter(#baseProvider), - returnValue: _FakeWgerBaseProvider_0(this, Invocation.getter(#baseProvider)), + returnValue: _FakeWgerBaseProvider_0( + this, + Invocation.getter(#baseProvider), + ), ) as _i2.WgerBaseProvider); @@ -77,41 +87,60 @@ class MockNutritionPlansProvider extends _i1.Mock implements _i8.NutritionPlansP _i3.IngredientDatabase get database => (super.noSuchMethod( Invocation.getter(#database), - returnValue: _FakeIngredientDatabase_1(this, Invocation.getter(#database)), + returnValue: _FakeIngredientDatabase_1( + this, + Invocation.getter(#database), + ), ) as _i3.IngredientDatabase); @override List<_i7.Ingredient> get ingredients => - (super.noSuchMethod(Invocation.getter(#ingredients), returnValue: <_i7.Ingredient>[]) + (super.noSuchMethod( + Invocation.getter(#ingredients), + returnValue: <_i7.Ingredient>[], + ) as List<_i7.Ingredient>); @override List<_i4.NutritionalPlan> get items => - (super.noSuchMethod(Invocation.getter(#items), returnValue: <_i4.NutritionalPlan>[]) + (super.noSuchMethod( + Invocation.getter(#items), + returnValue: <_i4.NutritionalPlan>[], + ) as List<_i4.NutritionalPlan>); @override - set database(_i3.IngredientDatabase? value) => - super.noSuchMethod(Invocation.setter(#database, value), returnValueForMissingStub: null); + set database(_i3.IngredientDatabase? value) => super.noSuchMethod( + Invocation.setter(#database, value), + returnValueForMissingStub: null, + ); @override - set ingredients(List<_i7.Ingredient>? value) => - super.noSuchMethod(Invocation.setter(#ingredients, value), returnValueForMissingStub: null); + set ingredients(List<_i7.Ingredient>? value) => super.noSuchMethod( + Invocation.setter(#ingredients, value), + returnValueForMissingStub: null, + ); @override bool get hasListeners => - (super.noSuchMethod(Invocation.getter(#hasListeners), returnValue: false) as bool); + (super.noSuchMethod(Invocation.getter(#hasListeners), returnValue: false) + as bool); @override - void clear() => - super.noSuchMethod(Invocation.method(#clear, []), returnValueForMissingStub: null); + void clear() => super.noSuchMethod( + Invocation.method(#clear, []), + returnValueForMissingStub: null, + ); @override _i4.NutritionalPlan findById(int? id) => (super.noSuchMethod( Invocation.method(#findById, [id]), - returnValue: _FakeNutritionalPlan_2(this, Invocation.method(#findById, [id])), + returnValue: _FakeNutritionalPlan_2( + this, + Invocation.method(#findById, [id]), + ), ) as _i4.NutritionalPlan); @@ -142,7 +171,10 @@ class MockNutritionPlansProvider extends _i1.Mock implements _i8.NutritionPlansP (super.noSuchMethod( Invocation.method(#fetchAndSetPlanSparse, [planId]), returnValue: _i9.Future<_i4.NutritionalPlan>.value( - _FakeNutritionalPlan_2(this, Invocation.method(#fetchAndSetPlanSparse, [planId])), + _FakeNutritionalPlan_2( + this, + Invocation.method(#fetchAndSetPlanSparse, [planId]), + ), ), ) as _i9.Future<_i4.NutritionalPlan>); @@ -152,7 +184,10 @@ class MockNutritionPlansProvider extends _i1.Mock implements _i8.NutritionPlansP (super.noSuchMethod( Invocation.method(#fetchAndSetPlanFull, [planId]), returnValue: _i9.Future<_i4.NutritionalPlan>.value( - _FakeNutritionalPlan_2(this, Invocation.method(#fetchAndSetPlanFull, [planId])), + _FakeNutritionalPlan_2( + this, + Invocation.method(#fetchAndSetPlanFull, [planId]), + ), ), ) as _i9.Future<_i4.NutritionalPlan>); @@ -162,7 +197,10 @@ class MockNutritionPlansProvider extends _i1.Mock implements _i8.NutritionPlansP (super.noSuchMethod( Invocation.method(#addPlan, [planData]), returnValue: _i9.Future<_i4.NutritionalPlan>.value( - _FakeNutritionalPlan_2(this, Invocation.method(#addPlan, [planData])), + _FakeNutritionalPlan_2( + this, + Invocation.method(#addPlan, [planData]), + ), ), ) as _i9.Future<_i4.NutritionalPlan>); @@ -215,11 +253,17 @@ class MockNutritionPlansProvider extends _i1.Mock implements _i8.NutritionPlansP as _i9.Future); @override - _i9.Future<_i6.MealItem> addMealItem(_i6.MealItem? mealItem, _i5.Meal? meal) => + _i9.Future<_i6.MealItem> addMealItem( + _i6.MealItem? mealItem, + _i5.Meal? meal, + ) => (super.noSuchMethod( Invocation.method(#addMealItem, [mealItem, meal]), returnValue: _i9.Future<_i6.MealItem>.value( - _FakeMealItem_4(this, Invocation.method(#addMealItem, [mealItem, meal])), + _FakeMealItem_4( + this, + Invocation.method(#addMealItem, [mealItem, meal]), + ), ), ) as _i9.Future<_i6.MealItem>); @@ -242,17 +286,41 @@ class MockNutritionPlansProvider extends _i1.Mock implements _i8.NutritionPlansP ) as _i9.Future); + @override + _i9.Future cacheIngredient( + _i7.Ingredient? ingredient, { + _i3.IngredientDatabase? database, + }) => + (super.noSuchMethod( + Invocation.method( + #cacheIngredient, + [ingredient], + {#database: database}, + ), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) + as _i9.Future); + @override _i9.Future<_i7.Ingredient> fetchIngredient( int? ingredientId, { _i3.IngredientDatabase? database, }) => (super.noSuchMethod( - Invocation.method(#fetchIngredient, [ingredientId], {#database: database}), + Invocation.method( + #fetchIngredient, + [ingredientId], + {#database: database}, + ), returnValue: _i9.Future<_i7.Ingredient>.value( _FakeIngredient_5( this, - Invocation.method(#fetchIngredient, [ingredientId], {#database: database}), + Invocation.method( + #fetchIngredient, + [ingredientId], + {#database: database}, + ), ), ), ) @@ -279,7 +347,9 @@ class MockNutritionPlansProvider extends _i1.Mock implements _i8.NutritionPlansP [name], {#languageCode: languageCode, #searchEnglish: searchEnglish}, ), - returnValue: _i9.Future>.value(<_i7.Ingredient>[]), + returnValue: _i9.Future>.value( + <_i7.Ingredient>[], + ), ) as _i9.Future>); @@ -307,7 +377,11 @@ class MockNutritionPlansProvider extends _i1.Mock implements _i8.NutritionPlansP DateTime? dateTime, ]) => (super.noSuchMethod( - Invocation.method(#logIngredientToDiary, [mealItem, planId, dateTime]), + Invocation.method(#logIngredientToDiary, [ + mealItem, + planId, + dateTime, + ]), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) @@ -344,10 +418,14 @@ class MockNutritionPlansProvider extends _i1.Mock implements _i8.NutritionPlansP ); @override - void dispose() => - super.noSuchMethod(Invocation.method(#dispose, []), returnValueForMissingStub: null); + void dispose() => super.noSuchMethod( + Invocation.method(#dispose, []), + returnValueForMissingStub: null, + ); @override - void notifyListeners() => - super.noSuchMethod(Invocation.method(#notifyListeners, []), returnValueForMissingStub: null); + void notifyListeners() => super.noSuchMethod( + Invocation.method(#notifyListeners, []), + returnValueForMissingStub: null, + ); } From 27c80ed550f91e4e8651179dff43d6240fc4a73b Mon Sep 17 00:00:00 2001 From: lenka369 Date: Tue, 18 Nov 2025 23:34:12 +0100 Subject: [PATCH 5/5] prevent duplicate ingredients in cache --- lib/providers/nutrition.dart | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/lib/providers/nutrition.dart b/lib/providers/nutrition.dart index d1603119..11a82485 100644 --- a/lib/providers/nutrition.dart +++ b/lib/providers/nutrition.dart @@ -306,19 +306,31 @@ class NutritionPlansProvider with ChangeNotifier { Future cacheIngredient(Ingredient ingredient, {IngredientDatabase? database}) async { database ??= this.database; - ingredients.add(ingredient); + if (!ingredients.any((e) => e.id == ingredient.id)) { + ingredients.add(ingredient); + } - final data = ingredient.toJson(); - await database - .into(database.ingredients) - .insert( - IngredientsCompanion.insert( - id: ingredient.id, - data: jsonEncode(data), - lastFetched: DateTime.now(), - ), - ); - _logger.finer("Saved ingredient '${ingredient.name}' to db cache"); + final ingredientDb = await (database.select( + database.ingredients, + )..where((e) => e.id.equals(ingredient.id))).getSingleOrNull(); + + if (ingredientDb == null) { + final data = ingredient.toJson(); + try { + await database + .into(database.ingredients) + .insert( + IngredientsCompanion.insert( + id: ingredient.id, + data: jsonEncode(data), + lastFetched: DateTime.now(), + ), + ); + _logger.finer("Saved ingredient '${ingredient.name}' to db cache"); + } catch (e) { + _logger.finer("Error caching ingredient '${ingredient.name}': $e"); + } + } } /// Fetch and return an ingredient