Letting users broadcast their achievements, forward referral links, or export generated documents is a core social feature for modern mobile applications. Instead of writing custom platform channels for Android's Intent system and iOS's UIActivityViewController, the share_plus package hooks directly into the native sharing sheet on Android, iOS, macOS, Windows, and Linux. This provides a familiar, OS-level interface that instantly connects your application to WhatsApp, Mail, Messages, and whatever else the user has installed.
In this guide, we will implement text and URL sharing before moving on to handling multiple file attachments. We will look at how to properly generate temporary files for export, handle share results to verify user completion, and fix the notorious iPad crash that catches out many developers. By the end of this tutorial, you will have a robust sharing architecture that works flawlessly across different form factors.
Before writing any Dart code, ensure you run flutter pub add share_plus in your terminal. For the file generation examples later in this guide, you will also need the path_provider package, so it is highly recommended to run flutter pub add path_provider alongside it.

The Complete Flutter Guide: Build Android, iOS and Web apps
Go from scratch to building industry-standard apps with Riverpod, Firebase, animations, REST APIs, and more.
Enrol nowSharing Simple Text and URLs
The most common use case for the share sheet is sending a string of text, often with a URL. Current share_plus uses SharePlus.instance.share() with a ShareParams object. Put the main copy in text and, when useful, add a subject; receiving applications decide whether they honour that subject.
import 'package:flutter/material.dart';
import 'package:share_plus/share_plus.dart';
class SimpleShareExample extends StatelessWidget {
const SimpleShareExample({Key? key}) : super(key: key);
Future<void> _shareContent() async {
await SharePlus.instance.share(
ShareParams(
text: 'Check out this Flutter course at https://codingliquids.com!',
subject: 'Learning Flutter',
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Text Share')),
body: Center(
child: ElevatedButton.icon(
onPressed: _shareContent,
icon: const Icon(Icons.share),
label: const Text('Share Link'),
),
),
);
}
}
When the user taps this button, the native OS takes over, pausing your Flutter UI and presenting the system dialogue. Because you are handing a standard URL string over to the OS, any app that registers itself as capable of handling plain text will appear in the list.
Preventing iPad Crashes with sharePositionOrigin
If you run the above code on an iPhone or an Android device, it works perfectly. However, if you run it on an iPad, the app will instantly crash. Apple's design guidelines dictate that on larger screens, context menus and share sheets must appear as a popover anchored to the specific UI element the user interacted with. If you do not provide these coordinates, iOS throws a fatal exception.
To fix this, we must pass the sharePositionOrigin parameter, which requires a Rect representing the button's position and size. We can extract this using the BuildContext of the widget that triggered the action.
Future<void> _shareSafelyOnPad(BuildContext context) async {
// 1. Find the RenderBox of the current context
final box = context.findRenderObject() as RenderBox?;
if (box == null) return;
// 2. Calculate the global position and size
final rect = box.localToGlobal(Offset.zero) & box.size;
// 3. Pass the rect to sharePositionOrigin
await SharePlus.instance.share(
ShareParams(
text: 'Look at this feature!',
subject: 'App update',
sharePositionOrigin: rect,
),
);
}
Because finding the RenderObject requires a BuildContext associated with the button itself, ensure you are passing the context of the button (often using a Builder widget) rather than the context of the entire Scaffold. Otherwise, the popover will anchor to the top-left corner of the screen.
Sharing Images and Documents With ShareParams
Share physical files such as images, PDFs, or videos by putting XFile objects in ShareParams.files. Older static methods including Share.shareFiles and Share.shareXFiles are deprecated; the instance API handles text, URIs, and files through one parameter object.
To share files, you must provide a list of local file paths. You cannot pass a raw HTTP URL to an image and expect the native share sheet to download it for you. If your image lives on the internet, you must download it to the device first.
import 'package:flutter/material.dart';
import 'package:share_plus/share_plus.dart';
class FileShareExample extends StatelessWidget {
final String localImagePath;
const FileShareExample({Key? key, required this.localImagePath}) : super(key: key);
Future<void> _shareImage(BuildContext context) async {
final box = context.findRenderObject() as RenderBox?;
final rect = box != null ? box.localToGlobal(Offset.zero) & box.size : null;
final xFile = XFile(localImagePath);
await SharePlus.instance.share(
ShareParams(
files: [xFile],
text: 'Here is the photo I took!',
subject: 'My holiday',
sharePositionOrigin: rect,
),
);
}
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () => _shareImage(context),
child: const Text('Share Image'),
);
}
}
Notice that you can still pass text and subject alongside your files. When a user selects an app like Gmail, the file becomes an attachment, the subject becomes the email subject, and the text becomes the email body.

The Complete Flutter Guide: Build Android, iOS and Web apps
Go from scratch to building industry-standard apps with Riverpod, Firebase, animations, REST APIs, and more.
Enrol nowCreating and Sharing Temporary Files on the Fly
A common requirement is generating a report, receipt, or CSV export dynamically and immediately offering it to the user. For a path-backed XFile, write the generated data to the device's temporary directory before invoking the share sheet.
This is where the path_provider package becomes invaluable. We can grab the temporary directory, write our string or binary data to a new file, and then pass that file's path to share_plus.
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:share_plus/share_plus.dart';
import 'package:path_provider/path_provider.dart';
class ExportDataExample extends StatelessWidget {
const ExportDataExample({Key? key}) : super(key: key);
Future<void> _generateAndShareCsv(BuildContext context) async {
try {
// 1. Generate your data
const csvData = "Name,Score\nAlice,95\nBob,82\nCharlie,88";
// 2. Get the temporary directory
final tempDir = await getTemporaryDirectory();
final file = await File('${tempDir.path}/leaderboard.csv').create();
// 3. Write data to the file
await file.writeAsString(csvData);
// 4. Share the file
final box = context.findRenderObject() as RenderBox?;
final rect = box != null ? box.localToGlobal(Offset.zero) & box.size : null;
await SharePlus.instance.share(
ShareParams(
files: [XFile(file.path)],
text: 'Latest leaderboard statistics',
sharePositionOrigin: rect,
),
);
} catch (e) {
debugPrint('Error generating file: $e');
}
}
@override
Widget build(BuildContext context) {
return ElevatedButton.icon(
onPressed: () => _generateAndShareCsv(context),
icon: const Icon(Icons.file_download),
label: const Text('Export CSV'),
);
}
}
The operating system may purge the temporary directory when it needs space, but that is not a prompt cleanup guarantee. Give generated exports unique names and periodically remove files your app created, for example during a later app start. Keep a file available for the duration of the share operation rather than deleting it immediately after opening the sheet.
Tracking User Actions with ShareResult
SharePlus.instance.share() returns a Future<ShareResult>, so you can distinguish a selected action, a dismissed sheet, and an unavailable result where the platform reports those states. Do not use it as proof that content reached another person.
Future<void> _shareWithFeedback() async {
final result = await SharePlus.instance.share(
ShareParams(text: 'Join me on this app!'),
);
if (result.status == ShareResultStatus.success) {
debugPrint('The user selected a share-sheet action.');
// Record an attempted/selected share, not confirmed delivery.
} else if (result.status == ShareResultStatus.dismissed) {
debugPrint('The user dismissed the share sheet.');
} else if (result.status == ShareResultStatus.unavailable) {
debugPrint('A share result is unavailable on this platform.');
}
}
Platform semantics differ. On Android and macOS, success can mean that the user selected an action rather than that the target application delivered the content. Even iOS actions control what completion information they return. Treat the result as share-sheet interaction only: never grant a reward, satisfy a referral condition, or claim delivery from this value alone.
Common Mistakes and Edge Cases
Use an application-controlled temporary file for generated exports and keep it readable until sharing has finished. share_plus handles the platform URI permissions; moving a file to public external storage is unnecessary and can introduce scoped-storage or permission problems. A document that is part of the user's lasting data can remain in the documents directory, while disposable exports need an explicit cleanup policy.
Another pitfall is freezing the UI. Generating large PDFs or copying massive video files before calling SharePlus.instance.share() can block the main isolate. Move CPU-heavy generation to an isolate; awaiting an asynchronous file operation keeps the event loop free but does not make CPU-heavy synchronous work non-blocking.
Lastly, remember that the subject parameter is entirely at the mercy of the receiving application. Do not place critical information exclusively in the subject line, as users selecting WhatsApp, SMS, or Slack will only see the text parameter. Always ensure the text string contains the full context.
Verifying Your Share Flows on Devices
Testing sharing functionality on simulators can be misleading. The iOS Simulator provides a very stripped-down version of the UIActivityViewController, often only showing "Copy" or "Save to Files". Similarly, the Android Emulator includes dummy contacts and generic intents, but lacks real-world targets like social media apps.
To properly verify your implementation, deploy your application to a physical device. Test the flow by selecting an email client to verify the subject mapping, selecting a messaging app to verify the text formatting, and selecting a cloud storage app (like Google Drive or iCloud) to ensure your XFile attachments upload without corruption. Also cancel the sheet and inspect the result: supported platforms report dismissed, while others can return unavailable.
Further reads
Keep going with these related tutorials from this site.
- Flutter: The Complete Guide — the full Flutter learning path on this site
- Flutter url_launcher: Open Links, Email, Phone, and Maps — learn how to open URLs directly without the share sheet
- Flutter image_picker: Camera and Gallery Access — allow users to select images before sharing them
- Flutter permission_handler: Request and Check Permissions — manage storage permissions for advanced file handling
- Flutter Build APK and App Bundle for Google Play — prepare your app for production release