diff --git a/.flutter-plugins-dependencies b/.flutter-plugins-dependencies new file mode 100644 index 0000000..9bce691 --- /dev/null +++ b/.flutter-plugins-dependencies @@ -0,0 +1 @@ +{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"firebase_core","path":"/Users/josueerazo/.pub-cache/hosted/pub.dartlang.org/firebase_core-1.24.0/","native_build":true,"dependencies":[]},{"name":"firebase_storage","path":"/Users/josueerazo/.pub-cache/hosted/pub.dartlang.org/firebase_storage-10.3.10/","native_build":true,"dependencies":["firebase_core"]},{"name":"path_provider_ios","path":"/Users/josueerazo/.pub-cache/hosted/pub.dartlang.org/path_provider_ios-2.0.11/","native_build":true,"dependencies":[]},{"name":"sqflite","path":"/Users/josueerazo/.pub-cache/hosted/pub.dartlang.org/sqflite-2.1.0/","native_build":true,"dependencies":[]}],"android":[{"name":"firebase_core","path":"/Users/josueerazo/.pub-cache/hosted/pub.dartlang.org/firebase_core-1.24.0/","native_build":true,"dependencies":[]},{"name":"firebase_storage","path":"/Users/josueerazo/.pub-cache/hosted/pub.dartlang.org/firebase_storage-10.3.10/","native_build":true,"dependencies":["firebase_core"]},{"name":"path_provider_android","path":"/Users/josueerazo/.pub-cache/hosted/pub.dartlang.org/path_provider_android-2.0.20/","native_build":true,"dependencies":[]},{"name":"sqflite","path":"/Users/josueerazo/.pub-cache/hosted/pub.dartlang.org/sqflite-2.1.0/","native_build":true,"dependencies":[]}],"macos":[{"name":"firebase_core","path":"/Users/josueerazo/.pub-cache/hosted/pub.dartlang.org/firebase_core-1.24.0/","native_build":true,"dependencies":[]},{"name":"firebase_storage","path":"/Users/josueerazo/.pub-cache/hosted/pub.dartlang.org/firebase_storage-10.3.10/","native_build":true,"dependencies":["firebase_core"]},{"name":"path_provider_macos","path":"/Users/josueerazo/.pub-cache/hosted/pub.dartlang.org/path_provider_macos-2.0.6/","native_build":true,"dependencies":[]},{"name":"sqflite","path":"/Users/josueerazo/.pub-cache/hosted/pub.dartlang.org/sqflite-2.1.0/","native_build":true,"dependencies":[]}],"linux":[{"name":"path_provider_linux","path":"/Users/josueerazo/.pub-cache/hosted/pub.dartlang.org/path_provider_linux-2.1.7/","native_build":false,"dependencies":[]}],"windows":[{"name":"path_provider_windows","path":"/Users/josueerazo/.pub-cache/hosted/pub.dartlang.org/path_provider_windows-2.1.3/","native_build":false,"dependencies":[]}],"web":[{"name":"cloud_firestore_web","path":"/Users/josueerazo/.pub-cache/hosted/pub.dartlang.org/cloud_firestore_web-2.8.9/","dependencies":["firebase_core_web"]},{"name":"firebase_core_web","path":"/Users/josueerazo/.pub-cache/hosted/pub.dartlang.org/firebase_core_web-1.7.3/","dependencies":[]},{"name":"firebase_storage_web","path":"/Users/josueerazo/.pub-cache/hosted/pub.dartlang.org/firebase_storage_web-3.3.8/","dependencies":["firebase_core_web"]}]},"dependencyGraph":[{"name":"cloud_firestore_web","dependencies":["firebase_core","firebase_core_web"]},{"name":"firebase_core","dependencies":["firebase_core_web"]},{"name":"firebase_core_web","dependencies":[]},{"name":"firebase_storage","dependencies":["firebase_core","firebase_storage_web"]},{"name":"firebase_storage_web","dependencies":["firebase_core","firebase_core_web"]},{"name":"path_provider","dependencies":["path_provider_android","path_provider_ios","path_provider_linux","path_provider_macos","path_provider_windows"]},{"name":"path_provider_android","dependencies":[]},{"name":"path_provider_ios","dependencies":[]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_macos","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"sqflite","dependencies":[]}],"date_created":"2022-10-01 19:33:18.857001","version":"3.3.3"} \ No newline at end of file diff --git a/.fvm/flutter_sdk b/.fvm/flutter_sdk new file mode 120000 index 0000000..dd2830e --- /dev/null +++ b/.fvm/flutter_sdk @@ -0,0 +1 @@ +/Users/josueerazo/fvm/versions/stable \ No newline at end of file diff --git a/.fvm/fvm_config.json b/.fvm/fvm_config.json new file mode 100644 index 0000000..b3db758 --- /dev/null +++ b/.fvm/fvm_config.json @@ -0,0 +1,4 @@ +{ + "flutterSdkVersion": "stable", + "flavors": {} +} \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..3287bb6 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,13 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Flutter", + "request": "launch", + "type": "dart" + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..9e779d8 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,11 @@ +{ + "dart.flutterSdkPath": ".fvm/flutter_sdk", + // Remove .fvm files from search + "search.exclude": { + "**/.fvm": true + }, + // Remove from file watching + "files.watcherExclude": { + "**/.fvm": true + } +} \ No newline at end of file diff --git a/example/main.dart b/example/main.dart index 348b226..9c2b7b2 100644 --- a/example/main.dart +++ b/example/main.dart @@ -38,12 +38,13 @@ class _MyHomePageState extends State { title: Text(widget.title), ), body: Image( - image: FirebaseImage( - 'gs://bucket123/userIcon123.jpg', - shouldCache: true, // The image should be cached (default: True) - maxSizeBytes: 3000 * 1000, // 3MB max file size (default: 2.5MB) - cacheRefreshStrategy: CacheRefreshStrategy.NEVER // Switch off update checking - ), + image: FirebaseImage('gs://bucket123/userIcon123.jpg', + shouldCache: true, // The image should be cached (default: True) + maxSizeBytes: 3000 * 1000, // 3MB max file size (default: 2.5MB) + cacheRefreshStrategy: CacheRefreshStrategy.NEVER, + errorAssetImage: + 'assets/some_image.png' // Switch off update checking + ), width: 100, ), ); diff --git a/lib/firebase_image.dart b/lib/firebase_image.dart index 1b5b38c..ed42d46 100644 --- a/lib/firebase_image.dart +++ b/lib/firebase_image.dart @@ -1,4 +1,4 @@ library firebase_image; -export 'src/firebase_image.dart'; export 'src/cache_refresh_strategy.dart'; +export 'src/firebase_image.dart'; diff --git a/lib/src/cache_manager.dart b/lib/src/cache_manager.dart index 7c3a0e3..5126501 100644 --- a/lib/src/cache_manager.dart +++ b/lib/src/cache_manager.dart @@ -2,14 +2,16 @@ 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/services.dart'; import 'package:path/path.dart'; import 'package:path_provider/path_provider.dart'; import 'package:sqflite/sqflite.dart'; +import 'cache_refresh_strategy.dart'; +import 'firebase_image.dart'; +import 'image_object.dart'; + class FirebaseImageCacheManager { static const String key = 'firebase_image'; @@ -26,8 +28,8 @@ class FirebaseImageCacheManager { Future open() async { db = await openDatabase( - join((await getDatabasesPath())!, dbName), - onCreate: (Database db, int version) async { + join((await getDatabasesPath()), dbName), + onCreate: (db, version) async { await db.execute(''' CREATE TABLE $table ( uri TEXT PRIMARY KEY, @@ -73,7 +75,7 @@ class FirebaseImageCacheManager { where: 'uri = ?', whereArgs: [object.uri], ); - return maps.length > 0; + return maps.isNotEmpty; } Future get(String uri, FirebaseImage image) async { @@ -88,11 +90,10 @@ class FirebaseImageCacheManager { where: 'uri = ?', whereArgs: [uri], ); - if (maps.length > 0) { - FirebaseImageObject returnObject = - FirebaseImageObject.fromMap(maps.first); + if (maps.isNotEmpty) { + var returnObject = FirebaseImageObject.fromMap(maps.first); returnObject.reference = getImageRef(returnObject, image.firebaseApp); - if (CacheRefreshStrategy.BY_METADATA_DATE == this.cacheRefreshStrategy) { + if (CacheRefreshStrategy.BY_METADATA_DATE == cacheRefreshStrategy) { checkForUpdate(returnObject, image); // Check for update in background } return returnObject; @@ -101,20 +102,20 @@ class FirebaseImageCacheManager { } Reference getImageRef(FirebaseImageObject object, FirebaseApp? firebaseApp) { - FirebaseStorage storage = + var 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()) + var 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); } } @@ -142,25 +143,33 @@ class FirebaseImageCacheManager { Future remoteFileBytes( FirebaseImageObject object, int maxSizeBytes) { - return object.reference.getData(maxSizeBytes); + return object.reference!.getData(maxSizeBytes); } Future upsertRemoteFileToCache( FirebaseImageObject object, int maxSizeBytes) async { - if (CacheRefreshStrategy.BY_METADATA_DATE == this.cacheRefreshStrategy) { - object.version = (await object.reference.getMetadata()) - .updated - ?.millisecondsSinceEpoch ?? - 0; + try { + if (CacheRefreshStrategy.BY_METADATA_DATE == cacheRefreshStrategy) { + object.version = (await object.reference! + .getMetadata() + .timeout(Duration(milliseconds: 5000), onTimeout: () { + throw FirebaseException( + code: 'timeout', message: 'no internet', plugin: 'storage'); + })) + .updated! + .millisecondsSinceEpoch; + } + var bytes = await remoteFileBytes(object, maxSizeBytes); + await putFile(object, bytes!); + return bytes; + } on FirebaseException catch (_) { + rethrow; } - Uint8List? bytes = await remoteFileBytes(object, maxSizeBytes); - await putFile(object, bytes); - return bytes; } Future putFile( - FirebaseImageObject object, final bytes) async { - String path = basePath + "/" + object.remotePath; + FirebaseImageObject object, Uint8List 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); diff --git a/lib/src/firebase_image.dart b/lib/src/firebase_image.dart index 5526269..f3c6f33 100644 --- a/lib/src/firebase_image.dart +++ b/lib/src/firebase_image.dart @@ -1,14 +1,17 @@ -import 'dart:typed_data'; import 'dart:ui'; import 'package:firebase_core/firebase_core.dart'; -import 'package:firebase_image/firebase_image.dart'; -import 'package:firebase_image/src/cache_manager.dart'; -import 'package:firebase_image/src/image_object.dart'; + import 'package:firebase_storage/firebase_storage.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +import 'cache_manager.dart'; +import 'cache_refresh_strategy.dart'; +import 'image_object.dart'; +@immutable class FirebaseImage extends ImageProvider { // Default: True. Specified whether or not an image should be cached (optional) final bool shouldCache; @@ -25,6 +28,9 @@ class FirebaseImage extends ImageProvider { /// Default: the default Firebase app. Specifies a custom Firebase app to make the request to the bucket from (optional) final FirebaseApp? firebaseApp; + /// Image to show when a trouble occur + final String errorAssetImage; + /// The model for the image object final FirebaseImageObject _imageObject; @@ -38,6 +44,7 @@ class FirebaseImage extends ImageProvider { /// [firebaseApp] Default: the default Firebase app. Specifies a custom Firebase app to make the request to the bucket from (optional) FirebaseImage( String location, { + required this.errorAssetImage, this.shouldCache = true, this.scale = 1.0, this.maxSizeBytes = 2500 * 1000, // 2.5MB @@ -49,11 +56,6 @@ class FirebaseImage extends ImageProvider { reference: _getImageRef(location, firebaseApp), ); - /// Returns the image as bytes - Future getBytes() { - return _fetchImage(); - } - static String _getBucket(String location) { final uri = Uri.parse(location); return '${uri.scheme}://${uri.authority}'; @@ -65,43 +67,45 @@ class FirebaseImage extends ImageProvider { } static Reference _getImageRef(String location, FirebaseApp? firebaseApp) { - FirebaseStorage storage = FirebaseStorage.instanceFor( + var storage = FirebaseStorage.instanceFor( app: firebaseApp, bucket: _getBucket(location)); return storage.ref().child(_getImagePath(location)); } - Future _fetchImage() async { + Future _fetchImage() async { Uint8List? bytes; - FirebaseImageCacheManager cacheManager = FirebaseImageCacheManager( + final cacheManager = FirebaseImageCacheManager( cacheRefreshStrategy, ); if (shouldCache) { await cacheManager.open(); - FirebaseImageObject? localObject = - await cacheManager.get(_imageObject.uri, this); + var 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); + _imageObject, maxSizeBytes); } } else { - bytes = await cacheManager.upsertRemoteFileToCache( - _imageObject, this.maxSizeBytes); + try { + bytes = await cacheManager.upsertRemoteFileToCache( + _imageObject, maxSizeBytes); + } on FirebaseException catch (_) { + bytes = (await rootBundle.load(errorAssetImage)).buffer.asUint8List(); + } } } else { - bytes = - await cacheManager.remoteFileBytes(_imageObject, this.maxSizeBytes); + bytes = await cacheManager.remoteFileBytes(_imageObject, maxSizeBytes); } - return bytes!; + return ImmutableBuffer.fromUint8List(bytes!); } Future _fetchImageCodec() async { - return await PaintingBinding.instance! - .instantiateImageCodec(await _fetchImage()); + return await PaintingBinding.instance + .instantiateImageCodecFromBuffer(await _fetchImage()); } @override @@ -110,7 +114,11 @@ class FirebaseImage extends ImageProvider { } @override - ImageStreamCompleter load(FirebaseImage key, DecoderCallback decode) { + ImageStreamCompleter load( + FirebaseImage key, + Future Function(Uint8List, + {bool allowUpscaling, int? cacheHeight, int? cacheWidth}) + decode) { return MultiFrameImageStreamCompleter( codec: key._fetchImageCodec(), scale: key.scale, @@ -122,13 +130,12 @@ class FirebaseImage extends ImageProvider { if (other.runtimeType != runtimeType) return false; final FirebaseImage typedOther = other; 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 => this.hashCode; @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..c519ef3 100644 --- a/lib/src/image_object.dart +++ b/lib/src/image_object.dart @@ -2,7 +2,7 @@ import 'package:firebase_storage/firebase_storage.dart'; class FirebaseImageObject { int version; - Reference reference; + Reference? reference; String? localPath; final String remotePath; final String bucket; @@ -10,7 +10,7 @@ class FirebaseImageObject { FirebaseImageObject({ this.version = -1, - required this.reference, + this.reference, this.localPath, required this.bucket, required this.remotePath, @@ -18,11 +18,11 @@ class FirebaseImageObject { Map toMap() { return { - 'version': this.version, - 'localPath': this.localPath, - 'bucket': this.bucket, - 'remotePath': this.remotePath, - 'uri': this.uri, + 'version': version, + 'localPath': localPath, + 'bucket': bucket, + 'remotePath': remotePath, + 'uri': uri, }; } diff --git a/pubspec.yaml b/pubspec.yaml index 3aa336b..bddc3b7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,17 +1,18 @@ name: firebase_image description: A cached Flutter ImageProvider for Firebase Cloud Storage image objects. -version: 1.0.1 +version: 1.0.2 homepage: https://github.com/mattreid1/firebase_image environment: sdk: ">=2.12.0 <3.0.0" + flutter: 3.3.3 dependencies: flutter: sdk: flutter - firebase_core: ^1.0.0 - firebase_storage: ^8.0.0 + firebase_core: ^1.24.0 + firebase_storage: ^10.3.10 sqflite: ^2.0.0+2 path: ^1.8.0 path_provider: ^2.0.1