chore: Migration to Dart 3.8 (#6668)

* Migration to Dart 3.8

* New GA

* Fix dartdoc
This commit is contained in:
Edouard Marquez
2025-06-23 18:14:17 +02:00
committed by GitHub
parent dda75e283e
commit e3bc40fdf3
628 changed files with 18834 additions and 17076 deletions

View File

@ -36,7 +36,8 @@ class BulkManager {
}
if (parameters.length % numCols != 0) {
throw Exception(
'Parameter list size (${parameters.length}) cannot be divided by $numCols');
'Parameter list size (${parameters.length}) cannot be divided by $numCols',
);
}
final String variables = '?${',?' * (columnNames.length - 1)}';
final int maxSlice = (SQLITE_MAX_VARIABLE_NUMBER ~/ numCols) * numCols;

View File

@ -46,30 +46,32 @@ class DaoOsmLocation extends AbstractSqlDao {
final int newVersion,
) async {
if (oldVersion < 6) {
await db.execute('create table $_table('
' $_columnId INT NOT NULL'
',$_columnType TEXT NOT NULL'
',$_columnLongitude REAL NOT NULL'
',$_columnLatitude REAL NOT NULL'
',$_columnName TEXT'
',$_columnStreet TEXT'
',$_columnCity TEXT'
',$_columnPostCode TEXT'
',$_columnCountry TEXT'
',$_columnCountryCode TEXT'
',$_columnLastAccess INT NOT NULL'
// cf. https://www.sqlite.org/lang_conflict.html
',PRIMARY KEY($_columnId,$_columnType) on conflict replace'
')');
await db.execute(
'create table $_table('
' $_columnId INT NOT NULL'
',$_columnType TEXT NOT NULL'
',$_columnLongitude REAL NOT NULL'
',$_columnLatitude REAL NOT NULL'
',$_columnName TEXT'
',$_columnStreet TEXT'
',$_columnCity TEXT'
',$_columnPostCode TEXT'
',$_columnCountry TEXT'
',$_columnCountryCode TEXT'
',$_columnLastAccess INT NOT NULL'
// cf. https://www.sqlite.org/lang_conflict.html
',PRIMARY KEY($_columnId,$_columnType) on conflict replace'
')',
);
}
/// Not brilliant, but for historical reasons we have to catch that here.
bool isDuplicateColumnException(
final DatabaseException e,
final String column,
) =>
e.toString().startsWith(
'DatabaseException(duplicate column name: $column (code 1 SQLITE_ERROR');
) => e.toString().startsWith(
'DatabaseException(duplicate column name: $column (code 1 SQLITE_ERROR',
);
if (oldVersion < 7) {
try {
@ -80,8 +82,9 @@ class DaoOsmLocation extends AbstractSqlDao {
}
}
try {
await db
.execute('alter table $_table add column $_columnOsmValue TEXT');
await db.execute(
'alter table $_table add column $_columnOsmValue TEXT',
);
} on DatabaseException catch (e) {
if (!isDuplicateColumnException(e, _columnOsmValue)) {
rethrow;
@ -101,12 +104,8 @@ class DaoOsmLocation extends AbstractSqlDao {
/// Returns all the [OsmLocation]s, ordered by descending last access.
Future<List<OsmLocation>> getAll() async {
final List<OsmLocation> result = <OsmLocation>[];
final List<Map<String, dynamic>> queryResults =
await localDatabase.database.query(
_table,
columns: _columns,
orderBy: '$_columnLastAccess DESC',
);
final List<Map<String, dynamic>> queryResults = await localDatabase.database
.query(_table, columns: _columns, orderBy: '$_columnLastAccess DESC');
for (final Map<String, dynamic> row in queryResults) {
final OsmLocation? item = _getItemFromQueryResult(row);
if (item != null) {
@ -117,28 +116,26 @@ class DaoOsmLocation extends AbstractSqlDao {
}
Future<int> put(final OsmLocation osmLocation) async =>
localDatabase.database.insert(
_table,
<String, Object?>{
_columnId: osmLocation.osmId,
_columnType: osmLocation.osmType.offTag,
_columnLongitude: osmLocation.longitude,
_columnLatitude: osmLocation.latitude,
_columnName: osmLocation.name,
_columnStreet: osmLocation.street,
_columnCity: osmLocation.city,
_columnPostCode: osmLocation.postcode,
_columnCountry: osmLocation.country,
_columnCountryCode: osmLocation.countryCode,
_columnOsmKey: osmLocation.osmKey,
_columnOsmValue: osmLocation.osmValue,
_columnLastAccess: LocalDatabase.nowInMillis(),
},
);
localDatabase.database.insert(_table, <String, Object?>{
_columnId: osmLocation.osmId,
_columnType: osmLocation.osmType.offTag,
_columnLongitude: osmLocation.longitude,
_columnLatitude: osmLocation.latitude,
_columnName: osmLocation.name,
_columnStreet: osmLocation.street,
_columnCity: osmLocation.city,
_columnPostCode: osmLocation.postcode,
_columnCountry: osmLocation.country,
_columnCountryCode: osmLocation.countryCode,
_columnOsmKey: osmLocation.osmKey,
_columnOsmValue: osmLocation.osmValue,
_columnLastAccess: LocalDatabase.nowInMillis(),
});
OsmLocation? _getItemFromQueryResult(final Map<String, dynamic> row) {
final LocationOSMType? type =
LocationOSMType.fromOffTag(row[_columnType] as String);
final LocationOSMType? type = LocationOSMType.fromOffTag(
row[_columnType] as String,
);
if (type == null) {
// very very unlikely
return null;

View File

@ -35,28 +35,32 @@ class DaoProduct extends AbstractSqlDao implements BulkDeletable {
final int newVersion,
) async {
if (oldVersion < 2) {
await db.execute('create table $_TABLE_PRODUCT('
// cf. https://www.sqlite.org/lang_conflict.html
'$_TABLE_PRODUCT_COLUMN_BARCODE TEXT PRIMARY KEY on conflict replace'
',$_TABLE_PRODUCT_COLUMN_GZIPPED_JSON BLOB NOT NULL'
',$_TABLE_PRODUCT_COLUMN_LAST_UPDATE INT NOT NULL'
')');
await db.execute(
'create table $_TABLE_PRODUCT('
// cf. https://www.sqlite.org/lang_conflict.html
'$_TABLE_PRODUCT_COLUMN_BARCODE TEXT PRIMARY KEY on conflict replace'
',$_TABLE_PRODUCT_COLUMN_GZIPPED_JSON BLOB NOT NULL'
',$_TABLE_PRODUCT_COLUMN_LAST_UPDATE INT NOT NULL'
')',
);
}
if (oldVersion < 4) {
await db.execute('alter table $_TABLE_PRODUCT add column '
'$_TABLE_PRODUCT_COLUMN_LANGUAGE TEXT');
await db.execute(
'alter table $_TABLE_PRODUCT add column '
'$_TABLE_PRODUCT_COLUMN_LANGUAGE TEXT',
);
}
}
/// Returns the [Product] that matches the [barcode], or null.
Future<Product?> get(final String barcode) async {
final List<Map<String, dynamic>> queryResults =
await localDatabase.database.query(
_TABLE_PRODUCT,
columns: _columns,
where: '$_TABLE_PRODUCT_COLUMN_BARCODE = ?',
whereArgs: <String>[barcode],
);
final List<Map<String, dynamic>> queryResults = await localDatabase.database
.query(
_TABLE_PRODUCT,
columns: _columns,
where: '$_TABLE_PRODUCT_COLUMN_BARCODE = ?',
whereArgs: <String>[barcode],
);
// O or 1 row expected
for (final Map<String, dynamic> row in queryResults) {
return _getProductFromQueryResult(row);
@ -70,20 +74,23 @@ class DaoProduct extends AbstractSqlDao implements BulkDeletable {
if (barcodes.isEmpty) {
return result;
}
for (int start = 0;
start < barcodes.length;
start += BulkManager.SQLITE_MAX_VARIABLE_NUMBER) {
for (
int start = 0;
start < barcodes.length;
start += BulkManager.SQLITE_MAX_VARIABLE_NUMBER
) {
final int size = min(
barcodes.length - start,
BulkManager.SQLITE_MAX_VARIABLE_NUMBER,
);
final List<Map<String, dynamic>> queryResults =
await localDatabase.database.query(
_TABLE_PRODUCT,
columns: _columns,
where: '$_TABLE_PRODUCT_COLUMN_BARCODE in(? ${',?' * (size - 1)})',
whereArgs: barcodes.sublist(start, start + size),
);
final List<Map<String, dynamic>> queryResults = await localDatabase
.database
.query(
_TABLE_PRODUCT,
columns: _columns,
where: '$_TABLE_PRODUCT_COLUMN_BARCODE in(? ${',?' * (size - 1)})',
whereArgs: barcodes.sublist(start, start + size),
);
for (final Map<String, dynamic> row in queryResults) {
result[row[_TABLE_PRODUCT_COLUMN_BARCODE] as String] =
_getProductFromQueryResult(row);
@ -100,20 +107,23 @@ class DaoProduct extends AbstractSqlDao implements BulkDeletable {
if (barcodes.isEmpty) {
return result;
}
for (int start = 0;
start < barcodes.length;
start += BulkManager.SQLITE_MAX_VARIABLE_NUMBER) {
for (
int start = 0;
start < barcodes.length;
start += BulkManager.SQLITE_MAX_VARIABLE_NUMBER
) {
final int size = min(
barcodes.length - start,
BulkManager.SQLITE_MAX_VARIABLE_NUMBER,
);
final List<Map<String, dynamic>> queryResults =
await localDatabase.database.query(
_TABLE_PRODUCT,
columns: _columns,
where: '$_TABLE_PRODUCT_COLUMN_BARCODE in(? ${',?' * (size - 1)})',
whereArgs: barcodes.sublist(start, start + size),
);
final List<Map<String, dynamic>> queryResults = await localDatabase
.database
.query(
_TABLE_PRODUCT,
columns: _columns,
where: '$_TABLE_PRODUCT_COLUMN_BARCODE in(? ${',?' * (size - 1)})',
whereArgs: barcodes.sublist(start, start + size),
);
for (final Map<String, dynamic> row in queryResults) {
final Product product = _getProductFromQueryResult(row);
final ProductType productType = product.productType ?? ProductType.food;
@ -133,11 +143,8 @@ class DaoProduct extends AbstractSqlDao implements BulkDeletable {
final String Function(Product) splitFunction,
) async {
final Map<String, List<String>> result = <String, List<String>>{};
final List<Map<String, dynamic>> queryResults =
await localDatabase.database.query(
_TABLE_PRODUCT,
columns: _columns,
);
final List<Map<String, dynamic>> queryResults = await localDatabase.database
.query(_TABLE_PRODUCT, columns: _columns);
for (final Map<String, dynamic> row in queryResults) {
final Product product = _getProductFromQueryResult(row);
final String splitValue = splitFunction(product);
@ -155,12 +162,7 @@ class DaoProduct extends AbstractSqlDao implements BulkDeletable {
final Product product,
final OpenFoodFactsLanguage language, {
required final ProductType productType,
}) async =>
putAll(
<Product>[product],
language,
productType: productType,
);
}) async => putAll(<Product>[product], language, productType: productType);
/// Replaces products in database
Future<void> putAll(
@ -174,23 +176,18 @@ class DaoProduct extends AbstractSqlDao implements BulkDeletable {
product.productType ??= productType;
}
await localDatabase.database.transaction(
(final Transaction transaction) async => _bulkReplaceLoop(
transaction,
products,
language,
),
(final Transaction transaction) async =>
_bulkReplaceLoop(transaction, products, language),
);
}
Future<List<String>> getAllKeys() async {
final List<String> result = <String>[];
final List<Map<String, dynamic>> queryResults =
await localDatabase.database.query(
_TABLE_PRODUCT,
columns: <String>[
_TABLE_PRODUCT_COLUMN_BARCODE,
],
);
final List<Map<String, dynamic>> queryResults = await localDatabase.database
.query(
_TABLE_PRODUCT,
columns: <String>[_TABLE_PRODUCT_COLUMN_BARCODE],
);
if (queryResults.isEmpty) {
return result;
}
@ -271,19 +268,20 @@ class DaoProduct extends AbstractSqlDao implements BulkDeletable {
if (!verbose) {
return;
}
final List<Map<String, dynamic>> queryResults =
await localDatabase.database.rawQuery(
'select'
' $_TABLE_PRODUCT_COLUMN_BARCODE'
', length($_TABLE_PRODUCT_COLUMN_GZIPPED_JSON) as mylength'
' from $_TABLE_PRODUCT',
);
final List<Map<String, dynamic>> queryResults = await localDatabase.database
.rawQuery(
'select'
' $_TABLE_PRODUCT_COLUMN_BARCODE'
', length($_TABLE_PRODUCT_COLUMN_GZIPPED_JSON) as mylength'
' from $_TABLE_PRODUCT',
);
debugPrint('Product by product');
debugPrint('barcode;gzipped;string;factor');
for (final Map<String, dynamic> row in queryResults) {
final String barcode = row[_TABLE_PRODUCT_COLUMN_BARCODE] as String;
final int asString =
utf8.encode(jsonEncode(map[barcode]!.toJson())).length;
final int asString = utf8
.encode(jsonEncode(map[barcode]!.toJson()))
.length;
final int asZipped = row['mylength'] as int;
final double factor = (asString * 1.0) / asZipped;
debugPrint('$barcode;$asZipped;$asString;$factor');
@ -293,11 +291,8 @@ class DaoProduct extends AbstractSqlDao implements BulkDeletable {
/// Get the total number of products in the database
Future<Map<ProductType, int>> getTotalNoOfProducts() async {
final Map<ProductType, int> result = <ProductType, int>{};
final List<Map<String, dynamic>> queryResults =
await localDatabase.database.query(
_TABLE_PRODUCT,
columns: _columns,
);
final List<Map<String, dynamic>> queryResults = await localDatabase.database
.query(_TABLE_PRODUCT, columns: _columns);
for (final Map<String, dynamic> row in queryResults) {
final Product product = _getProductFromQueryResult(row);
final ProductType productType = product.productType ?? ProductType.food;
@ -349,7 +344,8 @@ class DaoProduct extends AbstractSqlDao implements BulkDeletable {
const String tableJoin =
'p.$_TABLE_PRODUCT_COLUMN_BARCODE = a.${DaoProductLastAccess.COLUMN_BARCODE}';
final String languageCondition = ' ('
final String languageCondition =
' ('
'p.$_TABLE_PRODUCT_COLUMN_LANGUAGE is null '
"or p.$_TABLE_PRODUCT_COLUMN_LANGUAGE != '${language.offTag}'"
') ';
@ -385,11 +381,8 @@ class DaoProduct extends AbstractSqlDao implements BulkDeletable {
for (final String query in queries) {
// optimization: using a cursor, as we don't want all the rows,
// and we don't know how many rows we'll need.
final QueryCursor queryCursor =
await localDatabase.database.rawQueryCursor(
query,
null,
);
final QueryCursor queryCursor = await localDatabase.database
.rawQueryCursor(query, null);
while (await queryCursor.moveNext()) {
final Product product = _getProductFromQueryResult(queryCursor.current);
final String barcode = product.barcode!;
@ -416,7 +409,7 @@ class DaoProduct extends AbstractSqlDao implements BulkDeletable {
/// with a language null or different from the current app language", and use
/// the same mechanism as "switch language and refresh products accordingly".
Future<int> clearAllLanguages() async => localDatabase.database.update(
_TABLE_PRODUCT,
<String, Object?>{_TABLE_PRODUCT_COLUMN_LANGUAGE: null},
);
_TABLE_PRODUCT,
<String, Object?>{_TABLE_PRODUCT_COLUMN_LANGUAGE: null},
);
}

View File

@ -17,11 +17,13 @@ class DaoProductLastAccess extends AbstractSqlDao {
final int newVersion,
) async {
if (oldVersion < 5) {
await db.execute('create table $TABLE('
// cf. https://www.sqlite.org/lang_conflict.html
'$COLUMN_BARCODE TEXT PRIMARY KEY on conflict replace'
',$COLUMN_LAST_ACCESS INT NOT NULL'
')');
await db.execute(
'create table $TABLE('
// cf. https://www.sqlite.org/lang_conflict.html
'$COLUMN_BARCODE TEXT PRIMARY KEY on conflict replace'
',$COLUMN_LAST_ACCESS INT NOT NULL'
')',
);
}
}
@ -29,10 +31,7 @@ class DaoProductLastAccess extends AbstractSqlDao {
localDatabase.database.rawInsert(
'insert into $TABLE($COLUMN_BARCODE, $COLUMN_LAST_ACCESS) '
'values(?, ?)',
<Object>[
barcode,
LocalDatabase.nowInMillis(),
],
<Object>[barcode, LocalDatabase.nowInMillis()],
);
/// Delete all items from the database

View File

@ -13,25 +13,17 @@ const int _uselessTotalSizeValue = 0;
/// An immutable barcode list; e.g. my search yesterday about "Nutella"
class _BarcodeList {
const _BarcodeList(
this.timestamp,
this.barcodes,
this.totalSize,
);
const _BarcodeList(this.timestamp, this.barcodes, this.totalSize);
_BarcodeList.now(final List<String> barcodes)
: this(
LocalDatabase.nowInMillis(),
barcodes,
_uselessTotalSizeValue,
);
: this(LocalDatabase.nowInMillis(), barcodes, _uselessTotalSizeValue);
_BarcodeList.fromProductList(final ProductList productList)
: this(
LocalDatabase.nowInMillis(),
productList.barcodes,
productList.totalSize,
);
: this(
LocalDatabase.nowInMillis(),
productList.barcodes,
productList.totalSize,
);
/// Freshness indicator: last time the list was updated.
///
@ -186,10 +178,7 @@ 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.
Future<void> push(
final ProductList productList,
final String barcode,
) async {
Future<void> push(final ProductList productList, final String barcode) async {
final _BarcodeList? list = await _get(productList);
final List<String> barcodes;
if (list == null) {

View File

@ -23,24 +23,26 @@ class DaoWorkBarcode extends AbstractSqlDao {
final int newVersion,
) async {
if (oldVersion < 3) {
await db.execute('create table $_table('
'$_columnWork TEXT NOT NULL'
',$_columnBarcode TEXT NOT NULL'
// cf. https://www.sqlite.org/lang_conflict.html
',PRIMARY KEY($_columnWork,$_columnBarcode) on conflict replace'
')');
await db.execute(
'create table $_table('
'$_columnWork TEXT NOT NULL'
',$_columnBarcode TEXT NOT NULL'
// cf. https://www.sqlite.org/lang_conflict.html
',PRIMARY KEY($_columnWork,$_columnBarcode) on conflict replace'
')',
);
}
}
/// Returns the number of barcodes for that work.
Future<int> getCount(final String work) async {
final List<Map<String, dynamic>> queryResults =
await localDatabase.database.query(
_table,
columns: <String>['count(1) as my_count'],
where: '$_columnWork = ?',
whereArgs: <String>[work],
);
final List<Map<String, dynamic>> queryResults = await localDatabase.database
.query(
_table,
columns: <String>['count(1) as my_count'],
where: '$_columnWork = ?',
whereArgs: <String>[work],
);
for (final Map<String, dynamic> row in queryResults) {
return row['my_count'] as int;
}
@ -53,14 +55,14 @@ class DaoWorkBarcode extends AbstractSqlDao {
final int pageSize,
) async {
final List<String> result = <String>[];
final List<Map<String, dynamic>> queryResults =
await localDatabase.database.query(
_table,
columns: <String>[_columnBarcode],
where: '$_columnWork = ?',
whereArgs: <String>[work],
limit: pageSize,
);
final List<Map<String, dynamic>> queryResults = await localDatabase.database
.query(
_table,
columns: <String>[_columnBarcode],
where: '$_columnWork = ?',
whereArgs: <String>[work],
limit: pageSize,
);
for (final Map<String, dynamic> row in queryResults) {
result.add(row[_columnBarcode] as String);
}
@ -71,11 +73,10 @@ class DaoWorkBarcode extends AbstractSqlDao {
Future<int> putAll(
final String work,
final Iterable<String> barcodes,
) async =>
localDatabase.database.transaction(
(final Transaction transaction) async =>
_bulkInsert(transaction, work, barcodes),
);
) async => localDatabase.database.transaction(
(final Transaction transaction) async =>
_bulkInsert(transaction, work, barcodes),
);
/// Returns the number of inserted rows by optimized bulk insert.
Future<int> _bulkInsert(
@ -117,12 +118,8 @@ class DaoWorkBarcode extends AbstractSqlDao {
/// Deletes all barcodes for a given work.
///
/// Returns the number of rows deleted.
Future<int> deleteWork(final String work) async =>
localDatabase.database.delete(
_table,
where: '$_columnWork = ?',
whereArgs: <String>[work],
);
Future<int> deleteWork(final String work) async => localDatabase.database
.delete(_table, where: '$_columnWork = ?', whereArgs: <String>[work]);
/// Deletes all barcodes for a given work.
///
@ -130,11 +127,10 @@ class DaoWorkBarcode extends AbstractSqlDao {
Future<int> deleteBarcodes(
final String work,
final Iterable<String> barcodes,
) async =>
localDatabase.database.transaction(
(final Transaction transaction) async =>
_bulkDelete(transaction, work, barcodes),
);
) async => localDatabase.database.transaction(
(final Transaction transaction) async =>
_bulkDelete(transaction, work, barcodes),
);
/// Returns the number of deleted rows by optimized bulk delete.
Future<int> _bulkDelete(
@ -154,7 +150,8 @@ class DaoWorkBarcode extends AbstractSqlDao {
}
final int deleted = await databaseExecutor.delete(
_table,
where: '$_columnWork = ? '
where:
'$_columnWork = ? '
'and $_columnBarcode in(?${',?' * (parameters.length - 2)})',
whereArgs: parameters,
);

View File

@ -22,8 +22,8 @@ class TransientFile {
this.barcode,
this.language, [
this.uploadedDate,
]) : imageField = productImageData.imageField,
url = productImageData.imageUrl;
]) : imageField = productImageData.imageField,
url = productImageData.imageUrl;
factory TransientFile.fromProduct(
final Product product,
@ -60,18 +60,13 @@ class TransientFile {
static final Map<String, String> _transientFiles = <String, String>{};
/// Stores locally [file] as a transient image for [imageField] and [barcode].
void putImage(
final LocalDatabase localDatabase,
final File file,
) {
void putImage(final LocalDatabase localDatabase, final File file) {
_transientFiles[_getImageKey()] = file.path;
localDatabase.notifyListeners();
}
/// Removes the current transient image for [imageField] and [barcode].
void removeImage(
final LocalDatabase localDatabase,
) {
void removeImage(final LocalDatabase localDatabase) {
_transientFiles.remove(_getImageKey());
localDatabase.notifyListeners();
}
@ -93,8 +88,7 @@ class TransientFile {
static String _getImageKeyPrefix(
final ImageField imageField,
final String barcode,
) =>
'$barcode;$imageField;';
) => '$barcode;$imageField;';
/// Returns a way to display the image, either locally or from the server.
ImageProvider? getImageProvider() {