fluttercommunity / plus_plugins

Flutter Community Plus Plugins
BSD 3-Clause "New" or "Revised" License
1.55k stars 921 forks source link

[Bug]: share_plus Share.shareXFiles() is not working on web #1643

Open ekuleshov opened 1 year ago

ekuleshov commented 1 year ago

Platform

Chrome web browser on MacOS

Plugin

share_plus

Version

latest from the main

Flutter SDK

3.7.8

Steps to reproduce

  1. Checkout latetst sources from main
  2. Open project in Android Studio
  3. Select Chrome (web) as target and launch share_plus/example app
  4. Click on the "Share XFile from Assets" button
  5. The following exception is thrown but the plugin readme says "it falls back to downloading the shared files" when Web Share API is not available.

Code Sample

The `share_plus/example` app from https://github.com/fluttercommunity/plus_plugins/tree/main/packages/share_plus/share_plus

Logs

Debug service listening on ws://127.0.0.1:50601/BFGEsW3RKXM=/ws
TypeError: this.share is not a function
dart-sdk/lib/html/dart2js/html_dart2js.dart 22933:49                              share]
packages/share_plus/src/share_plus_web.dart 105:22                                shareXFiles
dart-sdk/lib/_internal/js_dev_runtime/patch/async_patch.dart 45:50                <fn>
dart-sdk/lib/async/zone.dart 1660:54                                              runUnary
dart-sdk/lib/async/future_impl.dart 147:18                                        handleValue
dart-sdk/lib/async/future_impl.dart 767:44                                        handleValueCallback
dart-sdk/lib/async/future_impl.dart 796:13                                        _propagateToListeners
dart-sdk/lib/async/future_impl.dart 558:7                                         [_complete]
dart-sdk/lib/async/stream_pipe.dart 61:11                                         _cancelAndValue
dart-sdk/lib/async/stream.dart 1587:7                                             <fn>
dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart 367:37  _checkAndCall
dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart 372:39  dcall
dart-sdk/lib/html/dart2js/html_dart2js.dart 37367:58                              <fn>

Flutter Doctor

.../flutter doctor --verbose
[✓] Flutter (Channel stable, 3.7.8, on macOS 12.6.1 21G217 darwin-arm64, locale en-CA)
    • Flutter version 3.7.8 on channel stable at .../flutter
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision 90c64ed42b (5 days ago), 2023-03-21 11:27:08 -0500
    • Engine revision 9aa7816315
    • Dart version 2.19.5
    • DevTools version 2.20.1

[✓] Xcode - develop for iOS and macOS (Xcode 14.2)
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • Build 14C18
    • CocoaPods version 1.11.3

[✓] Chrome - develop for the web
    • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome

[✓] Android Studio (version 2022.1)
    • Android Studio at /Applications/Android Studio.app/Contents
    • Flutter plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/6351-dart
    • Java version OpenJDK Runtime Environment (build 11.0.15+0-b2043.56-8887301)

Checklist before submitting a bug

cyrilhl commented 1 year ago

I got the same error message when I create a XFile with image data, and then share it by 'shareXFiles' function

TechAurelian2 commented 1 year ago

I got the same error message when I create a XFile with image data, and then share it by 'shareXFiles' function

I got the same error message on Chrome 113 on iOS 16.4.1 (iPhone):

TypeError: this.share is not a function. (In 'this.share(data_dict)', 'this.share' is undefined)

xonaman commented 1 year ago

Same here! I am using XFile.fromData(/* some image byte Uint8List */) if that helps finding the cause.

c-seeger commented 1 year ago

Same issue here!

Sample code that uses PictureRecorder to generate a png from canvas

ui.PictureRecorder recorder = ui.PictureRecorder();
Canvas canvas = Canvas(recorder);

Size exportSize = Size.square(size);
paint!.painter!.paint(canvas, exportSize);
ui.Image renderedImage = await recorder
    .endRecording()
    .toImage(exportSize.width.floor(), exportSize.height.floor());
ByteData? pngBytes =
    await renderedImage.toByteData(format: ui.ImageByteFormat.png);

Share.shareXFiles(
  [
    XFile.fromData(
      pngBytes!.buffer
          .asUint8List(pngBytes.offsetInBytes, pngBytes.lengthInBytes),
      name: 'qr-code.png',
      mimeType: 'image/png',
    )
  ],
);

followed the example https://github.com/fluttercommunity/plus_plugins/blob/b6888edcf224b9d5a1dc15716881b6e1a7114ef5/packages/share_plus/share_plus/example/lib/main.dart#L243-L252

still getting

Uncaught (in promise) TypeError: this.share is not a function
    at [dartx.share] (html_dart2js.dart:22933:49)
    at share_plus_web.SharePlusWebPlugin.new.shareXFiles (share_plus_web.dart:105:22)
    at shareXFiles.next (<anonymous>)
    at async_patch.dart:45:50
    at _RootZone.runUnary (zone.dart:1660:54)
    at _FutureListener.thenAwait.handleValue (future_impl.dart:147:18)
    at handleValueCallback (future_impl.dart:767:44)
    at _Future._propagateToListeners (future_impl.dart:796:13)
    at [_complete] (future_impl.dart:558:7)
    at Object._cancelAndValue (stream_pipe.dart:61:11)
    at stream.dart:1587:7
    at Object._checkAndCall (operations.dart:367:37)
    at Object.dcall (operations.dart:372:39)
pixnbit commented 1 year ago

Tried on Safari and Edge also, none worked.

codingiswhyicry commented 10 months ago

Seconding this is an issue for me.

My code sample:

var shareButton = TextButton( style: TextButton.styleFrom( backgroundColor: FauraColors().babiOrange(), padding: const EdgeInsets.all(5.0), ), onPressed: () async { Share.shareXFiles( [ await XFile.fromData(await pdfLogic.returnActionItemPDF( widget.actionItems, PdfPageFormat.letter)), ], subject: "Home Wildfire Mitigation Plan", ); }, child: Text( "Share PDF.", style: FauraTheme().fauraTextTheme().bodySmall, ));

Any plans for fixing or workarounds?

mengqutaoyuan commented 8 months ago

I have the same problem, has anyone solved it?

xonaman commented 8 months ago

I think the issue is that web browsers do not have a native share menu like mobile devices have. However, you can download single files. Here is an example:

await XFile.fromData(
  Uint8List.fromList(/* some mp3 data */),
  mimeType: 'audio/mpeg',
).saveTo('my-file-name.mp3');
dongnqdev commented 5 months ago

Does anyone knows how to fix it ?

xonaman commented 5 months ago

@dongnqdev would you mind sharing your implementation?

dongnqdev commented 5 months ago

@xonaman Yes, This is my code. If you wonder where is the bytes came from. It was data converted from Widget to Image. And it works well on android and iOS.

Btw, I have a question doesn't relates to this topic. But does ShareResult.raw provides us any information about the option that user have chosen? For example, save to device or name of the selected application (twitter x,....).

Thanks

Future<void> saveAndShare(
      Uint8List bytes, WidgetRef ref, bool? isFirstShared) async {
    late final XFile file;
    if (kIsWeb) {
      file = XFile.fromData(bytes);
    } else {
      final directory = await getApplicationDocumentsDirectory();
      final image = File('${directory.path}/xlp.png');
      image.writeAsBytesSync(bytes);
      file = XFile(image.path);
    }

    final result = await Share.shareXFiles([file],
        subject: BStrings.current.welcomeToBlockx,
        text: BStrings.current.welcomeToBlockx);
  }

This is my doctor -v, and I'm using share_plus: ^7.2.1

[!] Flutter (Channel [user-branch], 3.13.9, on macOS 13.6.3 22G436
    darwin-x64, locale en-US)
    ! Flutter version 3.13.9 on channel [user-branch] at
      /Users/developerblockx/Documents/development/flutter
      Currently on an unknown channel. Run `flutter channel` to
      switch to an official channel.
      If that doesn't fix the issue, reinstall Flutter by following
      instructions at https://flutter.dev/docs/get-started/install.
    ! Upstream repository unknown source is not a standard remote.
      Set environment variable "FLUTTER_GIT_URL" to unknown source
      to dismiss this error.
    • Framework revision d211f42860 (5 months ago), 2023-10-25
      13:42:25 -0700
    • Engine revision 0545f8705d
    • Dart version 3.1.5
    • DevTools version 2.25.0
    • If those were intentional, you can disregard the above
      warnings; however it is recommended to use "git" directly to
      perform update checks and upgrades.

[✓] Android toolchain - develop for Android devices (Android SDK
    version 34.0.0)
    • Android SDK at /Users/developerblockx/Library/Android/sdk
    • Platform android-34, build-tools 34.0.0
    • Java binary at: /Applications/Android
      Studio.app/Contents/jbr/Contents/Home/bin/java
    • Java version OpenJDK Runtime Environment (build
      17.0.7+0-17.0.7b1000.6-10550314)
    • All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS (Xcode 15.2)
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • Build 15C500b
    • CocoaPods version 1.14.3

[✓] Chrome - develop for the web
    • Chrome at /Applications/Google
      Chrome.app/Contents/MacOS/Google Chrome

[✓] Android Studio (version 2023.1)
    • Android Studio at /Applications/Android Studio.app/Contents
    • Flutter plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/6351-dart
    • Java version OpenJDK Runtime Environment (build
      17.0.7+0-17.0.7b1000.6-10550314)

[✓] VS Code (version 1.85.1)
    • VS Code at /Users/developerblockx/Downloads/Visual Studio
      Code.app/Contents
    • Flutter extension version 3.84.0

[✓] Connected device (2 available)
    • macOS (desktop) • macos  • darwin-x64     • macOS 13.6.3 22G436 darwin-x64
    • Chrome (web)    • chrome • web-javascript • Google Chrome 122.0.6261.112

[✓] Network resources
    • All expected network resources are available.
miquelbeltran commented 5 months ago

The documentation mentions the following:

  /// Share [XFile] objects.
  ///
  /// Remarks for the web implementation:
  /// This uses the [Web Share API](https://web.dev/web-share/) if it's
  /// available. Otherwise, uncaught Errors will be thrown.
  /// See [Can I Use - Web Share API](https://caniuse.com/web-share) to
  /// understand which browsers are supported. This builds on the
  /// [`cross_file`](https://pub.dev/packages/cross_file) package.

The code essentially all does is to call to dart:html share method with a list of "web" files.

https://github.com/fluttercommunity/plus_plugins/blob/main/packages/share_plus/share_plus/lib/src/share_plus_web.dart#L105

One improvement that could be done is using the canShare method from the Navigator to check if those files are "shareable": https://web.dev/articles/web-share#sharing_files as it looks like not all types of files are supported

One thing you can try as well is setting the mimeType for all files, just in case that helps the browser.

dongnqdev commented 5 months ago

@miquelbeltran Thank for your response. I did check the version of safari before posting my issue. My iPhone is running 17.3.1, so the safari on my phone is supported.

dongnqdev commented 5 months ago

@miquelbeltran Hi Miquelbentran I have tried to specify the mimeType, but still got the same problem. This is my code. About Navigator class, I couldn't find canShare method.

Future<void> saveAndShare(
      Uint8List bytes, WidgetRef ref, bool? isFirstShared) async {
    late final XFile file;
    if (kIsWeb) {
      file = XFile.fromData(bytes, mimeType: 'mage/png');<==== HERE
    } else {
      final directory = await getApplicationDocumentsDirectory();
      final image = File('${directory.path}/xlp.png');
      image.writeAsBytesSync(bytes);
      file = XFile(image.path); 
    }

    final result = await Share.shareXFiles([file],
        subject: BStrings.current.welcomeToBlockx,
        text: BStrings.current.welcomeToBlockx);

  }

This is StackTrace

TRACE 1: dart-sdk/lib/html/dart2js/html_dart2js.dart 22831:49                              share]
packages/share_plus/src/share_plus_web.dart 105:22                                shareXFiles
dart-sdk/lib/_internal/js_dev_runtime/patch/async_patch.dart 45:50                <fn>
dart-sdk/lib/async/zone.dart 1407:47                                              _rootRunUnary
dart-sdk/lib/async/zone.dart 1308:19                                              runUnary
dart-sdk/lib/async/future_impl.dart 156:18                                        handleValue
dart-sdk/lib/async/future_impl.dart 840:44                                        handleValueCallback
dart-sdk/lib/async/future_impl.dart 869:13                                        _propagateToListeners
dart-sdk/lib/async/future_impl.dart 632:7                                         [_complete]
dart-sdk/lib/async/stream_pipe.dart 61:11                                         _cancelAndValue
dart-sdk/lib/async/stream.dart 1581:7                                             <fn>
dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart 574:37  _checkAndCall
dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart 579:39  dcall
dart-sdk/lib/html/dart2js/html_dart2js.dart 37263:58                              <fn>
dart-sdk/lib/async/zone.dart 1415:13                                              _rootRunUnary
dart-sdk/lib/async/zone.dart 1308:19                                              runUnary
dart-sdk/lib/async/zone.dart 1217:7                                               runUnaryGuarded
dart-sdk/lib/async/zone.dart 1254:26                                              <fn>

dart-sdk/lib/html/dart2js/html_dart2js.dart 22831:49                              share]
packages/share_plus/src/share_plus_web.dart 105:22                                shareXFiles
dart-sdk/lib/_internal/js_dev_runtime/patch/async_patch.dart 45:50                <fn>
dart-sdk/lib/async/zone.dart 1407:47                                              _rootRunUnary
dart-sdk/lib/async/zone.dart 1308:19                                              runUnary
dart-sdk/lib/async/future_impl.dart 156:18                                        handleValue
dart-sdk/lib/async/future_impl.dart 840:44                                        handleValueCallback
dart-sdk/lib/async/future_impl.dart 869:13                                        _propagateToListeners
dart-sdk/lib/async/future_impl.dart 632:7                                         [_complete]
dart-sdk/lib/async/stream_pipe.dart 61:11                                         _cancelAndValue
dart-sdk/lib/async/stream.dart 1581:7                                             <fn>
dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart 574:37  _checkAndCall
dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart 579:39  dcall
dart-sdk/lib/html/dart2js/html_dart2js.dart 37263:58                              <fn>
dart-sdk/lib/async/zone.dart 1415:13                                              _rootRunUnary
dart-sdk/lib/async/zone.dart 1308:19                                              runUnary
dart-sdk/lib/async/zone.dart 1217:7                                               runUnaryGuarded
dart-sdk/lib/async/zone.dart 1254:26                                              <fn>
miquelbeltran commented 5 months ago

That doesn't look correct --> 'mage/png' it should be 'image/png' but anyway, this looks like an issue with the underlying browser/js rather than something the plugin can fix

dongnqdev commented 5 months ago

@miquelbeltran Yes, I tried the 'image/png', i missed the letter 'I' when copy the code. Thank you for clarifying. I have tried on safari (Desktop), chrome (Desktop), chome (IOS), Safari (IOS), chrome (Android). They all got the same problem, the pop up is white.

junixapp commented 5 months ago

same issue, flutter: 3.16.9

sladomic commented 3 months ago

You can use MemoryFileSystem for web and LocalFileSystem for mobile from https://pub.dev/packages/file.

final FileSystem _fileSystem = kIsWeb ? MemoryFileSystem() : const LocalFileSystem();

See their example to create a file https://github.com/google/file.dart/blob/master/packages/file/example/main.dart.

You can than pass this file's path and basename into an XFile.

XFile(
  file.path,
  name: file.basename,
)

But for now the Share sheet for me is only shown in Safari (not Chrome, not Firefox).

So as a fallback you can have a download action.

import 'package:universal_html/html.dart' as html;

final result = await Share.shareXFiles(
              [
                XFile(
                  file.path,
                  name: file.basename,
                ),
              ],
              subject: "Subject",
              text:
                  "Text",
              // required for iPads, see https://pub.dev/packages/share_plus
              sharePositionOrigin: (context.findRenderObject() as RenderBox?)!
                      .localToGlobal(Offset.zero) &
                  (context.findRenderObject() as RenderBox?)!.size,
            );
            // Web fallback if Share API is not available
            if (result.status == ShareResultStatus.unavailable && kIsWeb) {
              final url =
                  "data:application/pdf;base64,${base64Encode(await file.readAsBytes())}";
              try {
                final html.AnchorElement anchorElement =
                    html.AnchorElement(href: url);
                anchorElement.download = model.fetchedPDFFile!.basename;
                anchorElement.click();
                anchorElement.remove();
              } catch (e) {
                await launchUrl(
                  Uri.parse(
                    url,
                  ),
                );
              }
            }
hamishjohnson commented 1 week ago

That doesn't look correct --> 'mage/png' it should be 'image/png' but anyway, this looks like an issue with the underlying browser/js rather than something the plugin can fix

The documentation states On web, this uses the [Web Share API](https://web.dev/web-share/) if it's available. Otherwise it falls back to downloading the shared files.

However, this fallback does not seem to be working, which in my opinion is a bug. It can also be easily fixed with proper fallback handling.

Below is some code that works great for me, using the universal html package.

  Future<void> _downloadWeb(XFile file) async {
    final bytes = await file.readAsBytes();
    final blob = html.Blob([bytes]);
    final url = html.Url.createObjectUrlFromBlob(blob);

    final anchor = html.document.createElement('a') as html.AnchorElement
      ..href = url
      ..style.display = 'none'
      ..download = file.name;
    html.document.body!.children.add(anchor);
    anchor.click();
    html.document.body!.children.remove(anchor);
    html.Url.revokeObjectUrl(url);
  }
miquelbeltran commented 1 week ago

However, this fallback does not seem to be working, which in my opinion is a bug.

Yeah, looking at the code seems that it was removed (or even never implemented?).

Anyway, if you want, you can submit a PR with the change... Or the README should be corrected.