diff --git a/packages/firebase_storage/firebase_storage/example/lib/main.dart b/packages/firebase_storage/firebase_storage/example/lib/main.dart index 33b9e30b6ae1..927e38ffd49e 100755 --- a/packages/firebase_storage/firebase_storage/example/lib/main.dart +++ b/packages/firebase_storage/firebase_storage/example/lib/main.dart @@ -3,6 +3,7 @@ // BSD-style license that can be found in the LICENSE file. import 'dart:async'; +import 'dart:convert'; import 'dart:io' as io; import 'package:firebase_core/firebase_core.dart'; @@ -42,6 +43,9 @@ enum UploadType { /// Uploads a file from the device. file, + /// Uploads a Uint8List to Storage. + uint8List, + /// Clears any tasks from the list. clear, } @@ -135,6 +139,23 @@ class _TaskManager extends State { ); } + Future uploadUint8List() async { + UploadTask uploadTask; + + // Create a Reference to the file + Reference ref = FirebaseStorage.instance + .ref() + .child('flutter-tests') + .child('/some-json.json'); + + const response = '{"key": "value", "number": 42}'; + final data = jsonDecode(response); + + uploadTask = ref.putData(Uint8List.fromList(utf8.encode(jsonEncode(data)))); + + return Future.value(uploadTask); + } + /// Handles the user pressing the PopupMenuItem item. Future handleUploadType(UploadType type) async { switch (type) { @@ -153,6 +174,12 @@ class _TaskManager extends State { }); } break; + case UploadType.uint8List: + final task = await uploadUint8List(); + setState(() { + _uploadTasks = [..._uploadTasks, task]; + }); + break; case UploadType.clear: setState(() { _uploadTasks = []; @@ -241,6 +268,11 @@ class _TaskManager extends State { child: Text('Upload local file'), value: UploadType.file, ), + const PopupMenuItem( + // ignore: sort_child_properties_last + child: Text('Upload Uint8List'), + value: UploadType.uint8List, + ), if (_uploadTasks.isNotEmpty) const PopupMenuItem( // ignore: sort_child_properties_last diff --git a/packages/firebase_storage/firebase_storage/example/pubspec.yaml b/packages/firebase_storage/firebase_storage/example/pubspec.yaml index f4efd6a854c2..8c99c5fb9ff0 100755 --- a/packages/firebase_storage/firebase_storage/example/pubspec.yaml +++ b/packages/firebase_storage/firebase_storage/example/pubspec.yaml @@ -9,8 +9,8 @@ dependencies: firebase_storage: ^12.3.2 flutter: sdk: flutter - image_picker: ^1.0.2 - image_picker_for_web: ^2.1.12 + image_picker: ^1.1.2 + image_picker_for_web: ^3.0.5 web: ^1.0.0 flutter: diff --git a/packages/firebase_storage/firebase_storage_web/lib/src/interop/storage.dart b/packages/firebase_storage/firebase_storage_web/lib/src/interop/storage.dart index d0c0ba5c21db..b7e57b0ed74a 100644 --- a/packages/firebase_storage/firebase_storage_web/lib/src/interop/storage.dart +++ b/packages/firebase_storage/firebase_storage_web/lib/src/interop/storage.dart @@ -179,7 +179,9 @@ class StorageReference /// Uploads data [blob] to the actual location with optional [metadata]. /// Returns the [UploadTask] which can be used to monitor and manage /// the upload. - UploadTask put(dynamic blob, [UploadMetadata? metadata]) { + /// + /// `blob` can be a [Uint8List] or [Blob]. + UploadTask put(JSAny blob, [UploadMetadata? metadata]) { storage_interop.UploadTaskJsImpl taskImpl; if (metadata != null) { taskImpl = storage_interop.uploadBytesResumable( diff --git a/packages/firebase_storage/firebase_storage_web/lib/src/reference_web.dart b/packages/firebase_storage/firebase_storage_web/lib/src/reference_web.dart index 2ba54062b844..9f624582ab93 100644 --- a/packages/firebase_storage/firebase_storage_web/lib/src/reference_web.dart +++ b/packages/firebase_storage/firebase_storage_web/lib/src/reference_web.dart @@ -4,6 +4,7 @@ // BSD-style license that can be found in the LICENSE file. import 'dart:convert'; +import 'dart:js_interop'; import 'dart:typed_data'; import 'package:firebase_storage_platform_interface/firebase_storage_platform_interface.dart'; @@ -143,7 +144,7 @@ class ReferenceWeb extends ReferencePlatform { return TaskWeb( this, _ref.put( - data, + data.toJS, settableMetadataToFbUploadMetadata( _cache.store(metadata), ), @@ -186,18 +187,24 @@ class ReferenceWeb extends ReferencePlatform { PutStringFormat format, [ SettableMetadata? metadata, ]) { - dynamic _data = data; + late Uint8List _data; // The universal package is converting raw to base64, so we need to convert // Any base64 string values into a Uint8List. if (format == PutStringFormat.base64) { _data = base64Decode(data); + } else if (format == PutStringFormat.base64Url) { + _data = base64Url.decode(data); + } else { + // If the format is not base64 or base64Url, we need to encode the data + // as a base64 string. + _data = Uint8List.fromList(base64Encode(utf8.encode(data)).codeUnits); } return TaskWeb( this, _ref.put( - _data, + _data.toJS, settableMetadataToFbUploadMetadata( _cache.store(metadata), // md5 is computed server-side, so we don't have to unpack a potentially huge Blob. diff --git a/tests/integration_test/firebase_storage/reference_e2e.dart b/tests/integration_test/firebase_storage/reference_e2e.dart index f190c9ebd0ff..d9a8440be79e 100644 --- a/tests/integration_test/firebase_storage/reference_e2e.dart +++ b/tests/integration_test/firebase_storage/reference_e2e.dart @@ -240,33 +240,36 @@ void setupReferenceTests() { group( 'putData', () { - test('uploads a file with buffer and download to check content matches', - () async { - const text = 'put data text to compare with uploaded and downloaded'; - List list = utf8.encode(text); + test( + 'uploads a file with buffer and download to check content matches', + () async { + const text = + 'put data text to compare with uploaded and downloaded'; + List list = utf8.encode(text); - Uint8List data = Uint8List.fromList(list); + Uint8List data = Uint8List.fromList(list); - final Reference ref = - storage.ref('flutter-tests').child('flt-ok.txt'); + final Reference ref = + storage.ref('flutter-tests').child('flt-ok.txt'); - final TaskSnapshot complete = await ref.putData( - data, - SettableMetadata( - contentLanguage: 'en', - ), - ); + final TaskSnapshot complete = await ref.putData( + data, + SettableMetadata( + contentLanguage: 'en', + ), + ); - expect(complete.metadata?.size, text.length); - expect(complete.metadata?.contentLanguage, 'en'); + expect(complete.metadata?.size, text.length); + expect(complete.metadata?.contentLanguage, 'en'); - // Download the file from Firebase Storage - final downloadedData = await ref.getData(); - final downloadedContent = String.fromCharCodes(downloadedData!); + // Download the file from Firebase Storage + final downloadedData = await ref.getData(); + final downloadedContent = String.fromCharCodes(downloadedData!); - // Verify that the downloaded content matches the original content - expect(downloadedContent, equals(text)); - }); + // Verify that the downloaded content matches the original content + expect(downloadedContent, equals(text)); + }, + ); //TODO(pr-mais): causes the emulator to crash // test('errors if permission denied', () async { @@ -282,8 +285,27 @@ void setupReferenceTests() { // .having((e) => e.message, 'message', // 'User is not authorized to perform the desired action.'))); // }); + + test( + 'upload a json file', + () async { + final Map data = { + 'name': 'John Doe', + 'age': 30, + }; + final Uint8List jsonData = utf8.encode(jsonEncode(data)); + final Reference ref = + storage.ref('flutter-tests').child('flt-web-ok.json'); + final TaskSnapshot complete = await ref.putData( + jsonData, + SettableMetadata( + contentType: 'application/json', + ), + ); + expect(complete.metadata?.contentType, 'application/json'); + }, + ); }, - skip: kIsWeb, ); group('putBlob', () {