🤳 Effortless Sharing: From external apps to your Flutter app in no time
Tags: flutter, dart, beginners, tutorial
Published: 2023-02-14 03:35:01

---
title: 🤳 Effortless Sharing: From external apps to your Flutter app in no time
published: true
date: 2023-02-14 03:31:17 UTC
tags: flutter, dart, beginners, tutorial
canonical_url: https://saileshdahal.com.np/sharing-media-from-external-to-flutter-app
---
![Cover image of article: Effortless sharing from external apps to your flutter app in no time](https://cdn.hashnode.com/res/hashnode/image/upload/v1676265778828/26014b9d-cf49-48ba-b601-03022f207aa4.png)

Sharing things like files, pictures, videos, and texts from external apps can be difficult, especially when done without any external packages.

Facilitating content sharing via external apps boosts user engagement, improves the user experience, and helps to spread the word about our app.

In this article, we'll be taking advantage of the awesome [share\_handler](https://pub.dev/packages/share_handler) package to make sharing from external apps to our Flutter app really easy!

We will be using a multi-flavored flutter app as our starting point for this tutorial. You can [find the repo **here**](https://github.com/saileshbro/sharing_into_flutter_app.git)

> **🥳 Sounds great, right?**

Let's get started by adding `share_handler` as a dependency in `pubspec.yaml`

## Android setup 🤖

We will start with the easy setup. In our `android/app/src/main/AndroidManifest.xml` file, we will add the intent filters and metadata for the media types we want to support.

For text-only support

```xml
<intent-filter>
	<action android:name="android.intent.action.SEND" />
	<category android:name="android.intent.category.DEFAULT" />
	<data android:mimeType="text/*" />
</intent-filter>
```

For image-only support

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

For video-only support

```xml
<intent-filter>
	<action android:name="android.intent.action.SEND" />
	<category android:name="android.intent.category.DEFAULT" />
	<data android:mimeType="video/*" />
</intent-filter>
```

If you want to support any type of files

```xml
<intent-filter>
	<action android:name="android.intent.action.SEND" />
	<category android:name="android.intent.category.DEFAULT" />
	<data android:mimeType="**/*" />
</intent-filter>
```

> **💡 Tip:** You can add `SHARE_MULTIPLE` action, if you want to support more than one media sharing.

```xml
<intent-filter>
	<action android:name="android.intent.action.SEND_MULTIPLE" />
	<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="video/*" />
</intent-filter>
<intent-filter>
	<action android:name="android.intent.action.SEND_MULTIPLE" />
	<category android:name="android.intent.category.DEFAULT" />
	<data android:mimeType="*/*" />
</intent-filter>
```

> **💡Tip:** You can change the `android:mimeType` accordingly for the file types.

Also, if you want to prevent the incoming shares from opening a new activity each time, you can prevent this by changing `android:launchMode` to `singleTask`

```xml
<activity
...
android:lunchMode="singleTask"
...
>
...
</activity>
```

For adding our app in share suggestions and shortcuts, we can create a new file `share_targets.xml` in `android/app/src/main/res/xml/share_targets.xml`

```xml
<?xml version="1.0" encoding="utf-8"?>
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
  <share-target
    android:targetClass="@{'np.com.saileshdahal.sharing'+@strings/app_id_suffix+'.dynamic_share_target'}">
    <data android:mimeType="*/*" />
    <category
      android:name="@{'np.com.saileshdahal.sharing'+@strings/app_id_suffix+'.dynamic_share_target'}" />
  </share-target>
</shortcuts>
```

> **💡 Tip:** Make sure you have `app_id_suffix` resource string defined for each flavor.

```apache
resValue "string", "app_id_suffix", applicationIdSuffix
```

If you are **not using flavors**, you can define this in the following format.

```xml
<?xml version="1.0" encoding="utf-8"?>
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
    <share-target android:targetClass="{your.package.identifier}.MainActivity">
        <data android:mimeType="*/*" />
        <category android:name="{your.package.identifier}.dynamic_share_target" />
    </share-target>
</shortcuts>
```

Now, we will add a metadata field in the `AndroidManifest.xml` file.

```xml
<meta-data
	android:name="android.service.chooser.chooser_target_service"
	android:value="androidx.sharetarget.ChooserTargetServiceCompat" />
<meta-data
	android:name="android.app.shortcuts"
	android:resource="@xml/share_targets" />
```

Once done, if you try to share something, you should see the app icon on the share page.

![App showing in share intent](https://cdn.hashnode.com/res/hashnode/image/upload/v1676289713546/0efdd6d6-d920-41e8-91a9-457f328656b3.png)

---

## iOS setup 📱

### Update Runner `Info.plist`

For iOS, let's get started by editing our `Info.plist` file. We will be registering a deep link that will be launched by Share Extension.

For registering the deep link, add the following in your `Info.plist`

```xml
<key>CFBundleURLTypes</key>
<array>
    <dict>
        <key>CFBundleTypeRole</key>
        <string>Editor</string>
        <key>CFBundleURLSchemes</key>
        <array>
            <string>ShareMedia-$(PRODUCT_BUNDLE_IDENTIFIER)</string>
        </array>
    </dict>
</array>
```

If you already have some entries for `CFBundleURLTypes`, you can add a new entry like this.

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

> **💡 Tip:** Make sure you have a user-defined variable `PRODUCT_BUNDLE_IDENTIFIER` for your iOS project.

Make sure you have an entry for `NSPhotoLibraryUsageDescription` if you are planning to share images.

```xml
<key>NSPhotoLibraryUsageDescription</key>
<string>Photos can be shared to and used in this app</string>
```

If you are planning to add `AirDrop` support, add the following entry in `Info.plist`.

```xml
<key>LSSupportsOpeningDocumentsInPlace</key>
<string>No</string>
<key>CFBundleDocumentTypes</key>
<array>
    <dict>
        <key>CFBundleTypeName</key>
        <string>ShareHandler</string>
        <key>LSHandlerRank</key>
        <string>Alternate</string>
        <key>LSItemContentTypes</key>
        <array>
            <string>public.file-url</string>
            <string>public.image</string>
            <string>public.text</string>
            <string>public.movie</string>
            <string>public.url</string>
            <string>public.data</string>
        </array>
    </dict>
</array>
```

> 💡 You can modify `LSItemContentTypes` array based on your need.

### Share Extension setup

Now for this part, we will create a share extension from Xcode.

* Open `ios` folder of your flutter project in Xcode.


```bash
open ios/Runner.xcworkspace
```

* Go to `File` -&gt; `New` -&gt; `Target`


![Image showing how to create a new target](https://cdn.hashnode.com/res/hashnode/image/upload/v1676289784911/315f2d44-700d-4c8f-b816-348a5bc99e74.png)

* Search for `Share Extension` and hit `Next`


![Image showing how to select a Share Extension](https://cdn.hashnode.com/res/hashnode/image/upload/v1676289798585/fb9216af-8b17-4b2b-85f2-2aa3e53028e2.png)

Now create the extension with `ShareExtension` name, and hit save. This will add a new target for your project.

> **🚨 Note:** Make sure the name is `ShareExtension`

![Create a new Share Extension Prompt](https://cdn.hashnode.com/res/hashnode/image/upload/v1676289845846/c3d57abe-9d1a-4180-b745-ad8eaa6924a5.png)

> **🚨 IMPORTANT:** Make sure the minimum deployment version of both targets is the same.

![Setting min deployment version to 14](https://cdn.hashnode.com/res/hashnode/image/upload/v1676289961422/192e05e0-0731-4b0a-b919-0af9063176f2.png)

For `Target` -&gt; `Runner`, set minimum deployment version to `14.0`.

![Setting min deployment version to 14](https://cdn.hashnode.com/res/hashnode/image/upload/v1676289979648/2af298bf-0818-439f-ab10-bfd83df83f0d.png)

Similarly for `Target`\-&gt; `ShareExtension`, set minimum deployment version to `14.0`

![Setting min deployment version to 14](https://cdn.hashnode.com/res/hashnode/image/upload/v1676289992228/c8a9c274-7260-42e2-b569-031d02cb8255.png)

Once this is done, open `ios/ShareExtension/ShareViewController.swift` and replace everything with following

```swift
import share_handler_ios_models

class ShareViewController: ShareHandlerIosViewController {}
```

> **🚨 Note:** Make sure to run `flutter pub get` and `pod install --repo-update`

If you have multiple flavors set up then we will also have to create multiple flavors for our share extension. We will create 3 different schemes for share extension too.

![Manage scheme option](https://cdn.hashnode.com/res/hashnode/image/upload/v1676290018979/59d15593-eef5-433e-82ab-b741d3f14eb4.png)

Go to `Manage Scheme`, and then we will duplicate and create 3 different schemes for `ShareExtension` as `ShareExtension-production`, `ShareExtension-development` and `ShareExtension-staging`.

Rename `ShareExtension` to `ShareExtension-production` that will handle one scheme, and then for staging, we can duplicate `ShareExtension-production` and rename it to `ShareExtension-staging` and assign the correct configuration.

![Create a new Share Extension scheme for staging](https://cdn.hashnode.com/res/hashnode/image/upload/v1676290061564/62c12ca3-be04-4ddd-8d4c-ed45d3254211.jpeg)

Similarly, we will create one scheme for `ShareExtension-development` as well.

![All final schemes](https://cdn.hashnode.com/res/hashnode/image/upload/v1676290073981/a45b3dde-0a31-4ebc-9b34-bc71bb3277a5.png)

> 🚨 **Note: You don't have to duplicate the schemes if you are not using flavors**.

### Share Extension `Info.plist` update

Once this is done, we will make some changes to `ios/ShareExtension/Info.plist`

```xml
<?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>CFBundleVersion</key>
    <string>$(FLUTTER_BUILD_NUMBER)</string>
    <key>NSExtension</key>
    <dict>
      <key>NSExtensionAttributes</key>
      <dict>
        <key>IntentsSupported</key>
        <array>
          <string>INSendMessageIntent</string>
        </array>
        <key>NSExtensionActivationRule</key>
        <dict>
		  <key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
          <integer>999</integer>
          <key>NSExtensionActivationSupportsWebPageWithMaxCount</key>
          <integer>999</integer>
          <key>NSExtensionActivationSupportsMovieWithMaxCount</key>
          <integer>999</integer>
          <key>NSExtensionActivationSupportsImageWithMaxCount</key>
          <integer>999</integer>
          <key>NSExtensionActivationSupportsFileWithMaxCount</key>
          <integer>999</integer>
          <key>NSExtensionActivationSupportsAttachmentsWithMaxCount</key>
          <integer>999</integer>
          <key>NSExtensionActivationSupportsText</key>
          <true />
        </dict>
        <key>PHSupportedMediaTypes</key>
        <array>
          <string>Video</string>
          <string>Image</string>
        </array>
      </dict>
      <key>NSExtensionMainStoryboard</key>
      <string>MainInterface</string>
      <key>NSExtensionPointIdentifier</key>
      <string>com.apple.share-services</string>
    </dict>
  </dict>
</plist>
```

Here you can make changes to the `NSExtensionActivationRule` field based on your requirements.

> Note: Here we have set the maximum file-sharing count to 999, but you can disable or enable them based on your requirements.

Once this is done, make sure we have different bundle identifiers for each flavor and configuration for `Share Extension`. If you click `Target` -&gt; `Share Extension` -&gt; `Singing & Capabilities` -&gt; `All`

![All bundle ids for share extension](https://cdn.hashnode.com/res/hashnode/image/upload/v1676290130000/678a49d8-2cef-4d4d-9218-fb118a2f387b.png)

> **🚨 Note:** Make sure the `ShareExtension` bundle identifier for each config has a `.ShareExtension` prefix compared to the `Runner`.

Once done, we need to assign both targets with a group identifier. For both `Runner` and `ShareExtension` target, go to `Signing & Capabilities`.

![Show how to add app group](https://cdn.hashnode.com/res/hashnode/image/upload/v1676290144337/b32f39ad-7e5b-4e46-aa55-97644a3e2b34.png)

Now, for each flavor, we will add an app group.

![Show how to add app group](https://cdn.hashnode.com/res/hashnode/image/upload/v1676290163014/d90f88b6-7357-42f5-b16b-0a89a81ea540.png)

![Show how to add app group](https://cdn.hashnode.com/res/hashnode/image/upload/v1676290176925/0b4f0856-0a10-43f2-aeae-b851091f53fa.png)

> 🚨 Note: Make sure to add the group id as `group.<bundle identifier>` for each flavor.

For `Debug-production`, `Release-production`, and `Profile-production`.

![add new group id for production](https://cdn.hashnode.com/res/hashnode/image/upload/v1676290196245/04fb9de6-227c-4a9a-bf46-038303eddf60.png)

> 💡 Tip: For matching group, you can select the check box

![option showing the group id](https://cdn.hashnode.com/res/hashnode/image/upload/v1676290267867/dac703ea-93d6-421a-8291-9545ce4dbef3.png)

For `Debug-development`, `Release-development`, and `Profile-development`.

![add new group id for development](https://cdn.hashnode.com/res/hashnode/image/upload/v1676290276924/2b178b2b-1bb3-42f4-aab3-d09fbb44bd25.png)

Similarly for `Debug-staging`, `Release-staging`, and `Profile-staging`

![add new group id for staging](https://cdn.hashnode.com/res/hashnode/image/upload/v1676290291524/0f017a91-0600-46ec-8d4e-0eedb61cb93b.png)

If you click on `Target` -&gt; `Runner` -&gt; `Signing & Capabilities` -&gt; `All`, you should see the following.

![All app groups linked](https://cdn.hashnode.com/res/hashnode/image/upload/v1676290303653/04773b29-42fe-4a3a-8361-de3adb195cce.png)

This will create `entitlements` files for each configuration.

Similarly for `ShareExtension` we can quickly create `.entitlements` files. In `ios/ShareExtension` we will create an entitlements file for each config and each flavor. We will create 9 entitlements files for each flavor (`production`, `development`, and `staging`), and `Debug`, `Release`, and `Profile` config.

For `Debug`, `Release`, and `Profile` configuration for `development` flavor, we will create three files `ShareExtensionRelease-development.entitlements`, `ShareExtensionDebug-development.entitlements`, and `ShareExtensionProfile-development.entitlements`, with the following contents.

```xml
<?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>com.apple.security.application-groups</key>
	<array>
		<string>group.np.com.saileshdahal.sharing.dev</string>
	</array>
</dict>
</plist>
```

Similarly, for each configuration of `production` and `staging` flavor, we will create three entitlements files.

By the end, we will have the following files

```apache
ShareExtensionDebug-development.entitlements
ShareExtensionDebug-production.entitlements
ShareExtensionDebug-staging.entitlements
ShareExtensionProfile-development.entitlements
ShareExtensionProfile-production.entitlements
ShareExtensionProfile-staging.entitlements
ShareExtensionRelease-development.entitlements
ShareExtensionRelease-production.entitlements
ShareExtensionRelease-staging.entitlements
```

Once this is done, link the entitlement files by clicking `+` button for app group capabilities for each flavor.

![reverify your entitlements file](https://cdn.hashnode.com/res/hashnode/image/upload/v1676290359941/a5681d0c-2d34-4a64-9d15-657cc78085ea.png)

> **💡Tip:** Make sure the entitlements files are correctly linked, else this may not work properly for each flavor.

Make sure, we have all the entitlements files linked up correctly. If we do everything right we will see the following.

![all linked group ids](https://cdn.hashnode.com/res/hashnode/image/upload/v1676290370952/47123d8c-caa9-4a75-9178-e2420c29dbb5.png)

![final entitlement files](https://cdn.hashnode.com/res/hashnode/image/upload/v1676290380424/6dbfe44f-3a78-4b7e-a677-5c04999e3a55.png)

> 🚨 For iOS setup without flavors

All the setup above can be taken reference for the apps without flavors as well. The only difference here is, we can have single entitlements file for each target, that is `Runner.entitlements` and `ShareExtension.entitlements`.

The bundle id for the `Share Extension` will be `np.com.saileshdahal.sharing.ShareExtension`, and the group bundle id will be `group.np.com.saileshdahal.sharing`

Now, for the final setup, we will make some changes in `Podfile` and register the dependency.

```ruby
target 'Runner' do
  use_frameworks!
  use_modular_headers!

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

  # Add this block
  target 'ShareExtension' do
    inherit! :search_paths
    pod "share_handler_ios_models", :path => ".symlinks/plugins/share_handler_ios/ios/Models"
  end
  # End of block

end
```

Once this is done, run `pod install --repo-update` in `ios` directory.

---

## 🧪 Testing time

Let's check if we can see our app when we share something.

![app showing in share intent](https://cdn.hashnode.com/res/hashnode/image/upload/v1676290414640/002962c1-c902-45e5-b31c-528e5ffbf932.png)

Here, we can see both flavors appear while we try to share images from photos.

---

## Getting shared media in Flutter

Now that we have both Android and iOS working, we can start implementing our share handler service to get the shared media.

The package exposes a stream of `SharedMedia`. We can subscribe to this stream and do side effects based on the shared media type.

Also, the package has a method to get the initial shared media. This will be helpful if the app is in terminated state, and we open the app from the share page.

I have added the code snippet from [`share_handler`](https://pub.dev/packages/share_handler)

```dart
class HomePage extends StatefulWidget {
  const HomePage({super.key});

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  StreamSubscription<SharedMedia>? _streamSubscription;
  SharedMedia? media;

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

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

  Future<void> initPlatformState() async {
    final handler = ShareHandlerPlatform.instance;
    media = await handler.getInitialSharedMedia();
    _streamSubscription = handler.sharedMediaStream.listen((SharedMedia media) {
      if (!mounted) return;
      setState(() => this.media = media);
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Share Handler'),
      ),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: <Widget>[
          Text(
            'Shared to conversation identifier: ${media?.conversationIdentifier}',
          ),
          const SizedBox(height: 10),
          Text('Shared text: ${media?.content}'),
          const SizedBox(height: 10),
          Text('Shared files: ${media?.attachments?.length}'),
          ...(media?.attachments ?? []).map((attachment) {
            final path = attachment?.path;
            if (path != null &&
                attachment?.type == SharedAttachmentType.image) {
              return Column(
                children: [
                  ElevatedButton(
                    onPressed: () {
                      ShareHandlerPlatform.instance.recordSentMessage(
                        conversationIdentifier:
                            'custom-conversation-identifier',
                        conversationName: 'John Doe',
                        conversationImageFilePath: path,
                        serviceName: 'custom-service-name',
                      );
                    },
                    child: const Text('Record message'),
                  ),
                  const SizedBox(height: 10),
                  Image.file(File(path)),
                ],
              );
            }
            return Text(
              '${attachment?.type} Attachment: ${attachment?.path}',
            );
          }),
        ],
      ),
    );
  }
}
```

Now, if we run this code and try to share an image from Photos, we should see something like this 🥳.

![working demo of the gif](https://cdn.hashnode.com/res/hashnode/image/upload/v1676290453387/c35ad321-b438-4991-b90e-02fc03cff5af.gif)

---

🎉 Congratulations, we've reached the end of this article on how to easily share content from external apps to your Flutter app using the share\_handler package! 🚀 Hopefully, you found this tutorial helpful and have learned a lot about effortless sharing in Flutter.

As always, the source code for this tutorial can be found on the GitHub repository at [`saileshbro/sharing_into_flutter_app`](https://github.com/saileshbro/sharing_into_flutter_app). If you found this article useful, don't forget to leave a ⭐️ and share it with others.

And if you're interested in learning more about setting up flavors in Flutter, be sure to check out [**🍰 Simplifying flavor setup in the existing Flutter app: A comprehensive guide**](https://saileshdahal.com.np/flavor-setup-flutter) to learn how to add different flavors to your app with unique configurations and build targets.

Thank you for joining me on this journey, and happy coding! 💻👨‍💻

## References

* [`share_handler`](https://pub.dev/packages/share_handler)