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

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

Open
7 tasks done
ekuleshov opened this issue Mar 26, 2023 · 23 comments
Open
7 tasks done

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

ekuleshov opened this issue Mar 26, 2023 · 23 comments
Labels
bug Something isn't working triage

Comments

@ekuleshov
Copy link

ekuleshov commented Mar 26, 2023

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

  • I Google'd a solution and I couldn't find it
  • I searched on StackOverflow for a solution and I couldn't find it
  • I read the README.md file of the plugin
  • I'm using the latest version of the plugin
  • All dependencies are up to date with flutter pub upgrade
  • I did a flutter clean
  • I tried running the example project
@ekuleshov ekuleshov added bug Something isn't working triage labels Mar 26, 2023
@cyrilhl
Copy link

cyrilhl commented Apr 1, 2023

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

@TechAurelian2
Copy link

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
Copy link

xonaman commented Jun 10, 2023

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

@c-seeger
Copy link

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

final shareResult = await Share.shareXFiles(
[
XFile.fromData(
buffer.asUint8List(data.offsetInBytes, data.lengthInBytes),
name: 'flutter_logo.png',
mimeType: 'image/png',
),
],
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size,
);

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
Copy link

pixnbit commented Aug 22, 2023

Tried on Safari and Edge also, none worked.

@codingiswhyicry
Copy link

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
Copy link

I have the same problem, has anyone solved it?

@xonaman
Copy link

xonaman commented Dec 11, 2023

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
Copy link

Does anyone knows how to fix it ?

  • Tried on Desktop browser -> got error, NoSuchMethodError: tried to call a non-function, such as null: 'this.share'.
  • Tried on Mobile browser (Safari) -> got this white UI pop up
image

@xonaman
Copy link

xonaman commented Mar 8, 2024

@dongnqdev would you mind sharing your implementation?

@dongnqdev
Copy link

dongnqdev commented Mar 8, 2024

@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
Copy link
Member

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
Copy link

@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.

  • I will try your suggestion and post the result here. Thanks for your help

@dongnqdev
Copy link

@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
Copy link
Member

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
Copy link

@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
Copy link

same issue, flutter: 3.16.9

@sladomic
Copy link

sladomic commented May 17, 2024

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
Copy link

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
Copy link
Member

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.

Copy link

This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 15 days

@github-actions github-actions bot added the Stale label Nov 11, 2024
@ekuleshov
Copy link
Author

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

@miquelbeltran would you you be okay with fallback implementation using something along the lines of @hamishjohnson example in #1643 (comment) ?

@miquelbeltran
Copy link
Member

It makes sense to me!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working triage
Projects
None yet
Development

No branches or pull requests