40

I would like to share an image using the standard share dialogs in iOS and Android. The code below is mostly from https://pub.dartlang.org/packages/share which I'm using as a starting point (only Dart and Objective-C below). It currently shares only text.

Instead of Image below which I'm not sure is the best approach, how would I convert the image to a byte stream in Dart and handle in iOS and Android.

Dart

static const _kShareChannel = const MethodChannel('example.test.com/share');
Future<Null> shareImage(Image image) {
  assert(image != null);
  return _kShareChannel.invokeMethod('shareImage', image);
}

Objective-C

static NSString *const PLATFORM_CHANNEL = @"example.test.com/share";

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    [GeneratedPluginRegistrant registerWithRegistry:self];

    FlutterViewController* controller = (FlutterViewController*)self.window.rootViewController;

    FlutterMethodChannel *shareChannel = [FlutterMethodChannel methodChannelWithName:PLATFORM_CHANNEL
                            binaryMessenger:controller];

    [shareChannel setMethodCallHandler:^(FlutterMethodCall *call, FlutterResult result) {
        if ([@"shareImage" isEqualToString:call.method]) {
            [self share:call.arguments withController:[UIApplication sharedApplication].keyWindow.rootViewController];
            result(nil);
        } else {
            result([FlutterError errorWithCode:@"UNKNOWN_METHOD"
                                       message:@"Unknown share method called"
                                       details:nil]);
        }
    }];
    return [super application:application didFinishLaunchingWithOptions:launchOptions];
}

- (void)share:(id)sharedItems withController:(UIViewController *)controller {
    UIActivityViewController *activityViewController = [[UIActivityViewController alloc] initWithActivityItems:@[ sharedItems ]
                                  applicationActivities:nil];
    [controller presentViewController:activityViewController animated:YES completion:nil];
}
Albert Lardizabal
  • 6,548
  • 7
  • 34
  • 34
  • Related issue: https://github.com/flutter/flutter/issues/16743 – Duncan Jones Apr 23 '18 at 18:56
  • @DuncanJones what do you need exactly ? – Rémi Rousselet Apr 24 '18 at 09:56
  • @RémiRousselet As a relative newcomer to Flutter, I didn't understand Collin's answer and wasn't sure how to proceed. So I guess I'm looking for a more detailed walk through of the tasks involved; where there might be a need for native code vs what can be achieved in Flutter, etc. – Duncan Jones Apr 24 '18 at 19:25
  • @DuncanJones does alardizabel's answer below help with your question? I guess that boils down to: what is the source of the image you want to share? The answer shows it coming from an asset. What's your use case? Captured from the camera? Downloaded over http? Generated in memory using a proprietary codec? Do you have an encoded image (jpeg, png, etc) or a raw raster that needs encoding, or something else? If the answer here doesn't help, perhaps ask your own question. Incidentally, does this help you with your vCard question? If not, another question... – Richard Heap Apr 25 '18 at 01:49
  • @RichardHeap The answer looks helpful (and will likely claim the bounty). In my specific case, the image I want to share is already a file in the apps document directory, but I'm sure I can figure out the remaining gaps myself. – Duncan Jones Apr 25 '18 at 10:04
  • @DuncanJones Using the documents directory would just mean swapping out `getTemporaryDirectory` with `getApplicationDocumentsDirectory` on the dart side, `NSDocumentDirectory` instead of `NSCachesDirectory` on iOS. On Android, it’s a little trickier. `getFilesDir` is the simplest way to access the file system on Android, but the current path provider flutter plugin corresponds to `getDir`. You would also need to update `file_paths.xml`. This should be part of a plugin but it needs work on where to store files, valid file types, and cases of text only, image only, text and images, etc. – Albert Lardizabal Apr 25 '18 at 15:50

8 Answers8

48

The below will allow you to send a file (specifically an image in this example) using UIActivityViewController on iOS and as a share intent on Android.

FileProvider overview (Android)

Update pubspec.yaml to reference your image if local (image.jpg in this example) and to use the path_provider plugin in order to access the file system. https://pub.dartlang.org/packages/path_provider

main.dart

import 'dart:io';
import 'dart:typed_data';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:path_provider/path_provider.dart';

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Share Demo',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new MyHomePage(title: 'Share Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {

  @override
  Widget build(BuildContext context) {

    return new Scaffold(
      appBar: new AppBar(
        title: new Text(widget.title),
      ),
      body: new Center(
        child: new Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
          ],
        ),
      ),
      floatingActionButton: new FloatingActionButton(
        onPressed: _shareImage,
        tooltip: 'Share',
        child: new Icon(Icons.share),
      ),
    );
  }

  _shareImage() async {
    try {
      final ByteData bytes = await rootBundle.load('assets/image.jpg');
      final Uint8List list = bytes.buffer.asUint8List();

      final tempDir = await getTemporaryDirectory();
      final file = await new File('${tempDir.path}/image.jpg').create();
      file.writeAsBytesSync(list);

      final channel = const MethodChannel('channel:me.albie.share/share');
      channel.invokeMethod('shareFile', 'image.jpg');

    } catch (e) {
      print('Share error: $e');
    }
  }
}

AppDelegate.m

#include "AppDelegate.h"
#include "GeneratedPluginRegistrant.h"

@implementation AppDelegate

static NSString *const SHARE_CHANNEL = @"channel:me.albie.share/share";

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    [GeneratedPluginRegistrant registerWithRegistry:self];
    FlutterViewController* controller = (FlutterViewController*)self.window.rootViewController;

    FlutterMethodChannel *shareChannel =
    [FlutterMethodChannel methodChannelWithName:SHARE_CHANNEL
                                binaryMessenger:controller];

    [shareChannel setMethodCallHandler:^(FlutterMethodCall *call, FlutterResult result) {
        if ([@"shareFile" isEqualToString:call.method]) {
            [self shareFile:call.arguments
             withController:[UIApplication sharedApplication].keyWindow.rootViewController];
        }
    }];

    return [super application:application didFinishLaunchingWithOptions:launchOptions];
}

- (void)shareFile:(id)sharedItems withController:(UIViewController *)controller {
    NSMutableString *filePath = [NSMutableString stringWithString:sharedItems];
    NSString *docsPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0];
    NSString *imagePath = [docsPath stringByAppendingPathComponent:filePath];
    NSURL *imageUrl = [NSURL fileURLWithPath:imagePath];
    NSData *imageData = [NSData dataWithContentsOfURL:imageUrl];
    UIImage *shareImage = [UIImage imageWithData:imageData];

    UIActivityViewController *activityViewController =
    [[UIActivityViewController alloc] initWithActivityItems:@[ shareImage ]
                                      applicationActivities:nil];
    [controller presentViewController:activityViewController animated:YES completion:nil];
}

@end

MainActivity.java

package com.example.share;

import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;

import java.io.File;

import io.flutter.app.FlutterActivity;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugins.GeneratedPluginRegistrant;

import android.support.v4.content.FileProvider;

public class MainActivity extends FlutterActivity {

    private static final String SHARE_CHANNEL = "channel:me.albie.share/share";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        GeneratedPluginRegistrant.registerWith(this);

        new MethodChannel(this.getFlutterView(), SHARE_CHANNEL).setMethodCallHandler(new MethodChannel.MethodCallHandler() {
            public final void onMethodCall(MethodCall methodCall, MethodChannel.Result result) {
                if (methodCall.method.equals("shareFile")) {
                    shareFile((String) methodCall.arguments);
                }
            }
        });
    }

    private void shareFile(String path) {
        File imageFile = new File(this.getApplicationContext().getCacheDir(), path);
        Uri contentUri = FileProvider.getUriForFile(this, "me.albie.share", imageFile);
        Intent shareIntent = new Intent(Intent.ACTION_SEND);
        shareIntent.setType("image/jpg");
        shareIntent.putExtra(Intent.EXTRA_STREAM, contentUri);
        this.startActivity(Intent.createChooser(shareIntent, "Share image using"));
    }
}

AndroidManifest.xml

<provider
    android:name="android.support.v4.content.FileProvider"
    android:authorities="me.albie.share"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_paths" />
</provider>

xml/file_paths.xml

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <cache-path name="images" path="/"/>
</paths>

build.gradle (app)

dependencies {
    ...
    implementation 'com.android.support:support-v4:27.1.1'
}
Albert Lardizabal
  • 6,548
  • 7
  • 34
  • 34
  • 12
    Why don't you combine all the native ode and make a flutter plugin? Please do that. It's difficult for a people like me to Flutter – Mohith7548 Jun 17 '18 at 09:03
  • Thanks a lot, it worked for me.I'm very happy. I'm upvoting this answer now!! – Mohith7548 Jun 18 '18 at 03:40
  • 2
    Please, create a plugin with this functionality, there's no plugins with that functionality, share files. Is hard to us that have no experience with IOS. Thank you in advance. – Jorge Vieira Sep 20 '18 at 11:59
  • 1
    Please, anyone, create the plugin, or fix the https://pub.dartlang.org/packages/advanced_share plugin that is broken at the moment. I tried to use this answer, but without proper Android knowledge this is too complicated to debug. – Marek Lisý Sep 24 '18 at 16:40
  • 1
    When I used your flutter code, I got the following exception upon clicking on my button: MissingPluginException(No implementation found for method shareFile on channel channel:me.albie.share/share) Does anyone know how to resolve this? – mcfred Jan 22 '19 at 15:49
  • @bangbang, search for `shareFile` in the `AppDelegate` or `MainActivity` above depending on what platform you're referring to. In the method call handlers, there are `if` checks to match up the method calls you're making from Flutter to native. The arguments are passed as strings. You're seeing that error possibly because of a typo of the `shareFile` strings or you didn't include the code to handle the call. – Albert Lardizabal Jan 22 '19 at 16:30
  • But I thought I just have to copy paste the flutter code you shared and that's all. – mcfred Jan 22 '19 at 17:33
  • . I am developing an app using dart and flutter. So, that's why I copied the flutter code in the hopes that it will work. – mcfred Jan 22 '19 at 17:41
  • @bangbang The above uses native code and platform channels. You can also try the plugin mentioned below. – Albert Lardizabal Jan 22 '19 at 18:52
  • Thank you this code is very helpful but how to send an image with text using this method – mr.hir Feb 01 '19 at 05:16
  • Pls renew your answer. I got lot of error im my code.. – Sachin Jun 16 '20 at 05:34
  • I have upvoted it but your suggested channel is not working. final channel = const MethodChannel('channel:me.albie.share/share'); channel.invokeMethod('shareFile', 'image.jpg'); – Kamlesh Feb 15 '21 at 06:33
  • i tried all plugins for share a pdf from local mobile folder to whatsapp/gmail in 2022 all are outdated . nothing working . – btm me May 13 '22 at 10:34
13

We put that functionality into a plugin: https://pub.dartlang.org/packages/esys_flutter_share.

Dart:

final ByteData bytes = await rootBundle.load('assets/image1.png');
await Share.file('esys image', 'esys.png', bytes.buffer.asUint8List(), 'image/png');
Daniel
  • 333
  • 4
  • 13
8

Thanks to @albert-lardizabal for the code above it works just perfect!! I had to translate it to Swift and Kotlin, so here's the code in case you guys need it:

Swift:

override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?
    ) -> Bool {
    GeneratedPluginRegistrant.register(with: self)


    let shareChannelName = "channel:me.albie.share/share";
    let controller:FlutterViewController = self.window?.rootViewController as! FlutterViewController;
    let shareChannel:FlutterMethodChannel = FlutterMethodChannel.init(name: shareChannelName, binaryMessenger: controller);

    shareChannel.setMethodCallHandler({
        (call: FlutterMethodCall, result: FlutterResult) -> Void in
        if (call.method == "shareFile") {
            self.shareFile(sharedItems: call.arguments!,controller: controller);
        }
    });


    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}

func shareFile(sharedItems:Any, controller:UIViewController) {
    let filePath:NSMutableString = NSMutableString.init(string: sharedItems as! String);
    let docsPath:NSString = (NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.cachesDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)[0]) as NSString;
    let imagePath = docsPath.appendingPathComponent(filePath as String);
    let imageUrl = URL.init(fileURLWithPath: imagePath, relativeTo: nil);
    do {
        let imageData = try Data.init(contentsOf: imageUrl);
        let shareImage = UIImage.init(data: imageData);
        let activityViewController:UIActivityViewController = UIActivityViewController.init(activityItems: [shareImage!], applicationActivities: nil);
        controller.present(activityViewController, animated: true, completion: nil);
    } catch let error {
        print(error.localizedDescription);
    }
}

Kotlin:

    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    GeneratedPluginRegistrant.registerWith(this)

    MethodChannel(flutterView,"channel:me.albie.share/share").setMethodCallHandler { methodCall, _ ->
        if (methodCall.method == "shareFile") {
            shareFile(methodCall.arguments as String)
        }
    }
}

private fun shareFile(path:String) {
    val imageFile = File(this.applicationContext.cacheDir,path)
    val contentUri = FileProvider.getUriForFile(this,"me.albie.share",imageFile)

    val shareIntent = Intent()
    shareIntent.action = Intent.ACTION_SEND
    shareIntent.type="image/jpg"
    shareIntent.putExtra(Intent.EXTRA_STREAM, contentUri)
    startActivity(Intent.createChooser(shareIntent,"Compartir usando"))
}
Simon
  • 101
  • 1
  • 4
  • Please, i'd like to ask you some questions about your code, 1. can you do a lib of it to we use in the yaml file? or 2. Can you edit the post to show step by step how to do it working on a project? and 3. This code works with local and remote files? 4. it works only for image or for any type of file? thank you in advance.. – Jorge Vieira Nov 13 '18 at 20:31
  • 1
    how to send image with text in social media like whats app – mr.hir Feb 01 '19 at 05:44
8

Try using wc_flutter_share.

https://pub.dev/packages/wc_flutter_share

This plugin supports sharing image, text and subject. Unique thing about this plugin is that it also supports sharing image and text simultaneously which other plugins don't support at the time I am writing this answer.

Ali Abbas
  • 161
  • 2
  • 2
  • same as esys_flutter_share plugin: Your flutter's iOS code needs to be in swift. – Alessandro Santamaria Aug 15 '19 at 15:30
  • @AlessandroSantamaria - not the same as esys_flutter_share .. I could not get esys to show Subject on an email share. The image/video/audio would come across fine to email app, as well as body text, but the subject was always blank. WcFlutterShare works with subject, and works across any app I've shared content with, for sharing images, video, text and audio .. to email, hangouts, whatsapp, skype etc – WallyHale Dec 16 '19 at 14:23
2

In 2021, you should use share_plus , the official Share plugin. It's reliable and easy to use.

Import the library.

import 'package:share_plus/share_plus.dart';

Then invoke the static share method anywhere in your Dart code.

Share.share('check out my website https://example.com');

The share method also takes an optional subject that will be used when sharing to email.

Share.share('check out my website https://example.com', subject: 'Look what I made!');

To share one or multiple files invoke the static shareFiles method anywhere in your Dart code. Optionally you can also pass in text and subject.

Share.shareFiles(['${directory.path}/image.jpg'], text: 'Great picture');
Share.shareFiles(['${directory.path}/image1.jpg', '${directory.path}/image2.jpg']);
iStar
  • 1,112
  • 12
  • 20
  • What is this `directory.path`? – Abbas Jafari Aug 25 '22 at 06:59
  • @AbbasJafari `directory.path` is where you save the image to. Usually, the `directory` can be the app document directory and be gotten by calling `getApplicationDocumentsDirectory()`. – iStar Aug 26 '22 at 08:08
1

If the image file is downloaded, I would recommend saving it to a temporary file in Dart.

await new File('${systemTempDir.path}/foo.jpg').create();

Then you can invoke UIActivityViewController with a URL representing the filename of the image file. Here's some sample code on how to do this in a non-Flutter app that should get you started.

If your image file is constructed dynamically (e.g. using the Canvas API), you may be wondering how to encode a ui.Image object into an image file. The Flutter engine doesn't currently provide a way to do that, it would be possible modify the engine to add that support. You can take a look at how screenshot support is implemented for inspiration, but it won't be trivial.

Collin Jackson
  • 110,240
  • 31
  • 221
  • 152
0

I would suggest using the following flutter plugin:

https://pub.dartlang.org/packages/share

Text sharing is quite simple:

Share.share('Text I wish to share');

For image: Better convert an image to Base64 string and send as a string.

ThiagoAM
  • 1,432
  • 13
  • 20
Ankur Prakash
  • 1,417
  • 4
  • 18
  • 31
  • 8
    You cannot share a Base64 String with the Flutter Share Plugin. I have tried it, all that happens it shares the Base64 String as text :/ – Alessandro Santamaria Aug 15 '19 at 15:25
  • in 2022 i want to share pdf file from device local folder to gmail, and whatsapp. not an single solution worked for me. class FlutterShare2 { static const MethodChannel _channel = MethodChannel('flutter_share'); static Future shareFile({ required String filePath, }) async { assert(filePath.isNotEmpty); final success = await _channel.invokeMethod('shareFile', { 'filePath': filePath, });return success; } } /// await FlutterShare2.shareFile( filePath: file!.path as String, //filePath: file!.path.toString(), ); – btm me May 13 '22 at 10:33
0

We have two different cases:

  1. The image is already saved in the device
  2. The image is on the network like getting the image link from an API or having the image stored online.

For the first case, sharing it is simple using share_plus plugin like the following:

Share.shareFiles(['path_to_image']);

For the second case, we have to do the following:

  • Read the image as bytes
  • Write the image to a temporary file
  • Share the created image using the method in 1.

The code uses path_provider to get the temporary directory and the File class used from dart:io so you have to add this to the start import 'dart:io';

The code will be like this:

// function to get the local path
  Future<String> get _localPath async {
      final directory = await getTemporaryDirectory();
      return directory.path;
  }
// create the file
  Future<File> get _localFile async {
    final path = await _localPath;
    return File('$path/image.jpeg');
  }
// write the data to the file
  Future<File> writeImage(List<int> bytes) async {
    final file = await _localFile;
    return file.writeAsBytes(bytes);
  }
// the function that you send the link of the image to
  shareNetworkImage(String url) async {
    final path = await _localPath;
    http.Response response = await http.get(url);
    await writeImage(response.bodyBytes);
    Share.shareFiles(['$path/image.jpg']);
  }

It will be used like this:

shareNetworkImage('https://abdulrazakzakieh.com/images/abdul_razak_zakieh.jpg')