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

Stricter types with pedantic and fix for #37 #38

Open
wants to merge 5 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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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`

Expand Down
6 changes: 6 additions & 0 deletions analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
include: package:pedantic/analysis_options.yaml

analyzer:
strong-mode:
implicit-casts: false
implicit-dynamic: false
67 changes: 34 additions & 33 deletions lib/src/cache_manager.dart
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -26,7 +26,7 @@ class FirebaseImageCacheManager {

Future<void> open() async {
db = await openDatabase(
join((await getDatabasesPath())!, dbName),
join((await getDatabasesPath()), dbName),
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get the error The argument type 'String?' can't be assigned to the parameter type 'String'.dart(argument_type_not_assignable) here... Any ideas?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's odd, I don't think it returns a nullable string anymore.

Looking at ^2.0.0+3 of sqflite, all their uses of getDatabasesPath have a return type of Future<String>

onCreate: (Database db, int version) async {
await db.execute('''
CREATE TABLE $table (
Expand Down Expand Up @@ -73,7 +73,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,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<void> 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<List<FirebaseImageObject>> getAll() async {
Future<List<FirebaseImageObject>> getAll(FirebaseImage image) async {
final List<Map<String, dynamic>> maps = await db.query(table);
return List.generate(maps.length, (i) {
return FirebaseImageObject.fromMap(maps[i]);
return FirebaseImageObject.fromMap(maps[i], image.firebaseApp);
});
}

Expand All @@ -134,8 +131,10 @@ class FirebaseImageCacheManager {
}

Future<Uint8List?> 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;
}
Expand All @@ -147,33 +146,35 @@ class FirebaseImageCacheManager {

Future<Uint8List?> 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<FirebaseImageObject> putFile(
FirebaseImageObject object, final bytes) async {
String path = basePath + "/" + object.remotePath;
path = path.replaceAll("//", "/");
FirebaseImageObject object, List<int> 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);
object.localPath = localFile.path;
return await upsert(object);
}

Future<bool> _fileExists(FirebaseImageObject? object) async {
if (object?.localPath == null) {
return false;
}
return File(object!.localPath!).exists();
Future<bool> _fileExists(String localPath) async {
return File(localPath).exists();
}

Future<String> _createFilePath() async {
Expand Down
29 changes: 11 additions & 18 deletions lib/src/firebase_image.dart
Original file line number Diff line number Diff line change
Expand Up @@ -65,35 +65,29 @@ class FirebaseImage extends ImageProvider<FirebaseImage> {
}

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<Uint8List> _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(
stargazing-dino marked this conversation as resolved.
Show resolved Hide resolved
_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!;
Expand All @@ -120,15 +114,14 @@ class FirebaseImage extends ImageProvider<FirebaseImage> {
@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)';
}
46 changes: 34 additions & 12 deletions lib/src/image_object.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_storage/firebase_storage.dart';

class FirebaseImageObject {
Expand All @@ -17,22 +18,43 @@ class FirebaseImageObject {
}) : uri = '$bucket$remotePath';

Map<String, dynamic> toMap() {
return {
'version': this.version,
'localPath': this.localPath,
'bucket': this.bucket,
'remotePath': this.remotePath,
'uri': this.uri,
return <String, dynamic>{
'version': version,
'localPath': localPath,
'bucket': bucket,
'remotePath': remotePath,
'uri': uri,
};
}

factory FirebaseImageObject.fromMap(Map<String, dynamic> map) {
factory FirebaseImageObject.fromMap(
Map<String, dynamic> 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);
}
}
3 changes: 2 additions & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -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:
Expand All @@ -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:
Expand Down