diff --git a/CHANGELOG.md b/CHANGELOG.md index ea046a0..5646f5a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## [1.0.1+1] 07/05/2021 +* Fixes [#37] "Operand of null-aware operation '!' has type 'String' which excludes null." +* Switches to strong-mode which includes no implicit casts and no implicit-dynamics. +* Removes several force unwraps (!) thus handling nullable values better +* Reference is no longer a field on a map and is thus constructed beforehand using a path + ## [1.0.1] 12/03/2021 * Formatted code according to `dartfmt` diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..b0045a1 --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,6 @@ +include: package:pedantic/analysis_options.yaml + +analyzer: + strong-mode: + implicit-casts: false + implicit-dynamic: false \ No newline at end of file diff --git a/lib/src/cache_manager.dart b/lib/src/cache_manager.dart index 7c3a0e3..2f5e957 100644 --- a/lib/src/cache_manager.dart +++ b/lib/src/cache_manager.dart @@ -1,13 +1,13 @@ import 'dart:io'; import 'dart:typed_data'; -import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_image/firebase_image.dart'; import 'package:firebase_image/src/firebase_image.dart'; import 'package:firebase_image/src/image_object.dart'; -import 'package:firebase_storage/firebase_storage.dart'; +import 'package:flutter/foundation.dart'; import 'package:path/path.dart'; import 'package:path_provider/path_provider.dart'; +import 'package:pedantic/pedantic.dart'; import 'package:sqflite/sqflite.dart'; class FirebaseImageCacheManager { @@ -26,7 +26,7 @@ class FirebaseImageCacheManager { Future open() async { db = await openDatabase( - join((await getDatabasesPath())!, dbName), + join((await getDatabasesPath()), dbName), onCreate: (Database db, int version) async { await db.execute(''' CREATE TABLE $table ( @@ -73,7 +73,7 @@ class FirebaseImageCacheManager { where: 'uri = ?', whereArgs: [object.uri], ); - return maps.length > 0; + return maps.isNotEmpty; } Future get(String uri, FirebaseImage image) async { @@ -88,40 +88,37 @@ class FirebaseImageCacheManager { where: 'uri = ?', whereArgs: [uri], ); - if (maps.length > 0) { - FirebaseImageObject returnObject = - FirebaseImageObject.fromMap(maps.first); - returnObject.reference = getImageRef(returnObject, image.firebaseApp); - if (CacheRefreshStrategy.BY_METADATA_DATE == this.cacheRefreshStrategy) { - checkForUpdate(returnObject, image); // Check for update in background + if (maps.isNotEmpty) { + final returnObject = + FirebaseImageObject.fromMap(maps.first, image.firebaseApp); + if (CacheRefreshStrategy.BY_METADATA_DATE == cacheRefreshStrategy) { + // Check for update in background + unawaited(checkForUpdate( + returnObject, + image, + )); } return returnObject; } return null; } - Reference getImageRef(FirebaseImageObject object, FirebaseApp? firebaseApp) { - FirebaseStorage storage = - FirebaseStorage.instanceFor(app: firebaseApp, bucket: object.bucket); - return storage.ref().child(object.remotePath); - } - Future checkForUpdate( FirebaseImageObject object, FirebaseImage image) async { - int remoteVersion = (await object.reference.getMetadata()) + final remoteVersion = (await object.reference.getMetadata()) .updated ?.millisecondsSinceEpoch ?? -1; if (remoteVersion != object.version) { // If true, download new image for next load - await this.upsertRemoteFileToCache(object, image.maxSizeBytes); + await upsertRemoteFileToCache(object, image.maxSizeBytes); } } - Future> getAll() async { + Future> getAll(FirebaseImage image) async { final List> maps = await db.query(table); return List.generate(maps.length, (i) { - return FirebaseImageObject.fromMap(maps[i]); + return FirebaseImageObject.fromMap(maps[i], image.firebaseApp); }); } @@ -134,8 +131,10 @@ class FirebaseImageCacheManager { } Future localFileBytes(FirebaseImageObject? object) async { - if (await _fileExists(object)) { - return File(object!.localPath!).readAsBytes(); + final localPath = object?.localPath; + if (localPath == null) return null; + if (await _fileExists(localPath)) { + return File(localPath).readAsBytes(); } return null; } @@ -147,21 +146,26 @@ class FirebaseImageCacheManager { Future upsertRemoteFileToCache( FirebaseImageObject object, int maxSizeBytes) async { - if (CacheRefreshStrategy.BY_METADATA_DATE == this.cacheRefreshStrategy) { + if (CacheRefreshStrategy.BY_METADATA_DATE == cacheRefreshStrategy) { object.version = (await object.reference.getMetadata()) .updated ?.millisecondsSinceEpoch ?? 0; } - Uint8List? bytes = await remoteFileBytes(object, maxSizeBytes); - await putFile(object, bytes); + final bytes = await remoteFileBytes(object, maxSizeBytes); + + if (bytes != null) { + debugPrint('Bytes of object ${object.remotePath} could not be fetched'); + await putFile(object, bytes); + } + return bytes; } Future putFile( - FirebaseImageObject object, final bytes) async { - String path = basePath + "/" + object.remotePath; - path = path.replaceAll("//", "/"); + FirebaseImageObject object, List bytes) async { + var path = basePath + '/' + object.remotePath; + path = path.replaceAll('//', '/'); //print(join(basePath, object.remotePath)); Join isn't working? final localFile = await File(path).create(recursive: true); await localFile.writeAsBytes(bytes); @@ -169,11 +173,8 @@ class FirebaseImageCacheManager { return await upsert(object); } - Future _fileExists(FirebaseImageObject? object) async { - if (object?.localPath == null) { - return false; - } - return File(object!.localPath!).exists(); + Future _fileExists(String localPath) async { + return File(localPath).exists(); } Future _createFilePath() async { diff --git a/lib/src/firebase_image.dart b/lib/src/firebase_image.dart index 5526269..ae49c2c 100644 --- a/lib/src/firebase_image.dart +++ b/lib/src/firebase_image.dart @@ -65,35 +65,29 @@ class FirebaseImage extends ImageProvider { } static Reference _getImageRef(String location, FirebaseApp? firebaseApp) { - FirebaseStorage storage = FirebaseStorage.instanceFor( + final storage = FirebaseStorage.instanceFor( app: firebaseApp, bucket: _getBucket(location)); return storage.ref().child(_getImagePath(location)); } Future _fetchImage() async { Uint8List? bytes; - FirebaseImageCacheManager cacheManager = FirebaseImageCacheManager( - cacheRefreshStrategy, - ); + final cacheManager = FirebaseImageCacheManager(cacheRefreshStrategy); if (shouldCache) { await cacheManager.open(); - FirebaseImageObject? localObject = - await cacheManager.get(_imageObject.uri, this); + final localObject = await cacheManager.get(_imageObject.uri, this); if (localObject != null) { bytes = await cacheManager.localFileBytes(localObject); - if (bytes == null) { - bytes = await cacheManager.upsertRemoteFileToCache( - _imageObject, this.maxSizeBytes); - } + bytes ??= await cacheManager.upsertRemoteFileToCache( + _imageObject, maxSizeBytes); } else { bytes = await cacheManager.upsertRemoteFileToCache( - _imageObject, this.maxSizeBytes); + _imageObject, maxSizeBytes); } } else { - bytes = - await cacheManager.remoteFileBytes(_imageObject, this.maxSizeBytes); + bytes = await cacheManager.remoteFileBytes(_imageObject, maxSizeBytes); } return bytes!; @@ -120,15 +114,14 @@ class FirebaseImage extends ImageProvider { @override bool operator ==(dynamic other) { if (other.runtimeType != runtimeType) return false; - final FirebaseImage typedOther = other; + final typedOther = other as FirebaseImage; return _imageObject.uri == typedOther._imageObject.uri && - this.scale == typedOther.scale; + scale == typedOther.scale; } @override - int get hashCode => hashValues(_imageObject.uri, this.scale); + int get hashCode => hashValues(_imageObject.uri, scale); @override - String toString() => - '$runtimeType("${_imageObject.uri}", scale: ${this.scale})'; + String toString() => '$runtimeType("${_imageObject.uri}", scale: $scale)'; } diff --git a/lib/src/image_object.dart b/lib/src/image_object.dart index 95fc97e..b95d3d1 100644 --- a/lib/src/image_object.dart +++ b/lib/src/image_object.dart @@ -1,3 +1,4 @@ +import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_storage/firebase_storage.dart'; class FirebaseImageObject { @@ -17,22 +18,43 @@ class FirebaseImageObject { }) : uri = '$bucket$remotePath'; Map toMap() { - return { - 'version': this.version, - 'localPath': this.localPath, - 'bucket': this.bucket, - 'remotePath': this.remotePath, - 'uri': this.uri, + return { + 'version': version, + 'localPath': localPath, + 'bucket': bucket, + 'remotePath': remotePath, + 'uri': uri, }; } - factory FirebaseImageObject.fromMap(Map map) { + factory FirebaseImageObject.fromMap( + Map map, + FirebaseApp? firebaseApp, + ) { + final remotePath = map['remotePath'] as String; + final bucket = map['bucket'] as String; + final reference = getImageRef( + bucket: bucket, + remotePath: remotePath, + firebaseApp: firebaseApp, + ); + return FirebaseImageObject( - version: map["version"] ?? -1, - reference: map["reference"], - localPath: map["localPath"], - bucket: map["bucket"], - remotePath: map["remotePath"], + reference: reference, + version: map['version'] as int? ?? -1, + localPath: map['localPath'] as String?, + bucket: bucket, + remotePath: remotePath, ); } + + static Reference getImageRef({ + required String bucket, + required String remotePath, + FirebaseApp? firebaseApp, + }) { + final storage = + FirebaseStorage.instanceFor(app: firebaseApp, bucket: bucket); + return storage.ref().child(remotePath); + } } diff --git a/pubspec.yaml b/pubspec.yaml index 3aa336b..66123d6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: firebase_image description: A cached Flutter ImageProvider for Firebase Cloud Storage image objects. -version: 1.0.1 +version: 1.0.1+1 homepage: https://github.com/mattreid1/firebase_image environment: @@ -15,6 +15,7 @@ dependencies: sqflite: ^2.0.0+2 path: ^1.8.0 path_provider: ^2.0.1 + pedantic: ^1.11.0 dev_dependencies: flutter_test: