Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature/#55 - SQLite local database, and refactoring with StatefulWidget's #62

Merged
merged 7 commits into from
Jan 8, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/smooth_app/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ android {
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "org.openfoodfacts.app"
minSdkVersion 24
minSdkVersion 19
targetSdkVersion 29
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
Expand Down
101 changes: 0 additions & 101 deletions packages/smooth_app/lib/data_models/choose_page_model.dart

This file was deleted.

126 changes: 59 additions & 67 deletions packages/smooth_app/lib/data_models/continuous_scan_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,62 +8,65 @@ import 'package:smooth_app/cards/product_cards/smooth_product_card_found.dart';
import 'package:smooth_app/cards/product_cards/smooth_product_card_not_found.dart';
import 'package:smooth_app/cards/product_cards/smooth_product_card_loading.dart';
import 'package:smooth_app/cards/product_cards/smooth_product_card_thanks.dart';
import 'package:smooth_app/database/full_products_database.dart';
import 'package:smooth_app/database/barcode_product_query.dart';
import 'package:smooth_app/database/local_database.dart';

enum ScannedProductState { FOUND, NOT_FOUND, LOADING }

class ContinuousScanModel extends ChangeNotifier {
Copy link
Member

@PrimaelQuemerais PrimaelQuemerais Jan 1, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a valid reason to get rid of the ChangeNotifier ?
We are using Provider as State Management and the StatefulWidgets should be avoided (Now that I think about it I shouldn't have accepted the new Profile page as it doesn't follow this guideline).

Provider works by separating the UI from the logic, every Widget can be stateless and the class that extends ChangeNotifier holds all the variables, calling notifyListener() tells the Widget to rebuild.

Sorry that this wasn't specified anywhere on the project, I will update the guidelines.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand your point about the ChangeNotifier vs. StatefulWidget.
That being said, the code before I refactored it was really painful to read, with Consumers here and there.
My idea is to have most of the relevant data in the Providers that are already in the main.dart - in that case we could notify each time the scan list is updated as a product list stored in the database, for instance.
Interesting point, to be investigated.
Is there a link for the guidelines?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that the use of Consumers is quite heavy but I also fear that mixing ChangeNotifiers and StatefulWidgets can become confusing. Perhaps we should determine a clear rule on when Provider or StatefulWidgets should be used.
Having the data at a higher levels such as main.dart sounds good to me, I don't really see how that will reduce the use of Consumers though.

There is actually no guidelines at the moment, only the technical document you already saw for the choice of database. I will start working on guidelines and documentation for future contributors.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't really see how that will reduce the use of Consumers though.

That's the beauty of Providers: if I start my build(BuildContext context) with a context.watch<LocalDatabase>(), every time the notify listener method is called on LocalDatabase, the Widget is rebuilt. And I don't need any Consumer.

Don't put too much effort in the guidelines for the moment; I think we're still in an early stage, with many open questions. I guess a simple README would do the job in first approach.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh indeed that's really clever!
Should we split this PR by keeping only the database part, or do you think you can update it to support this new data structure?

I will take some note and make a small file with the essential guidelines

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not want to change the screens in the first place, but I had too, because of the impact of the change of database.
If you give me a couple of days I think I can do something nice with ContinuousScanPage, in this PR. That will give us food for thought, and perhaps ideas of templates / for your guidelines.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand, that would be great. Once we have this page working we can transition the rest of the app to use the same principle and the document it (or probably the other way around).
I will wait for your update and we can discuss it then, let me know if you need any help.

ContinuousScanModel({@required this.contributionMode}) {
carouselController = CarouselController();
class ContinuousScanModel {
ContinuousScanModel({@required this.contributionMode})
: carouselController = CarouselController();

scannedBarcodes = <String, ScannedProductState>{};
cardTemplates = <String, Widget>{};
}

final GlobalKey scannerViewKey = GlobalKey(debugLabel: 'Barcode Scanner');
QRViewController scannerController;
CarouselController carouselController;
QRViewController _scannerController;
final CarouselController carouselController;

Map<String, ScannedProductState> scannedBarcodes;
Map<String, Widget> cardTemplates;
final Map<String, ScannedProductState> _scannedBarcodes =
<String, ScannedProductState>{};
final Map<String, Widget> cardTemplates = <String, Widget>{};

List<Product> foundProducts = <Product>[];

String barcodeTrustCheck;
String _barcodeTrustCheck;

bool contributionMode;

bool contributionMode = false;
bool _firstScan = true;

bool firstScan = true;
LocalDatabase _localDatabase;

void setupScanner(QRViewController controller) {
scannerController = controller;
scannerController.scannedDataStream.listen((String barcode) {
onScan(barcode);
});
void setLocalDatabase(final LocalDatabase localDatabase) =>
_localDatabase = localDatabase;

void setupScanner(QRViewController controller, final Function setState) {
_scannerController = controller;
_scannerController.scannedDataStream.listen(
(String barcode) => onScan(barcode, setState),
);
}

void onScan(String code) {
Future<void> onScan(String code, Function setState) async {
print('Barcode detected : $code');
if (barcodeTrustCheck != code) {
barcodeTrustCheck = code;
if (_barcodeTrustCheck != code) {
_barcodeTrustCheck = code;
return;
}
if (addBarcode(code)) {
_generateScannedProductsCardTemplates();
if (!firstScan) {
carouselController.animateToPage(
cardTemplates.length - 1,
);
if (_addBarcode(code)) {
await _generateScannedProductsCardTemplates();
setState(() {});
if (!_firstScan) {
carouselController.animateToPage(cardTemplates.length - 1);
} else {
firstScan = false;
_firstScan = false;
}
}
}

void onScanAlt(String code, List<Offset> offsets) {
Future<void> onScanAlt(
String code, List<Offset> offsets, final Function setState) async {
print('Barcode detected : $code');
if (addBarcode(code)) {
_generateScannedProductsCardTemplates();
if (_addBarcode(code)) {
await _generateScannedProductsCardTemplates();
setState(() {});
if (cardTemplates.isNotEmpty) {
carouselController.animateToPage(
cardTemplates.length - 1,
Expand All @@ -74,15 +77,13 @@ class ContinuousScanModel extends ChangeNotifier {

Future<bool> _generateScannedProductsCardTemplates(
{bool switchMode = false}) async {
final FullProductsDatabase productsDatabase = FullProductsDatabase();

for (final String scannedBarcode in scannedBarcodes.keys) {
switch (scannedBarcodes[scannedBarcode]) {
for (final String scannedBarcode in _scannedBarcodes.keys) {
switch (_scannedBarcodes[scannedBarcode]) {
case ScannedProductState.FOUND:
if (switchMode) {
final Product product = await productsDatabase.getProduct(
final Product product = await _localDatabase.getProduct(
scannedBarcode); // Acceptable thanks to offline first
setCardTemplate(
_setCardTemplate(
scannedBarcode,
contributionMode
? SmoothProductCardEdit(
Expand All @@ -94,13 +95,12 @@ class ContinuousScanModel extends ChangeNotifier {
case ScannedProductState.NOT_FOUND:
break;
case ScannedProductState.LOADING:
final bool result =
await productsDatabase.checkAndFetchProduct(scannedBarcode);
if (result) {
scannedBarcodes[scannedBarcode] = ScannedProductState.FOUND;
final Product product =
await productsDatabase.getProduct(scannedBarcode);
setCardTemplate(
final Product product =
await BarcodeProductQuery(scannedBarcode).getProduct();
if (product != null) {
_scannedBarcodes[scannedBarcode] = ScannedProductState.FOUND;
await _localDatabase.putProduct(product);
_setCardTemplate(
scannedBarcode,
contributionMode
? SmoothProductCardEdit(
Expand All @@ -109,14 +109,13 @@ class ContinuousScanModel extends ChangeNotifier {
heroTag: product.barcode, product: product));
foundProducts.add(product);
} else {
scannedBarcodes[scannedBarcode] = ScannedProductState.NOT_FOUND;
setCardTemplate(
_scannedBarcodes[scannedBarcode] = ScannedProductState.NOT_FOUND;
_setCardTemplate(
scannedBarcode,
SmoothProductCardNotFound(
barcode: scannedBarcode,
callback: () {
setCardTemplate(scannedBarcode, SmoothProductCardThanks());
},
callback: () =>
_setCardTemplate(scannedBarcode, SmoothProductCardThanks()),
),
);
}
Expand All @@ -126,29 +125,22 @@ class ContinuousScanModel extends ChangeNotifier {
return true;
}

bool addBarcode(String newBarcode) {
if (scannedBarcodes[newBarcode] == null) {
scannedBarcodes[newBarcode] = ScannedProductState.LOADING;
cardTemplates[newBarcode] = SmoothProductCardLoading(barcode: newBarcode);
notifyListeners();
bool _addBarcode(String newBarcode) {
if (_scannedBarcodes[newBarcode] == null) {
_scannedBarcodes[newBarcode] = ScannedProductState.LOADING;
_setCardTemplate(
newBarcode, SmoothProductCardLoading(barcode: newBarcode));
return true;
}
return false;
}

void setProductState(String barcode, ScannedProductState state) {
scannedBarcodes[barcode] = state;
notifyListeners();
}

void setCardTemplate(String barcode, Widget cardTemplate) {
void _setCardTemplate(String barcode, Widget cardTemplate) {
cardTemplates[barcode] = cardTemplate;
notifyListeners();
}

void contributionModeSwitch(bool value) {
Future<void> contributionModeSwitch(bool value) async {
contributionMode = value;
_generateScannedProductsCardTemplates(switchMode: true);
notifyListeners();
await _generateScannedProductsCardTemplates(switchMode: true);
}
}
7 changes: 6 additions & 1 deletion packages/smooth_app/lib/data_models/product_query_model.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import 'package:openfoodfacts/model/Product.dart';
import 'package:smooth_app/data_models/user_preferences_model.dart';
import 'package:smooth_app/data_models/match.dart';
import 'package:smooth_app/database/local_database.dart';
import 'package:openfoodfacts/model/SearchResult.dart';
import 'package:smooth_app/database/product_query.dart';
import 'package:smooth_app/temp/user_preferences.dart';

Expand All @@ -20,12 +22,15 @@ class ProductQueryModel {
final ProductQuery productQuery,
final UserPreferences userPreferences,
final UserPreferencesModel userPreferencesModel,
final LocalDatabase localDatabase,
) async {
if (_products != null) {
return true;
}

_products = await productQuery.queryProducts();
final SearchResult searchResult = await productQuery.getSearchResult();
_products = searchResult.products;
localDatabase.putProducts(_products);
Match.sort(_products, userPreferences, userPreferencesModel);

displayProducts = _products;
Expand Down
27 changes: 27 additions & 0 deletions packages/smooth_app/lib/database/barcode_product_query.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import 'dart:async';

import 'package:openfoodfacts/openfoodfacts.dart';
import 'package:smooth_app/database/product_query.dart';
import 'package:openfoodfacts/utils/LanguageHelper.dart';

class BarcodeProductQuery {
BarcodeProductQuery(this.barcode);

final String barcode;

Future<Product> getProduct() async {
final ProductQueryConfiguration configuration = ProductQueryConfiguration(
barcode,
fields: ProductQuery.fields,
language: OpenFoodFactsLanguage.ENGLISH,
);

final ProductResult result =
await OpenFoodAPIClient.getProduct(configuration);

if (result.status == 1) {
return result.product;
}
return null;
}
}
Loading