file_share_intent

pub package

Flutter plugin that implements Android Intent and iOS Share Extension to receive shared content including media files, documents, text, and URLs from external applications.

Also, supports iOS Share extension and launching the host app automatically. Check the provided example for more info.

Note: This plugin does not require the NSPhotoLibraryUsageDescription permission on iOS. Photos and videos are shared as file URLs, not photo library identifiers.

This is a fork of the original receive_sharing_intent and listen_sharing_intent plugin.

Main differences:

  • Pull requests merged
  • Extend UIViewController instead of SLComposeServiceViewController to enable WhatsApp-style seamless sharing instead of showing a text field popup
Android iOS
Support SDK 19+ (Kotlin 1.9.22) 12.0+ (Swift 5.0)

Alt Text

Usage

To use this plugin, add file_share_intent as a dependency in your pubspec.yaml file. For example:

dependencies:
  file_share_intent: ^latest

Limitations

  • iOS: Photo library asset identifiers are not supported. When photos are shared from the Photos app or other apps, they are provided as file URLs which work without requiring photo library access. This removes the need for NSPhotoLibraryUsageDescription in your Info.plist.

Android

Add the following filters to your android/app/src/main/AndroidManifest.xml:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
.....
 <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

  <application
        android:name="io.flutter.app.FlutterApplication"
        ...
        >
<!--Set activity launchMode to singleTask, if you want to prevent creating new activity instance everytime there is a new intent.-->
    <activity
            android:name=".MainActivity"
            android:launchMode="singleTask"
            android:theme="@style/LaunchTheme"
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
            android:hardwareAccelerated="true"
            android:windowSoftInputMode="adjustResize">

            <!--TODO:  Add this filter, if you want support opening urls into your app-->
            <intent-filter>
               <action android:name="android.intent.action.VIEW" />
               <category android:name="android.intent.category.DEFAULT" />
               <category android:name="android.intent.category.BROWSABLE" />
               <data
                   android:scheme="https"
                   android:host="example.com"
                   android:pathPrefix="/invite"/>
            </intent-filter>

            <!--TODO:  Add this filter, if you want support opening files into your app-->
            <intent-filter>
              <action android:name="android.intent.action.VIEW" />
              <category android:name="android.intent.category.DEFAULT" />
              <data
                   android:mimeType="*/*"
                   android:scheme="content" />
            </intent-filter>

             <!--TODO: Add this filter, if you want to support sharing text into your app-->
            <intent-filter>
               <action android:name="android.intent.action.SEND" />
               <category android:name="android.intent.category.DEFAULT" />
               <data android:mimeType="text/*" />
            </intent-filter>

            <!--TODO: Add this filter, if you want to support sharing images into your app-->
            <intent-filter>
                <action android:name="android.intent.action.SEND" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:mimeType="image/*" />
            </intent-filter>

            <intent-filter>
                <action android:name="android.intent.action.SEND_MULTIPLE" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:mimeType="image/*" />
            </intent-filter>

             <!--TODO: Add this filter, if you want to support sharing videos into your app-->
            <intent-filter>
                <action android:name="android.intent.action.SEND" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:mimeType="video/*" />
            </intent-filter>
            <intent-filter>
                <action android:name="android.intent.action.SEND_MULTIPLE" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:mimeType="video/*" />
            </intent-filter>

            <!--TODO: Add this filter, if you want to support sharing any type of files-->
            <intent-filter>
                <action android:name="android.intent.action.SEND" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:mimeType="*/*" />
            </intent-filter>
            <intent-filter>
                <action android:name="android.intent.action.SEND_MULTIPLE" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:mimeType="*/*" />
            </intent-filter>
      </activity>

  </application>
</manifest>
....

iOS

1. Create Share Extension

  • Using Xcode, go to File/New/Target and Choose "Share Extension".
  • Give it a name, i.e., "Share Extension".
  • Choose project "Runner", embed in "Runner".

Make sure the deployment target for Runner.app and the share extension is the same.

2. Replace your ios/Share Extension/Info.plist with the following:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>AppGroupId</key>
    <string>$(CUSTOM_GROUP_ID)</string>
	<key>CFBundleVersion</key>
	<string>$(FLUTTER_BUILD_NUMBER)</string>
	<key>NSExtension</key>
	<dict>
		<key>NSExtensionAttributes</key>
        <dict>
            <key>PHSupportedMediaTypes</key>
               <array>
                    <!--TODO: Add this flag, if you want to support sharing video into your app-->
                   <string>Video</string>
                   <!--TODO: Add this flag, if you want to support sharing images into your app-->
                   <string>Image</string>
               </array>
            <key>NSExtensionActivationRule</key>
            <dict>
                <!--TODO: Add this flag, if you want to support sharing text into your app-->
                <key>NSExtensionActivationSupportsText</key>
                <true/>
                <!--TODO: Add this tag, if you want to support sharing urls into your app-->
            	<key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
            	<integer>1</integer>
            	<!--TODO: Add this flag, if you want to support sharing images into your app-->
                <key>NSExtensionActivationSupportsImageWithMaxCount</key>
                <integer>100</integer>
                <!--TODO: Add this flag, if you want to support sharing video into your app-->
                <key>NSExtensionActivationSupportsMovieWithMaxCount</key>
                <integer>100</integer>
                <!--TODO: Add this flag, if you want to support sharing other files into your app-->
                <!--Change the integer to however many files you want to be able to share at a time-->
				<key>NSExtensionActivationSupportsFileWithMaxCount</key>
				<integer>1</integer>
            </dict>
        </dict>
		<key>NSExtensionMainStoryboard</key>
		<string>MainInterface</string>
		<key>NSExtensionPointIdentifier</key>
		<string>com.apple.share-services</string>
	</dict>
  </dict>
</plist>

3. Add the following to your ios/Runner/Info.plist:

...
<key>AppGroupId</key>
<string>$(CUSTOM_GROUP_ID)</string>
<key>CFBundleURLTypes</key>
	<array>
		<dict>
			<key>CFBundleTypeRole</key>
			<string>Editor</string>
			<key>CFBundleURLSchemes</key>
			<array>
				<string>ShareMedia-$(PRODUCT_BUNDLE_IDENTIFIER)</string>
			</array>
		</dict>
	</array>
...

4. Add the following to your ios/Runner/Runner.entitlements:

....
    <!--TODO:  Add this tag, if you want support opening urls into your app-->
    <key>com.apple.developer.associated-domains</key>
    <array>
        <string>applinks:example.com</string>
    </array>
....

5. Add the following to your ios/Podfile:

...
target 'Runner' do
  use_frameworks!
  use_modular_headers!

  flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))

  # Share Extension is name of Extension which you created which is in this case 'Share Extension'
  target 'Share Extension' do
    inherit! :search_paths
  end
end
...

6. Add Runner and Share Extension in the same group

  • Go to Signing & Capabilities tab and add App Groups capability in BOTH Targets: Runner and Share Extension
  • Add a new container with the name of your choice. For example group.MyContainer in the example project its group.com.walter.ShareExtention
  • Add User-defined(Build Settings -> +) string CUSTOM_GROUP_ID in BOTH Targets: Runner and Share Extension and set value to group id created above. You can use different group ids depends on your flavor schemes

7. Go to Build Phases of your Runner target and move Embed Foundation Extension to the top of Thin Binary.

8. Choose Your Share Extension Implementation

You have two options for implementing your Share Extension:

Option A: Seamless Sharing (NEW - No Compose Dialog)

For WhatsApp-style seamless sharing without a compose dialog:

// If you get no such module 'file_share_intent' error. 
// Go to Build Phases of your Runner target and
// move `Embed Foundation Extension` to the top of `Thin Binary`. 
import file_share_intent

class ShareViewController: RSIBaseShareViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Configure seamless sharing
        showUI = true          // Show minimal loading indicator (optional)
        processingMessage = "Sharing..."
        autoRedirect = true    // Automatically redirect after processing
    }
    
    // Optional: Custom behavior after attachments are processed
    override func onAttachmentsProcessed() {
        // Add custom logic here if needed
        super.onAttachmentsProcessed()
    }
}

Option B: Traditional Compose Dialog (LEGACY)

For traditional sharing with a compose dialog where users can add messages:

// If you get no such module 'file_share_intent' error. 
// Go to Build Phases of your Runner target and
// move `Embed Foundation Extension` to the top of `Thin Binary`. 
import file_share_intent

class ShareViewController: RSIShareViewController {
      
    // Use this method to return false if you don't want to redirect to host app automatically.
    // Default is true
    override func shouldAutoRedirect() -> Bool {
        return false
    }
    
    // Optional: Customize the Post button text
    override func presentationAnimationDidFinish() {
        super.presentationAnimationDidFinish()
        navigationController?.navigationBar.topItem?.rightBarButtonItem?.title = "Send"
    }
}

Which Option to Choose?

  • Use Option A (RSIBaseShareViewController) for seamless sharing like WhatsApp, Instagram, etc.
  • Use Option B (RSIShareViewController) for traditional sharing with user message input like Mail, Messages, etc.

Compiling issues and their fixes

  • Error: No such module 'file_share_intent'

    • Fix: Go to Build Phases of your Runner target and move Embed Foundation Extension to the top of Thin Binary.
  • Error: App does not build after adding Share Extension?

    • Fix: Check Build Settings of your share extension and remove everything that tries to import Cocoapods from your main project. i.e. remove everything under Linking/Other Linker Flags
  • You might need to disable bitcode for the extension target

  • Error: Invalid Bundle. The bundle at 'Runner.app/Plugins/Sharing Extension.appex' contains disallowed file 'Frameworks'

Full Example

main.dart

import 'package:flutter/material.dart';
import 'dart:async';

import 'package:file_share_intent/file_share_intent.dart';

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

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  late StreamSubscription _intentSub;
  final _sharedFiles = <SharedMediaFile>[];

  @override
  void initState() {
    super.initState();

    // Listen to media sharing coming from outside the app while the app is in the memory.
    _intentSub = FileShareIntent.instance.getMediaStream().listen((value) {
      setState(() {
        _sharedFiles.clear();
        _sharedFiles.addAll(value);

        print(_sharedFiles.map((f) => f.toMap()));
      });
    }, onError: (err) {
      print("getIntentDataStream error: $err");
    });

    // Get the media sharing coming from outside the app while the app is closed.
    FileShareIntent.instance.getInitialMedia().then((value) {
      setState(() {
        _sharedFiles.clear();
        _sharedFiles.addAll(value);
        print(_sharedFiles.map((f) => f.toMap()));

        // Tell the library that we are done processing the intent.
        FileShareIntent.instance.reset();
      });
    });
  }

  @override
  void dispose() {
    _intentSub.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    const textStyleBold = const TextStyle(fontWeight: FontWeight.bold);
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Plugin example app'),
        ),
        body: Center(
          child: Column(
            children: <Widget>[
              Text("Shared files:", style: textStyleBold),
              Text(_sharedFiles
                      .map((f) => f.toMap())
                      .join(",\n****************\n")),
            ],
          ),
        ),
      ),
    );
  }
}

Libraries

file_share_intent