Skip to content

Sharing

Let users send a file from your app to any other app, and receive files other apps share into yours. @symbiote-native/sharing wraps expo-sharing so every SymbioteNative adapter can reach it, not just React. Outgoing share is two free functions in a shared core; the incoming half ships as useIncomingShare on React, Vue, Svelte and Solid, and injectIncomingShare on Angular.

OS platform Support
iOS ✅ live
Android ✅ live
Framework adapter Support
React ✅ live
Vue ✅ live
Angular ✅ live
Svelte ✅ live
Solid ✅ live

expo-sharing has two halves, and both are ported to every adapter.

Half Upstream API Here
Outgoing - hand a local file to another app shareAsync, isAvailableAsync ported in full
Incoming - receive files other apps share into your app useIncomingShare, getSharedPayloads, getResolvedSharedPayloadsAsync, clearSharedPayloads ported in full
import { useIncomingShare } from '@symbiote-native/sharing/react';
const { sharedPayloads, resolvedSharedPayloads, isResolving, error, clearSharedPayloads } =
useIncomingShare();

The payloads are read synchronously, resolved after mount, and re-read whenever the app returns to the foreground. refreshSharePayloads() forces a re-read. Resolving a shared URL may need the network. Vue returns a ComputedRef, Solid an Accessor, Svelte a { current } box, and Angular’s injectIncomingShare() a Signal.

Terminal window
npm install @symbiote-native/sharing

Scaffolding or extending a SymbioteNative app? npx @symbiote-native/cli new --sharing (or add --sharing in an existing app) installs and wires this for you — see @symbiote-native/cli.

expo-sharing and expo-modules-core come along as regular dependencies, pinned to exact versions — never install either yourself, and never add the expo meta-package to your project (it bundles its own Metro/Babel pipeline, which conflicts with this project’s own).

The package’s native-link.json asks symbiote-expo-link for the Android Gradle dependency and module-map entry on every install. Nothing else is needed: opening the share sheet requires no iOS permission, so there is no usage-description string, and the SharingFileProvider plus the <queries> block Android needs on API 30+ ship inside expo-sharing’s own manifest and merge into your app automatically.

All five adapters (React, Vue, Angular, Svelte, Solid) re-export the exact same functions; there’s no per-adapter hook/composable/service to reach for, since nothing here holds live state or a subscription.

import { useState } from 'react';
import { isAvailableAsync, shareAsync } from '@symbiote-native/sharing/react';
export default function ShareReport({ fileUri }: { fileUri: string }) {
const [error, setError] = useState<string | null>(null);
async function onShare() {
if (!(await isAvailableAsync())) {
setError('Sharing is not available on this device');
return;
}
await shareAsync(fileUri, { mimeType: 'application/pdf', dialogTitle: 'Send the report' });
}
return (
<view>
<button title="Share the report" onPress={onShare} />
{error === null ? null : <text>{error}</text>}
</view>
);
}

iOS presents the share sheet as a popover on iPad and needs somewhere to point it. Pass the rectangle of whatever the user tapped; without one the popover anchors to the bottom-center of the presenting view.

await shareAsync(fileUri, { anchor: { x: 40, y: 120, width: 1, height: 1 } });
Signature Description
isAvailableAsync(): Promise<boolean> Whether the share sheet can be opened on this device. Resolves true on Android and iOS — it reports on the presence of the native module, not on any device capability
shareAsync(url, options?): Promise<void> Opens the platform share sheet for the local file at url. Resolves once the sheet is dismissed, whether or not the user picked anything

url must be a non-empty string pointing at a file the app can read — a file:// URI, or a path from a file-system API. Anything else throws before the native call is made.

Field Type Description
mimeType string | undefined MIME type of the file, deciding which apps the chooser offers. Guessed from the file name when omitted. Android only
UTI string | undefined Uniform Type Identifier of the file. Accepted by the native options record but unread in expo-sharing@57.0.8; carried through for forward compatibility. iOS only
dialogTitle string | undefined Title of the share dialog. Android renders it as the chooser header; iOS assigns it to the activity controller, where most share sheets ignore it
anchor ISharingAnchor | undefined Rectangle the iPad popover points at. Ignored on iPhone and on Android. iOS only
Field Type Description
x number | undefined Horizontal offset in points, relative to the presenting view. Defaults to that view’s horizontal center
y number | undefined Vertical offset in points, relative to the presenting view. Defaults to that view’s bottom edge
width number | undefined Width of the anchor rectangle in points. Defaults to 0
height number | undefined Height of the anchor rectangle in points. Defaults to 0
  • url has to be local. A remote http(s) URL is not downloaded first — fetch it to a local file yourself, then share that file.
  • A resolved promise is not a delivery receipt. Neither platform reports which app the user picked, or whether they picked one at all: the promise resolves when the sheet closes. iOS resolves on every dismissal path, including “picked Print, then cancelled the print dialog”.
  • Android runs one share at a time. Calling shareAsync again while a chooser is still open rejects rather than queueing.
  • The iOS share extension is experimental upstream. It opens the main app target instead of handling the share in its own view controller, which Apple does not officially support and may stop working in a future iOS release.
  • The share sheet can only be verified on a device or simulator. The headless test suite fakes the native module, so it proves argument marshalling and the error paths, not the sheet itself.

Android says “Not allowed to read file under given URL” or “Failed to find configured root”. Share a file that lives in the app’s own document or cache directory, for example one written with the file system package. A path owned by another app, such as a media library asset, can be rejected. Copy it into Paths.cache first, then share the copy.

It shares fine on iOS but the receiving app cannot open the file on Android. The same cause: Android hands the other app a content URI built from the file, and only files in the app’s directories are covered. Check url points inside them.

Can I share a web URL or plain text? Not with this function. shareAsync takes a local file. To share a link, send it through the system’s text share (React Native’s Share module), or download the content to a file first.

How do I know the user actually shared it? You cannot. The promise resolves when the sheet closes, whichever app was picked, or none.

Sources: Expo docs: Sharing, expo/expo#5933 can’t share file on Android, expo/expo#18887 Not allowed to read file under given URL, expo/expo#9222 sharing photos from MediaLibrary throws on Android.

@symbiote-native/sharing ships zero React/Vue/Angular/Svelte/Solid logic — expo-sharing’s own JS is hand-ported into this package’s core/, resolving the native module through expo-modules-core’s requireNativeModule rather than the expo meta-package this project never installs:

packages/sharing/src/
core/ framework-agnostic: shareAsync, isAvailableAsync, the payload functions and the
incoming-share store. native-module.ts resolves ExpoSharing via requireNativeModule
react/ @symbiote-native/sharing/react (core + useIncomingShare)
vue/ @symbiote-native/sharing/vue (core + useIncomingShare)
svelte/ @symbiote-native/sharing/svelte (core + useIncomingShare)
angular/ @symbiote-native/sharing/angular (core + injectIncomingShare)
solid/ @symbiote-native/sharing/solid (core + useIncomingShare)

The store owns the refresh logic (payload comparison, the foreground resync, error capture) once, and each adapter binds it with its own event-value primitive, so the lifecycle code is a few lines per framework. The native code itself is never vendored or copied — expo-modules-autolinking resolves it straight out of node_modules (see the native setup guide).