Share Sheet Extension Development for Android
In Android, there is no "extension" as a separate target—the application registers a regular Activity as an Intent receiver through intent-filter in the manifest. When the user shares content from any application, the system shows a list of available recipients. Your application is in this list.
Intent Filter and MIME Types
Registration in AndroidManifest.xml:
<activity android:name=".ShareReceiverActivity">
<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>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
</activity>
A separate <intent-filter> for each type is important. One filter with multiple <data> elements doesn't always work as expected due to Intent matching specifics.
Getting Data
In ShareReceiverActivity.onCreate(), check the action and extract data:
when (intent.action) {
Intent.ACTION_SEND -> {
if (intent.type?.startsWith("image/") == true) {
val imageUri = intent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM)
// URI must be processed through ContentResolver
} else if (intent.type == "text/plain") {
val sharedText = intent.getStringExtra(Intent.EXTRA_TEXT)
}
}
Intent.ACTION_SEND_MULTIPLE -> {
val imageUris = intent.getParcelableArrayListExtra<Uri>(Intent.EXTRA_STREAM)
}
}
URI and ContentResolver. This is the main pain point. Uri from EXTRA_STREAM is a content:// URI from the sending application. Read it through contentResolver.openInputStream(uri), not through File API. On Android 10+, direct reading through /data/... paths is blocked by scope storage. Copy the file to your own storage before the activity dies.
Retention URI permission. The Uri is valid only while the activity is open (unless you take persistedUriPermission). If you need to process the file in the background—copy it to app-specific storage first:
val inputStream = contentResolver.openInputStream(sourceUri)
val destFile = File(cacheDir, "shared_${System.currentTimeMillis()}.jpg")
inputStream?.use { it.copyTo(destFile.outputStream()) }
// Now destFile lives independently of the original Uri
Direct Share — Personalized Recipients
Since Android 10, you can show specific recipients directly in the share menu—for example, "Share with Ivan" with an avatar. Implemented through ChooserTargetService (deprecated) or the new ShortcutManager.pushDynamicShortcut() with setLongLived(true) and category SHORTCUT_CATEGORY_CONVERSATION.
This is separate work: you must publish dynamic shortcuts for each contact/recipient, update them on changes, handle them in ShortcutManagerCompat. Without it, the share menu just shows the application icon without personalization.
Share as Entry Point to the Application
If the application hasn't launched yet, ShareReceiverActivity starts it from scratch. Consider: the task (task stack) is created anew, back stack is empty. After processing the share, pressing Back should go to home, not to the previous application. Configure android:taskAffinity and android:launchMode correctly for this activity.
Timeline
Basic Share Extension (text + images, simple UI): 2–3 weeks. With Direct Share, background file processing, server synchronization: 4–6 weeks. Cost is calculated after analyzing content types and integration with the main application.







