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 all commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ class AttributeCard extends StatelessWidget {
fit: BoxFit.fitHeight,
)
: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Flexible(
child: Text(
Expand Down
101 changes: 0 additions & 101 deletions packages/smooth_app/lib/data_models/choose_page_model.dart

This file was deleted.

198 changes: 75 additions & 123 deletions packages/smooth_app/lib/data_models/continuous_scan_model.dart
Original file line number Diff line number Diff line change
@@ -1,154 +1,106 @@
import 'package:carousel_slider/carousel_controller.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:openfoodfacts/model/Product.dart';
import 'package:qr_code_scanner/qr_code_scanner.dart';
import 'package:smooth_app/cards/product_cards/smooth_product_card_edit.dart';
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';

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();
import 'package:smooth_app/database/barcode_product_query.dart';
import 'package:smooth_app/database/local_database.dart';

enum ScannedProductState {
FOUND,
NOT_FOUND,
LOADING,
THANKS,
}

scannedBarcodes = <String, ScannedProductState>{};
cardTemplates = <String, Widget>{};
class ContinuousScanModel {
ContinuousScanModel({@required bool contributionMode})
: _contributionMode = contributionMode;

final Map<String, ScannedProductState> _states =
<String, ScannedProductState>{};
final Map<String, Product> _products = <String, Product>{};
final List<String> _barcodes = <String>[];
bool _contributionMode;
QRViewController _scannerController;
String _barcodeTrustCheck;
LocalDatabase _localDatabase;

bool get isNotEmpty => getBarcodes().isNotEmpty;
bool get contributionMode => _contributionMode;

List<String> getBarcodes() => _barcodes;

void setBarcodeState(
final String barcode,
final ScannedProductState state,
) {
_states[barcode] = state;
_localDatabase.dummyNotifyListeners();
}

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

Map<String, ScannedProductState> scannedBarcodes;
Map<String, Widget> cardTemplates;

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

String barcodeTrustCheck;
ScannedProductState getBarcodeState(final String barcode) => _states[barcode];

bool contributionMode = false;
Product getProduct(final String barcode) => _products[barcode];

bool firstScan = true;
void setLocalDatabase(final LocalDatabase localDatabase) =>
_localDatabase = localDatabase;

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

void onScan(String code) {
print('Barcode detected : $code');
if (barcodeTrustCheck != code) {
barcodeTrustCheck = code;
return;
}
if (addBarcode(code)) {
_generateScannedProductsCardTemplates();
if (!firstScan) {
carouselController.animateToPage(
cardTemplates.length - 1,
);
} else {
firstScan = false;
List<Product> getFoundProducts() {
final List<Product> result = <Product>[];
for (final String barcode in _barcodes) {
if (getBarcodeState(barcode) == ScannedProductState.FOUND) {
result.add(_products[barcode]);
}
}
return result;
}

void onScanAlt(String code, List<Offset> offsets) {
Future<void> onScan(String code) async {
print('Barcode detected : $code');
if (addBarcode(code)) {
_generateScannedProductsCardTemplates();
if (cardTemplates.isNotEmpty) {
carouselController.animateToPage(
cardTemplates.length - 1,
);
}
if (_barcodeTrustCheck != code) {
_barcodeTrustCheck = code;
return;
}
_addBarcode(code);
}

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

for (final String scannedBarcode in scannedBarcodes.keys) {
switch (scannedBarcodes[scannedBarcode]) {
case ScannedProductState.FOUND:
if (switchMode) {
final Product product = await productsDatabase.getProduct(
scannedBarcode); // Acceptable thanks to offline first
setCardTemplate(
scannedBarcode,
contributionMode
? SmoothProductCardEdit(
heroTag: product.barcode, product: product)
: SmoothProductCardFound(
heroTag: product.barcode, product: product));
}
break;
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(
scannedBarcode,
contributionMode
? SmoothProductCardEdit(
heroTag: product.barcode, product: product)
: SmoothProductCardFound(
heroTag: product.barcode, product: product));
foundProducts.add(product);
} else {
scannedBarcodes[scannedBarcode] = ScannedProductState.NOT_FOUND;
setCardTemplate(
scannedBarcode,
SmoothProductCardNotFound(
barcode: scannedBarcode,
callback: () {
setCardTemplate(scannedBarcode, SmoothProductCardThanks());
},
),
);
}
break;
}
Future<void> onScanAlt(String code, List<Offset> offsets) async {
print('Barcode detected : $code');
_addBarcode(code);
}

void contributionModeSwitch(bool value) {
if (_contributionMode != value) {
_contributionMode = value;
_localDatabase.dummyNotifyListeners();
}
return true;
}

bool addBarcode(String newBarcode) {
if (scannedBarcodes[newBarcode] == null) {
scannedBarcodes[newBarcode] = ScannedProductState.LOADING;
cardTemplates[newBarcode] = SmoothProductCardLoading(barcode: newBarcode);
notifyListeners();
bool _addBarcode(final String barcode) {
if (getBarcodeState(barcode) == null) {
_barcodes.add(barcode);
setBarcodeState(barcode, ScannedProductState.LOADING);
_loadBarcode(barcode);
return true;
}
return false;
}

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

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

void contributionModeSwitch(bool value) {
contributionMode = value;
_generateScannedProductsCardTemplates(switchMode: true);
notifyListeners();
Future<void> _loadBarcode(final String barcode) async {
final Product product = await BarcodeProductQuery(barcode).getProduct();
if (product != null) {
_localDatabase.putProduct(product);
_products[barcode] = product;
setBarcodeState(barcode, ScannedProductState.FOUND);
} else {
setBarcodeState(barcode, ScannedProductState.NOT_FOUND);
}
}
}
2 changes: 1 addition & 1 deletion packages/smooth_app/lib/data_models/match.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class Match {
_debug += '$variable $value\n';
} else {
if (attribute.status == _UNKNOWN_STATUS) {
if (_status) {
if (_status ?? false) {
_status = null;
}
} else {
Expand Down
Loading