file_share_intent 3.0.1
file_share_intent: ^3.0.1 copied to clipboard
Implements Android Intent and iOS Share Extension to receive shared content.
file_share_intent #
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) |
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
andShare Extension
- Add a new container with the name of your choice. For example
group.MyContainer
in the example project itsgroup.com.walter.ShareExtention
- Add User-defined(
Build Settings -> +
) stringCUSTOM_GROUP_ID
in BOTH Targets:Runner
andShare 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 ofThin Binary
.
- Fix: Go to Build Phases of your Runner target and move
-
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
- 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
-
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 #
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")),
],
),
),
),
);
}
}