firebase / flutterfire

šŸ”„ A collection of Firebase plugins for Flutter apps.
https://firebase.google.com/docs/flutter/setup
BSD 3-Clause "New" or "Revised" License
8.49k stars 3.92k forks source link

šŸ› [firebase_storage] MissingPluginException when using firebase_storage via flutter_isolate #10514

Open guenth39 opened 1 year ago

guenth39 commented 1 year ago

Bug report

Describe the bug Trying to upload a file in a flutter isolate to firebase storage fails on iOS with the following error message. On Android, everything works as expected. At first, I thought the error was the same as #9790, but it is a different error. For simplicity, I have forked the example project of that ticket and adapted it.

[VERBOSE-2:dart_vm_initializer.cc(41)] Unhandled Exception: MissingPluginException(No implementation found for method Task#startPutFile on channel plugins.flutter.io/firebase_storage)
#0      MethodChannel._invokeMethod
platform_channel.dart:313
<asynchronous suspension>

Steps to reproduce

Steps to reproduce the behavior:

  1. Check out the provided sample project
  2. Configure a firebase project
  3. run the app and tap the plus button

Expected behavior

The file should be uploaded correctly as on Android.

Sample project

https://github.com/guenth39/firebase-storage-isolate-issue-sample-app


Flutter doctor

Run flutter doctor and paste the output below:

Click To Expand ``` Doctor summary (to see all details, run flutter doctor -v): [āœ“] Flutter (Channel stable, 3.7.4, on macOS 13.2 22D49 darwin-arm64 (Rosetta), locale de-DE) [āœ“] Android toolchain - develop for Android devices (Android SDK version 33.0.0-rc3) [āœ“] Xcode - develop for iOS and macOS (Xcode 14.2) [āœ“] Chrome - develop for the web [āœ“] Android Studio (version 2021.2) [āœ“] VS Code (version 1.75.1) [āœ“] Connected device (4 available) [āœ“] HTTP Host Availability ā€¢ No issues found! ```

Flutter dependencies

Run flutter pub deps -- --style=compact and paste the output below:

Click To Expand ``` Dart SDK 2.19.2 Flutter SDK 3.7.4 sample 10.0.1+423 dependencies: - firebase_core 2.7.0 [firebase_core_platform_interface firebase_core_web flutter meta] - firebase_storage 11.0.14 [firebase_core firebase_core_platform_interface firebase_storage_platform_interface firebase_storage_web flutter] - flutter 0.0.0 [characters collection js material_color_utilities meta vector_math sky_engine] - flutter_isolate 2.0.4 [flutter uuid] - path_provider 2.0.12 [flutter path_provider_android path_provider_foundation path_provider_linux path_provider_platform_interface path_provider_windows] dev dependencies: - flutter_test 0.0.0 [flutter test_api path fake_async clock stack_trace vector_math async boolean_selector characters collection js matcher material_color_utilities meta source_span stream_channel string_scanner term_glyph] - integration_test 0.0.0 [flutter flutter_driver flutter_test path vm_service archive async boolean_selector characters clock collection crypto fake_async file js matcher material_color_utilities meta source_span stack_trace stream_channel string_scanner sync_http term_glyph test_api typed_data vector_math webdriver] transitive dependencies: - _flutterfire_internals 1.0.16 [collection firebase_core firebase_core_platform_interface flutter meta] - archive 3.3.2 [crypto path] - async 2.10.0 [collection meta] - boolean_selector 2.1.1 [source_span string_scanner] - characters 1.2.1 - clock 1.1.1 - collection 1.17.0 - crypto 3.0.2 [typed_data] - fake_async 1.3.1 [clock collection] - ffi 2.0.1 - file 6.1.4 [meta path] - firebase_core_platform_interface 4.5.3 [collection flutter flutter_test meta plugin_platform_interface] - firebase_core_web 2.2.1 [firebase_core_platform_interface flutter flutter_web_plugins js meta] - firebase_storage_platform_interface 4.1.30 [collection firebase_core flutter meta plugin_platform_interface] - firebase_storage_web 3.3.23 [_flutterfire_internals async firebase_core firebase_core_web firebase_storage_platform_interface flutter flutter_web_plugins http js meta] - flutter_driver 0.0.0 [file flutter flutter_test fuchsia_remote_debug_protocol path meta vm_service webdriver archive async boolean_selector characters clock collection crypto js matcher material_color_utilities platform process source_span stack_trace stream_channel string_scanner sync_http term_glyph test_api typed_data vector_math] - flutter_web_plugins 0.0.0 [flutter js characters collection material_color_utilities meta vector_math] - fuchsia_remote_debug_protocol 0.0.0 [process vm_service file meta path platform] - http 0.13.5 [async http_parser meta path] - http_parser 4.0.2 [collection source_span string_scanner typed_data] - js 0.6.5 [meta] - matcher 0.12.13 [meta stack_trace] - material_color_utilities 0.2.0 - meta 1.8.0 - path 1.8.2 - path_provider_android 2.0.22 [flutter path_provider_platform_interface] - path_provider_foundation 2.1.1 [flutter path_provider_platform_interface] - path_provider_linux 2.1.8 [ffi flutter path path_provider_platform_interface xdg_directories] - path_provider_platform_interface 2.0.5 [flutter platform plugin_platform_interface] - path_provider_windows 2.1.3 [ffi flutter path path_provider_platform_interface win32] - platform 3.1.0 - plugin_platform_interface 2.1.3 [meta] - process 4.2.4 [file path platform] - sky_engine 0.0.99 - source_span 1.9.1 [collection path term_glyph] - stack_trace 1.11.0 [path] - stream_channel 2.1.1 [async] - string_scanner 1.2.0 [source_span] - sync_http 0.3.1 - term_glyph 1.2.1 - test_api 0.4.16 [async boolean_selector collection meta source_span stack_trace stream_channel string_scanner term_glyph matcher] - typed_data 1.3.1 [collection] - uuid 3.0.7 [crypto] - vector_math 2.1.4 - vm_service 9.4.0 - webdriver 3.0.1 [archive matcher path stack_trace sync_http] - win32 3.1.3 [ffi] - xdg_directories 1.0.0 [meta path process] ```

darshankawar commented 1 year ago

Thanks for the report. Cloning the repo and then added local firebase app's firebase_options file and ran on iOS. Tapping on the button throws the same error as reported.

makda-mohammad commented 1 year ago

facing the same issue does anyone have any solution for this as it is running absolutely fine in android but throws MissingPluginException on iOS.

ariefwijaya commented 1 year ago

I also got this error

[VERBOSE-2:dart_vm_initializer.cc(41)] Unhandled Exception: MissingPluginException(No implementation found for method Task#writeToFile on channel plugins.flutter.io/firebase_storage)
#0      MethodChannel._invokeMethod
platform_channel.dart:313
<asynchronous suspension>

@nmfisher Do we need to create this ticket to flutter_isolate repo? we have no idea, why this happened on iOS only.

Doppelklick commented 1 year ago

same with workmanager (Flutter version 3.7.10).

Anybody found a workaround?

pulpcorn commented 1 year ago

I also got this error. Any solutions for iOS?

[VERBOSE-2:dart_vm_initializer.cc(41)] Unhandled Exception: MissingPluginException(No implementation found for method Task#startPutData on channel plugins.flutter.io/firebase_storage)

0 MethodChannel._invokeMethod (package:flutter/src/services/platform_channel.dart:313:7)

thecaptainXgod commented 1 year ago

Facing this issue as well.

eran247 commented 11 months ago

I also got this error. Any solutions for iOS?

[VERBOSE-2:dart_vm_initializer.cc(41)] Unhandled Exception: MissingPluginException(No implementation found for method Task#startPutData on channel plugins.flutter.io/firebase_storage)

0 MethodChannel._invokeMethod (package:flutter/src/services/platform_channel.dart:313:7)

3 months later, but facing the same issue. Is there any update yet?

yoman07 commented 11 months ago

I have the same problem. Any solution?

eran247 commented 11 months ago

@yoman07 I have downgraded my Firebase storage dependency to 10.x for now. That seems to work for now.

yoman07 commented 11 months ago

Thank you @eran247 . I fixed it by modified firebase_storage plugin but it it's just temporary hack

scottynoshotty commented 8 months ago

Seeing the same error. Trying to upload files in the background on iOS using work manager. Can't downgrade firebase_storage due to other dependencies. Stuck until this is resolved.

ScottAtRedHawk commented 8 months ago

I'm seeing the same thing.

scottynoshotty commented 8 months ago

I found a workaround. Upload using the cloud storage http resumable upload api. I also created a cloud function to generate the access token.

  static Future<void> uploadLocalMediaInBackground(
      List<LocalMedia> localMedia) async {
    for (LocalMedia media in localMedia) {
      final File file = media.file;
      final fileSize = await file.length();
      final chunkSize = 1024 * 1024;
      // Initialize variables for tracking progress.
      int offset = 0;
      int bytesLeft = fileSize;
      final chunkedStreamReader = ChunkedStreamReader(file.openRead());

      // Initialize the resumable upload session.
      final uploadUrl = await initiateResumableUpload(
          MediaConstants.bucketName, media.cloudStoragePath, fileSize);

      while (bytesLeft > 0) {
        final int bytesToRead = (bytesLeft < chunkSize) ? bytesLeft : chunkSize;

        final chunk = await chunkedStreamReader.readChunk(bytesToRead);

        // Upload the chunk to the Cloud Storage.
        await uploadChunk(uploadUrl, offset, Uint8List.fromList(chunk));

        offset += chunk.length;
        bytesLeft -= chunk.length;
      }

      // Finalize the upload (commit).
      await finalizeUpload(uploadUrl);
    }
  }

  static Future<String> initiateResumableUpload(
      String bucketName, String objectName, int fileSize) async {
    final url =
        'https://storage.googleapis.com/upload/storage/v1/b/$bucketName/o?uploadType=resumable&name=$objectName';
    String? accessToken = await AuthService.getAccessToken();
    if (accessToken == null) {
      throw Exception('Could not get access token, it was null');
    }
    final response = await http.post(
      Uri.parse(url),
      headers: {
        'Authorization': 'Bearer $accessToken',
        // Use your access token here
        'Content-Length': '0',
        // Empty body for initiation
        'X-Upload-Content-Length': '$fileSize',
        // Total file size
      },
    );

    if (response.statusCode == 200) {
      final uploadUrl = response.headers['location'];
      if (uploadUrl != null) {
        return uploadUrl;
      } else {
        throw Exception('Failed to get upload url as it was null');
      }
    } else {
      throw Exception('Failed to initiate resumable upload');
    }
  }

  static Future<void> uploadChunk(
      String uploadUrl, int offset, Uint8List chunk) async {
    final response = await http.put(
      Uri.parse(uploadUrl),
      headers: {
        'Content-Range': 'bytes $offset-${offset + chunk.length - 1}/*',
      },
      body: chunk,
    );
  }

  static Future<void> finalizeUpload(String uploadUrl) async {
    final response = await http.post(
      Uri.parse(uploadUrl),
      headers: {
        'Content-Range': 'bytes */*', // Finalize the upload
      },
    );

    if (response.statusCode != 200) {
      throw Exception('Failed to finalize upload');
    }
  }
ScottAtRedHawk commented 8 months ago

@scottynoshotty Could you post your cloud function, please?

scottynoshotty commented 8 months ago

I have the json key from one of my service accounts in the same dir as my functions. I know it's bad security practice but it's what I've found works (certainly won't submit to Github)

export const generateAccessToken = functions.https.onRequest(async (req, res) => {
  try {
    functions.logger.log("Generating access token");

    // Get the service account key from Firebase functions config
    const serviceAccountJson = fs.readFileSync("creds.json", "utf8");

    functions.logger.log("Service Account JSON:", serviceAccountJson);

    // Parse the service account JSON
    const serviceAccount = JSON.parse(serviceAccountJson);

    functions.logger.log("Loaded service account key from functions config");
    functions.logger.log("Service Account Email:", serviceAccount.client_email);
    functions.logger.log("Service Account Scopes:", serviceAccount.scopes);

    // Create a JWT client to generate access tokens
    const client = new JWT({
      email: serviceAccount.client_email,
      key: serviceAccount.private_key,
      scopes: ["https://www.googleapis.com/auth/cloud-platform"],
    });

    functions.logger.log("Created JWT client");

    // Generate an access token
    const accessToken = await client.authorize();

    res.json({data: {accessToken: accessToken.access_token}});
  } catch (error) {
    console.error(error);
    functions.logger.error("Error generating access token:", error);
    res.status(500).json({error: "Unable to generate access token"});
  }
});
AntoineChauviere commented 8 months ago

Is there a solution for uploading images to Firebase Storage using Isolate that works on both Android and iOS?

DominicOrga commented 7 months ago

I've been facing the same problem since Feb. However, the error appears to have changed. Currently, the issue manifests as follows on my end:

Unhandled PlatformException(channel-error, Unable to establish connection on channel., null, null)
#0      FirebaseStorageHostApi.referencePutFile (package:firebase_storage_platform_interface/src/pigeon/messages.pigeon.dart:685:7)
<asynchronous suspension>
#1      new MethodChannelTask.mapNativeStream (package:firebase_storage_platform_interface/src/method_channel/method_channel_task.dart:29:26)
Doppelklick commented 6 months ago

I've been facing the same problem since Feb. However, the error appears to have changed. Currently, the issue manifests as follows on my end:

Unhandled PlatformException(channel-error, Unable to establish connection on channel., null, null)
#0      FirebaseStorageHostApi.referencePutFile (package:firebase_storage_platform_interface/src/pigeon/messages.pigeon.dart:685:7)
<asynchronous suspension>
#1      new MethodChannelTask.mapNativeStream (package:firebase_storage_platform_interface/src/method_channel/method_channel_task.dart:29:26)

I can confirm that it looks like the error has changed. Problem still exists...

russellwheatley commented 5 months ago

Isolates won't work for this task because it uses the event channel (i.e. native -> dart). I tried without the use of flutter_isolates and used essentially what is described here. I received the same exception on android and iOS:

E/flutter (11420): Unsupported operation: Background isolates do not support setMessageHandler(). Messages from the host platform always go to the root isolate.

There is an open issue on Flutter for this specific purpose: https://github.com/flutter/flutter/issues/119207

This won't work until the above issue is resolved.