receive_sharing_intent

repository·master·Indexed 18 days ago

https://github.com/kasemjaffer/receive_sharing_intent

A Flutter plugin that enables applications to receive shared content, including text, URLs, images, videos, and files, from other apps on Android and iOS. It provides tools for configuring Android Intent Filters, setting up iOS Share Extensions via Swift Package Manager, and handling shared media through both streams for running apps and initial media fetches for cold starts.

Tokens
6K
Snippets
14
Records
15
Agent score
62%

What's inside receive_sharing_intent

  1. Customize iOS launch screen assets

    master

    To change the image displayed during the app's launch on iOS, replace the existing image files within the example/ios/Runner/Assets.xcassets/LaunchImage.imageset/ directory with your own assets.

    Alternatively, you can manage these assets using Xcode:

    1. Open the iOS project workspace using open ios/Runner.xcworkspace.
    2. In the Xcode Project Navigator, navigate to Runner/Assets.xcassets.
    3. Locate the LaunchImage asset set and drag and drop your desired images into it.
    open ios/Runner.xcworkspace
  2. Enable Swift Package Manager for iOS

    master

    The receive_sharing_intent plugin is distributed exclusively as a Swift Package (SPM). It does not support CocoaPods. You must enable SPM in your Flutter environment and manually link the library to your Share Extension target.

    1. Enable SPM: Run flutter config --enable-swift-package-manager.
    2. Link Library to Extension: In Xcode, select your Share Extension target $\rightarrow$ General tab $\rightarrow$ Frameworks and Libraries. Click + and select the FlutterGeneratedPluginSwiftPackage library from the receive_sharing_intent package.

    Note for CocoaPods users: If you are migrating from CocoaPods, remove ios/Podfile, run pod deintegrate in the ios/ directory, and remove all references to receive-sharing-intent in your .xcconfig and .pbxproj files.

    flutter config --enable-swift-package-manager
  3. Adopt UISceneDelegate for iOS Lifecycle

    master

    Newer iOS versions deliver shared URLs through UISceneDelegate rather than AppDelegate. To ensure the plugin continues to work, your app must adopt a SceneDelegate that subclasses FlutterSceneDelegate.

    If your app uses other URL-handling libraries (like flutter_branch_sdk or uni_links), you must manually pass the lifecycle events to ReceiveSharingIntentPlugin to prevent them from being intercepted by other handlers.

    import Flutter
    import UIKit
    import receive_sharing_intent
    
    class SceneDelegate: FlutterSceneDelegate {
        // Called on a cold start
        override func scene(
            _ scene: UIScene,
            willConnectTo session: UISceneSession,
            options connectionOptions: UIScene.ConnectionOptions
        ) {
            // Pass events to the plugin to prevent other libraries from absorbing links
            _ = ReceiveSharingIntentPlugin.instance.scene(scene, willConnectTo: session, options: connectionOptions)
            super.scene(scene, willConnectTo: session, options: connectionOptions)
        }
    
        // Called while the app is already running (warm start)
        override func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) { 
            if ReceiveSharingIntentPlugin.instance.scene(scene, openURLContexts: URLContexts) {
                return
            }
            super.scene(scene, openURLContexts: URLContexts)
        }
    }
  4. Setup Share Extension for iOS

    master

    To enable sharing from other iOS apps into your Flutter app, you must create a dedicated Share Extension target in Xcode.

    1. Create Target: In Xcode, go to File/New/Target and choose Share Extension. Give it a name (e.g., "Share Extension").
    2. Sync Deployment Targets: Ensure the deployment target for both Runner.app and your new Share Extension is identical.
    3. Configure Info.plist (Extension): Replace your ios/Share Extension/Info.plist with a configuration that defines AppGroupId and NSExtensionAttributes. Use PHSupportedMediaTypes to specify supported media (e.g., Video, Image) and NSExtensionActivationRule to define rules for text, URLs, images, videos, and files.
    4. Configure Info.plist (Runner): Update ios/Runner/Info.plist to include the AppGroupId, CFBundleURLTypes (for deep linking back to your app), and NSPhotoLibraryUsageDescription for photo access.
    5. Configure Entitlements: Add com.apple.developer.associated-domains to ios/Runner/Runner.entitlements if you need to support opening URLs into your app.
    6. App Groups: In the Signing & Capabilities tab, add the App Groups capability to BOTH the Runner and Share Extension targets. Create a container (e.g., group.com.example.app) and add a User-defined build setting CUSTOM_GROUP_ID to BOTH targets with that same value.
    <!-- Example NSExtensionActivationRule snippet for Info.plist -->
    <key>NSExtensionActivationRule</key>
    <dict>
        <key>NSExtensionActivationSupportsText</key>
        <true/>
        <key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
        <integer>1</integer>
        <key>NSExtensionActivationSupportsImageWithMaxCount</key>
        <integer>100</integer>
        <key>NSExtensionActivationSupportsMovieWithMaxCount</key>
        <integer>100</integer>
        <key>NSExtensionActivationSupportsFileWithMaxCount</key>
        <integer>1</integer>
    </dict>
  5. Configure Android Intent Filters

    master

    To allow your Android app to receive shared content, you must add specific <intent-filter> blocks to your android/app/src/main/AndroidManifest.xml.

    Key Configuration Steps:

    1. Permissions: Ensure you have <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> if accessing files.
    2. Activity Launch Mode: Set android:launchMode="singleTask" on your .MainActivity to prevent creating a new activity instance every time a new intent is received.
    3. Intent Filters: Add filters based on the type of content you want to support (URLs, text, images, videos, or generic files).
    <manifest xmlns:android="http://schemas.android.com/apk/res/android">
     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    
      <application>
        <activity
                android:name=".MainActivity"
                android:launchMode="singleTask"
                ...
                >
    
                <!-- Support opening URLs -->
                <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>
    
                <!-- Support opening files -->
                <intent-filter>
                  <action android:name="android.intent.action.VIEW" />
                  <category android:name="android.intent.category.DEFAULT" />
                  <data android:mimeType="*/*" android:scheme="content" />
                </intent-filter>
    
                <!-- Support sharing text -->
                <intent-filter>
                   <action android:name="android.intent.action.SEND" />
                   <category android:name="android.intent.category.DEFAULT" />
                   <data android:mimeType="text/*" />
                </intent-filter>
    
                <!-- Support sharing images -->
                <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>
    
                <!-- Support sharing videos -->
                <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>
    
                <!-- 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>
  6. Troubleshoot iOS Compilation Errors

    master

    Common issues when setting up the iOS Share Extension:

    • Error: No such module 'receive_sharing_intent' (in the Share Extension)

      • Fix: Ensure you have added the FlutterGeneratedPluginSwiftPackage library to the Share Extension target under General $\rightarrow$ Frameworks and Libraries in Xcode.
    • Error: Unable to resolve module dependency: 'receive_sharing_intent'

      • Fix: Verify that Swift Package Manager is enabled via flutter config --enable-swift-package-manager and that the Share Extension target is correctly linked to the package.
    • Error: Invalid Bundle. The bundle at 'Runner.app/Plugins/Sharing Extension.appex' contains disallowed file 'Frameworks'

    • Build Order Issue: If you encounter module errors, go to the Build Phases of your Runner target and move Embed Foundation Extension to the top of Thin Binary.

  7. Implement sharing intent handling in Flutter

    master

    To handle shared media or files in your Flutter application, you must handle two distinct scenarios: when the app is already running in the background (via a Stream) and when the app is launched from a closed state (via an initial media fetch).

    1. App in memory: Use ReceiveSharingIntent.instance.getMediaStream() to listen for new sharing intents while the app is running. This returns a Stream<List<SharedMediaFile>>.
    2. App closed: Use ReceiveSharingIntent.instance.getInitialMedia() to retrieve the intent that launched the app. This returns a Future<List<SharedMediaFile>>.
    3. Cleanup: After processing the initial media, call ReceiveSharingIntent.instance.reset() to clear the intent. Always cancel your stream subscription in the dispose() method of your widget to prevent memory leaks.
    import 'package:flutter/material.dart';
    import 'dart:async';
    import 'package:receive_sharing_intent/receive_sharing_intent.dart';
    
    // ... inside a State class ...
    
    late StreamSubscription _intentSub;
    final _sharedFiles = <SharedMediaFile>[];
    
    @override
    void initState() {
      super.initState();
    
      // 1. Listen to media sharing while the app is in memory
      _intentSub = ReceiveSharingIntent.instance.getMediaStream().listen((value) {
        setState(() {
          _sharedFiles.clear();
          _sharedFiles.addAll(value);
        });
      }, onError: (err) {
        print("getIntentDataStream error: $err");
      });
    
      // 2. Get media sharing while the app was closed
      ReceiveSharingIntent.instance.getInitialMedia().then((value) {
        setState(() {
          _sharedFiles.clear();
          _sharedFiles.addAll(value);
        });
    
        // 3. Tell the library that we are done processing the intent
        ReceiveSharingIntent.instance.reset();
      });
    }
    
    @override
    void dispose() {
      _intentSub.cancel();
      super.dispose();
    }
  8. Customize the Share Extension UI with RSIShareViewController

    master

    The RSIShareViewController (which your extension's ShareViewController should inherit from) allows you to control how shared content is presented to the user.

    Redirect Behavior

    • shouldAutoRedirect() -> Bool (Default: true): If true, no UI is shown. The content is processed and the user is immediately sent to your host app. If false, a built-in compose sheet is shown.

    Built-in Compose UI Customization

    If you set shouldAutoRedirect to false, you can customize the following members:

    • placeholder: The text shown in the empty message field.
    • sendButtonTitle: The label for the confirmation button.
    • isContentValid() -> Bool: A validation check for the Send button (default is always valid).
    • didSelectPost(): Called when the user taps Send. Use saveAndRedirect(message: contentText) to save the typed message and redirect.
    • didSelectCancel(): Called when the user cancels. Use cancel() to end the request.
    import receive_sharing_intent
    
    class ShareViewController: RSIShareViewController {
    
        override func shouldAutoRedirect() -> Bool { false }
    
        // Placeholder shown in the empty message field.
        override var placeholder: String { "Add a caption…" }
    
        // Title of the confirm button
        override var sendButtonTitle: String { "Send to Example" }
    
        // Gate the Send button on your own validation.
        override func isContentValid() -> Bool { !contentText.isEmpty }
    
        // Called when the user taps Send.
        override func didSelectPost() { saveAndRedirect(message: contentText) }
    
        // Called when the user taps Cancel.
        override func didSelectCancel() { cancel() }
    }
  9. Retrieve initial shared media via getInitialMedia()

    master

    Use getInitialMedia() to retrieve the media files that were shared with the app when it was launched from a cold start. This method returns a Future<List<SharedMediaFile>>. If no media is shared, it returns an empty list.

    List<SharedMediaFile> initialMedia = await receiveSharingIntent.getInitialMedia();
    for (var file in initialMedia) {
      print('Shared file path: ${file.path}');
    }
  10. Listen to shared media updates via getMediaStream()

    master

    Use getMediaStream() to listen to a continuous stream of shared media files. This is useful for handling sharing intents while the app is already running in the background or foreground. The stream emits a List<SharedMediaFile> whenever new content is shared.

    receiveSharingIntent.getMediaStream().listen((List<SharedMediaFile> sharedFiles) {
      sharedFiles.forEach((file) {
        print('New shared file: ${file.path}');
      });
    }, onError: (err) {
      print('Error receiving sharing intent: $err');
    });