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

Passed all static analysis and added a default error image #42

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions .flutter-plugins-dependencies
Original file line number Diff line number Diff line change
@@ -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"}
1 change: 1 addition & 0 deletions .fvm/flutter_sdk
4 changes: 4 additions & 0 deletions .fvm/fvm_config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"flutterSdkVersion": "stable",
"flavors": {}
}
13 changes: 13 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -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"
}
]
}
11 changes: 11 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -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
}
}
13 changes: 7 additions & 6 deletions example/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,13 @@ class _MyHomePageState extends State<MyHomePage> {
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,
),
);
Expand Down
2 changes: 1 addition & 1 deletion lib/firebase_image.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
library firebase_image;

export 'src/firebase_image.dart';
export 'src/cache_refresh_strategy.dart';
export 'src/firebase_image.dart';
57 changes: 33 additions & 24 deletions lib/src/cache_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -26,8 +28,8 @@ class FirebaseImageCacheManager {

Future<void> 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,
Expand Down Expand Up @@ -73,7 +75,7 @@ class FirebaseImageCacheManager {
where: 'uri = ?',
whereArgs: [object.uri],
);
return maps.length > 0;
return maps.isNotEmpty;
}

Future<FirebaseImageObject?> get(String uri, FirebaseImage image) async {
Expand All @@ -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;
Expand All @@ -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<void> 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);
}
}

Expand Down Expand Up @@ -142,25 +143,33 @@ class FirebaseImageCacheManager {

Future<Uint8List?> remoteFileBytes(
FirebaseImageObject object, int maxSizeBytes) {
return object.reference.getData(maxSizeBytes);
return object.reference!.getData(maxSizeBytes);
}

Future<Uint8List?> 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<FirebaseImageObject> 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);
Expand Down
61 changes: 34 additions & 27 deletions lib/src/firebase_image.dart
Original file line number Diff line number Diff line change
@@ -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<FirebaseImage> {
// Default: True. Specified whether or not an image should be cached (optional)
final bool shouldCache;
Expand All @@ -25,6 +28,9 @@ class FirebaseImage extends ImageProvider<FirebaseImage> {
/// 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;

Expand All @@ -38,6 +44,7 @@ class FirebaseImage extends ImageProvider<FirebaseImage> {
/// [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
Expand All @@ -49,11 +56,6 @@ class FirebaseImage extends ImageProvider<FirebaseImage> {
reference: _getImageRef(location, firebaseApp),
);

/// Returns the image as bytes
Future<Uint8List> getBytes() {
return _fetchImage();
}

static String _getBucket(String location) {
final uri = Uri.parse(location);
return '${uri.scheme}://${uri.authority}';
Expand All @@ -65,43 +67,45 @@ class FirebaseImage extends ImageProvider<FirebaseImage> {
}

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<Uint8List> _fetchImage() async {
Future<ImmutableBuffer> _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<Codec> _fetchImageCodec() async {
return await PaintingBinding.instance!
.instantiateImageCodec(await _fetchImage());
return await PaintingBinding.instance
.instantiateImageCodecFromBuffer(await _fetchImage());
}

@override
Expand All @@ -110,7 +114,11 @@ class FirebaseImage extends ImageProvider<FirebaseImage> {
}

@override
ImageStreamCompleter load(FirebaseImage key, DecoderCallback decode) {
ImageStreamCompleter load(
FirebaseImage key,
Future<Codec> Function(Uint8List,
{bool allowUpscaling, int? cacheHeight, int? cacheWidth})
decode) {
return MultiFrameImageStreamCompleter(
codec: key._fetchImageCodec(),
scale: key.scale,
Expand All @@ -122,13 +130,12 @@ class FirebaseImage extends ImageProvider<FirebaseImage> {
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)';
}
14 changes: 7 additions & 7 deletions lib/src/image_object.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,27 @@ import 'package:firebase_storage/firebase_storage.dart';

class FirebaseImageObject {
int version;
Reference reference;
Reference? reference;
String? localPath;
final String remotePath;
final String bucket;
final String uri;

FirebaseImageObject({
this.version = -1,
required this.reference,
this.reference,
this.localPath,
required this.bucket,
required this.remotePath,
}) : uri = '$bucket$remotePath';

Map<String, dynamic> 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,
};
}

Expand Down
7 changes: 4 additions & 3 deletions pubspec.yaml
Original file line number Diff line number Diff line change
@@ -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
Expand Down