Skip to content

Commit

Permalink
feat: openfoodfacts#2785 - async access to dao product list (openfood…
Browse files Browse the repository at this point in the history
…facts#2788)

Impacted files:
* `all_user_product_list_page.dart`: split in a not-loaded / loaded version; computes each user list length with a `FutureBuilder
* `continuous_scan_model.dart`: minor refactoring
* `dao_product_list.dart`: switch from "box" to "lazyBox" in order to cleanly make all methods `async`
* `new_product_page.dart`: now loading lists with `FutureBuilder`
* `paged_product_query.dart`: replaced deprecated methods
* `paged_to_be_completed_product_query.dart`: replaced deprecated methods
* `product_list_import_export.dart`: replaced deprecated methods
* `product_list_page.dart`: minor refactoring; replaced deprecated methods
* `product_list_supplier.dart`: minor refactoring
* `product_list_user_dialog_helper.dart`: now explicitly calling `notifyListeners` as `setState` won't be enough because of pre-loaded data
* `pubspec.lock`: generated
* `pubspec.yaml`: upgrade to `openfoodfacts: ^1.24.0`
* `query_product_list_supplier.dart`: minor refactoring
  • Loading branch information
monsieurtanuki authored Aug 16, 2022
1 parent 133071d commit d922511
Show file tree
Hide file tree
Showing 8 changed files with 143 additions and 83 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -249,22 +249,22 @@ class ContinuousScanModel with ChangeNotifier {
) async {
if (_latestFoundBarcode != barcode) {
_latestFoundBarcode = barcode;
_daoProductList.push(productList, _latestFoundBarcode!);
_daoProductList.push(_history, _latestFoundBarcode!);
await _daoProductList.push(productList, _latestFoundBarcode!);
await _daoProductList.push(_history, _latestFoundBarcode!);
_daoProductList.localDatabase.notifyListeners();
}
_setBarcodeState(barcode, state);
}

Future<void> clearScanSession() async {
_daoProductList.clear(productList);
await _daoProductList.clear(productList);
await refresh();
}

Future<void> removeBarcode(
final String barcode,
) async {
_daoProductList.set(
await _daoProductList.set(
productList,
barcode,
false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ abstract class ProductListSupplier {
final PagedProductQuery productQuery,
final LocalDatabase localDatabase,
) async {
final int? timestamp = DaoProductList(localDatabase).getTimestamp(
final int? timestamp = await DaoProductList(localDatabase).getTimestamp(
productQuery.getProductList(),
);
return timestamp == null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class QueryProductListSupplier extends ProductListSupplier {
partialProductList.add(productList);
await DaoProduct(localDatabase).putAll(searchResult.products!);
}
DaoProductList(localDatabase).put(productList);
await DaoProductList(localDatabase).put(productList);
return null;
} catch (e) {
return e.toString();
Expand Down
58 changes: 30 additions & 28 deletions packages/smooth_app/lib/database/dao_product_list.dart
Original file line number Diff line number Diff line change
Expand Up @@ -77,25 +77,26 @@ class DaoProductList extends AbstractDao {
static const String _keySeparator = '::';

@override
Future<void> init() async => Hive.openBox<_BarcodeList>(_hiveBoxName);
Future<void> init() async => Hive.openLazyBox<_BarcodeList>(_hiveBoxName);

@override
void registerAdapter() => Hive.registerAdapter(_BarcodeListAdapter());

Box<_BarcodeList> _getBox() => Hive.box<_BarcodeList>(_hiveBoxName);
LazyBox<_BarcodeList> _getBox() => Hive.lazyBox<_BarcodeList>(_hiveBoxName);

_BarcodeList? _get(final ProductList productList) =>
Future<_BarcodeList?> _get(final ProductList productList) =>
_getBox().get(_getKey(productList));

int? getTimestamp(final ProductList productList) =>
_get(productList)?.timestamp;
Future<int?> getTimestamp(final ProductList productList) async =>
(await _get(productList))?.timestamp;

// Why the "base64" part? Because of #753!
// "HiveError: String keys need to be ASCII Strings with a max length of 255"
// Encoding the parameter part in base64 makes us safe regarding ASCII.
// As it's a list of keywords, there's a fairly high probability
// that we'll be under the 255 character length.
String _getKey(final ProductList productList) => '${productList.listType.key}'
static String _getKey(final ProductList productList) =>
'${productList.listType.key}'
'$_keySeparator'
'${base64.encode(utf8.encode(productList.getParametersKey()))}';

Expand Down Expand Up @@ -125,14 +126,14 @@ class DaoProductList extends AbstractDao {
throw Exception('Unknown product list type: "$value" from "$key"');
}

void _put(final String key, final _BarcodeList barcodeList) =>
Future<void> _put(final String key, final _BarcodeList barcodeList) async =>
_getBox().put(key, barcodeList);

void put(final ProductList productList) =>
Future<void> put(final ProductList productList) async =>
_put(_getKey(productList), _BarcodeList.fromProductList(productList));

Future<bool> delete(final ProductList productList) async {
final Box<_BarcodeList> box = _getBox();
final LazyBox<_BarcodeList> box = _getBox();
final String key = _getKey(productList);
if (!box.containsKey(key)) {
return false;
Expand All @@ -143,7 +144,7 @@ class DaoProductList extends AbstractDao {

/// Loads the barcode list.
Future<void> get(final ProductList productList) async {
final _BarcodeList? list = _get(productList);
final _BarcodeList? list = await _get(productList);
final List<String> barcodes = <String>[];
productList.totalSize = list?.totalSize ?? 0;
if (list == null || list.barcodes.isEmpty) {
Expand All @@ -154,8 +155,8 @@ class DaoProductList extends AbstractDao {
}

/// Returns the number of barcodes quickly but without product check.
int getLength(final ProductList productList) {
final _BarcodeList? list = _get(productList);
Future<int> getLength(final ProductList productList) async {
final _BarcodeList? list = await _get(productList);
if (list == null || list.barcodes.isEmpty) {
return 0;
}
Expand All @@ -167,11 +168,11 @@ class DaoProductList extends AbstractDao {
/// One barcode duplicate is potentially removed:
/// * If the barcode was already there, it's moved to the end of the list.
/// * If the barcode wasn't there, it's added to the end of the list.
void push(
Future<void> push(
final ProductList productList,
final String barcode,
) {
final _BarcodeList? list = _get(productList);
) async {
final _BarcodeList? list = await _get(productList);
final List<String> barcodes;
if (list == null) {
barcodes = <String>[];
Expand All @@ -181,23 +182,23 @@ class DaoProductList extends AbstractDao {
barcodes.remove(barcode); // removes a potential duplicate
barcodes.add(barcode);
final _BarcodeList newList = _BarcodeList.now(barcodes);
_put(_getKey(productList), newList);
await _put(_getKey(productList), newList);
}

void clear(final ProductList productList) {
Future<void> clear(final ProductList productList) async {
final _BarcodeList newList = _BarcodeList.now(<String>[]);
_put(_getKey(productList), newList);
await _put(_getKey(productList), newList);
}

/// Adds or removes a barcode within a product list (depending on [include])
///
/// Returns true if there was a change in the list.
bool set(
Future<bool> set(
final ProductList productList,
final String barcode,
final bool include,
) {
final _BarcodeList? list = _get(productList);
) async {
final _BarcodeList? list = await _get(productList);
final List<String> barcodes;
if (list == null) {
barcodes = <String>[];
Expand All @@ -216,7 +217,7 @@ class DaoProductList extends AbstractDao {
barcodes.add(barcode);
}
final _BarcodeList newList = _BarcodeList.now(barcodes);
_put(_getKey(productList), newList);
await _put(_getKey(productList), newList);
return true;
}

Expand All @@ -225,8 +226,9 @@ class DaoProductList extends AbstractDao {
final String newName,
) async {
final ProductList newList = ProductList.user(newName);
final _BarcodeList list = _get(initialList) ?? _BarcodeList.now(<String>[]);
_put(_getKey(newList), list);
final _BarcodeList list =
await _get(initialList) ?? _BarcodeList.now(<String>[]);
await _put(_getKey(newList), list);
await delete(initialList);
await get(newList);
return newList;
Expand All @@ -235,7 +237,7 @@ class DaoProductList extends AbstractDao {
/// Exports a list - typically for debug purposes
Future<Map<String, dynamic>> export(final ProductList productList) async {
final Map<String, dynamic> result = <String, dynamic>{};
final _BarcodeList? list = _get(productList);
final _BarcodeList? list = await _get(productList);
if (list == null) {
return result;
}
Expand All @@ -256,7 +258,7 @@ class DaoProductList extends AbstractDao {
/// Returns the names of the user lists.
///
/// Possibly restricted to the user lists that contain the given barcode.
List<String> getUserLists({String? withBarcode}) {
Future<List<String>> getUserLists({String? withBarcode}) async {
final List<String> result = <String>[];
for (final dynamic key in _getBox().keys) {
final String tmp = key.toString();
Expand All @@ -265,7 +267,7 @@ class DaoProductList extends AbstractDao {
continue;
}
if (withBarcode != null) {
final _BarcodeList? barcodeList = _getBox().get(key);
final _BarcodeList? barcodeList = await _getBox().get(key);
if (barcodeList == null ||
!barcodeList.barcodes.contains(withBarcode)) {
continue;
Expand Down Expand Up @@ -295,6 +297,6 @@ class DaoProductList extends AbstractDao {
/// List<String> barcodes = _getSafeBarcodeListCopy(_barcodeList.barcodes);
/// barcodes.add('1234'); // no risk at all
/// ```
List<String> _getSafeBarcodeListCopy(final List<String> barcodes) =>
static List<String> _getSafeBarcodeListCopy(final List<String> barcodes) =>
List<String>.from(barcodes);
}
77 changes: 52 additions & 25 deletions packages/smooth_app/lib/pages/all_user_product_list_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,47 @@ import 'package:smooth_app/themes/constant_icons.dart';
import 'package:smooth_app/widgets/smooth_scaffold.dart';

/// Page that lists all user product lists.
class AllUserProductList extends StatefulWidget {
class AllUserProductList extends StatelessWidget {
const AllUserProductList();

@override
State<AllUserProductList> createState() => _AllUserProductListState();
Widget build(BuildContext context) {
final LocalDatabase localDatabase = context.watch<LocalDatabase>();
final DaoProductList daoProductList = DaoProductList(localDatabase);
return FutureBuilder<List<String>>(
future: daoProductList.getUserLists(),
builder: (
final BuildContext context,
final AsyncSnapshot<List<String>> snapshot,
) {
if (snapshot.data != null) {
return _AllUserProductListLoaded(snapshot.data!);
}
return const Center(child: CircularProgressIndicator());
},
);
}
}

/// Page that lists all user product lists, with already loaded data.
class _AllUserProductListLoaded extends StatefulWidget {
const _AllUserProductListLoaded(this.userLists);

final List<String> userLists;

@override
State<_AllUserProductListLoaded> createState() =>
_AllUserProductListLoadedState();
}

class _AllUserProductListState extends State<AllUserProductList> {
class _AllUserProductListLoadedState extends State<_AllUserProductListLoaded> {
@override
Widget build(BuildContext context) {
final LocalDatabase localDatabase = context.watch<LocalDatabase>();
final DaoProductList daoProductList = DaoProductList(localDatabase);
final AppLocalizations appLocalizations = AppLocalizations.of(context);
final ThemeData themeData = Theme.of(context);
final List<String> userLists = daoProductList.getUserLists();
final List<String> userLists = widget.userLists;
return SmoothScaffold(
appBar: AppBar(title: Text(appLocalizations.user_list_all_title)),
body: userLists.isEmpty
Expand Down Expand Up @@ -57,10 +83,22 @@ class _AllUserProductListState extends State<AllUserProductList> {
itemBuilder: (final BuildContext context, final int index) {
final String userList = userLists[index];
final ProductList productList = ProductList.user(userList);
final int length = daoProductList.getLength(productList);
return UserPreferencesListTile(
title: Text(userList),
subtitle: Text(appLocalizations.user_list_length(length)),
subtitle: FutureBuilder<int>(
future: daoProductList.getLength(productList),
builder: (
final BuildContext context,
final AsyncSnapshot<int> snapshot,
) {
if (snapshot.data != null) {
return Text(
appLocalizations.user_list_length(snapshot.data!),
);
}
return EMPTY_WIDGET;
},
),
trailing: Icon(ConstantIcons.instance.getForwardIcon()),
onTap: () async {
await daoProductList.get(productList);
Expand All @@ -76,29 +114,18 @@ class _AllUserProductListState extends State<AllUserProductList> {
);
setState(() {});
},
onLongPress: () async {
final ProductList productList = ProductList.user(userList);
final bool deleted =
await ProductListUserDialogHelper(daoProductList)
.showDeleteUserListDialog(context, productList);
if (!deleted) {
return;
}
setState(() {});
},
onLongPress: () async =>
ProductListUserDialogHelper(daoProductList)
.showDeleteUserListDialog(
context,
ProductList.user(userList),
),
);
},
),
floatingActionButton: FloatingActionButton.extended(
onPressed: () async {
final ProductList? newProductList =
await ProductListUserDialogHelper(daoProductList)
.showCreateUserListDialog(context);
if (newProductList == null) {
return;
}
setState(() {});
},
onPressed: () async => ProductListUserDialogHelper(daoProductList)
.showCreateUserListDialog(context),
label: Row(
children: <Widget>[
const Icon(Icons.add),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ class _ProductListPageState extends State<ProductListPage>
body: Text(appLocalizations.confirm_clear),
positiveAction: SmoothActionButton(
onPressed: () async {
daoProductList.clear(productList);
await daoProductList.clear(productList);
await daoProductList.get(productList);
setState(() {});
if (!mounted) {
Expand Down Expand Up @@ -291,10 +291,11 @@ class _ProductListPageState extends State<ProductListPage>
onDismissed: (final DismissDirection direction) async {
final bool removed = productList.remove(barcode);
if (removed) {
DaoProductList(localDatabase).put(productList);
await DaoProductList(localDatabase).put(productList);
_selectedBarcodes.remove(barcode);
setState(() => barcodes.removeAt(index));
}
//ignore: use_build_context_synchronously
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
Expand Down
Loading

0 comments on commit d922511

Please sign in to comment.