The inability to retrieve intent global URLs in a Flutter iOS share extension typically stems from missing or misconfigured App Group entitlements. Without these shared container permissions, the extension cannot access the files passed by the host app. This guide details the correct setup for Xcode project capabilities and the necessary Dart code to properly handle incoming share data using the
share_plus plugin.
Understanding the Architecture
To debug this issue effectively, it is crucial to understand how iOS handles communication between an application and its extensions. A share extension runs in a separate process from your main Flutter app. By default, these two processes have isolated file systems and memory spaces. When a user shares content from another app (like Safari or Photos) into your Flutter app, that external app does not send data directly to your app’s memory. Instead, it places the data into a temporary location and provides a URL pointing to that location.
Your share extension receives this URL via the
NSExtensionContext. The critical step is ensuring that both your main app and the share extension have permission to read from and write to a shared directory. This is achieved through App Groups. If this configuration is missing, the extension may receive the URL, but when it attempts to access the file at that URL, it will fail with a permission denied error or return null, leading to the "unable to get intent" symptom.
Configuring App Groups in Xcode
The most common cause of this failure is an incomplete App Group setup. You must enable this feature for both the main target and the extension target.
First, open your iOS project in Xcode. Select your main app target, navigate to the
Signing & Capabilities tab, and click
Add Capability. Search for and add
App Groups. Once added, ensure the group identifier is checked. The identifier usually follows the format
group.com.yourcompany.yourapp.
Next, you must repeat this exact process for the Share Extension target. Select the share extension target in the project navigator, go to
Signing & Capabilities, and add the same
App Groups capability. It is vital that the group identifier string matches exactly between the main app and the extension. If they do not match, the sandboxing layer will prevent cross-process file access.
After adding the capability, verify that the
Entitlements.plist files for both targets include the
com.apple.security.application-groups key with your specific group ID. Do not manually edit these files if you can avoid it; use the Xcode UI to ensure proper formatting.
Verifying the Container Path
Once configured, you need to confirm that the path generated by your code actually points to the shared container. In your Dart code, you can log the path returned by the file system provider. Ensure you are using the standard library methods to resolve the group container. For example, using
Directory.systemTemp is incorrect for persistent sharing; you must use the group-specific directory.
If you previously attempted to use custom keys like
PHSharedContainerID, remove them immediately. These are not standard keys for general file sharing and will likely cause build failures or silent runtime errors. Stick to the standard App Groups mechanism provided by Apple.
Implementing Correct File Handling in Flutter
With the Xcode configuration complete, the next step is ensuring your Flutter code correctly interprets the incoming share data. The
share_plus plugin is the standard tool for this interaction. However, simply calling the share function is not enough; you must also handle the incoming data in the extension’s entry point.
In your Swift or Objective-C code within the share extension, you need to extract the URLs from the
NSExtensionContext. Iterate through the
itemProviders and request the data. When you receive the file URL, copy it to the shared app group directory. This step is mandatory because the temporary URL provided by the system may expire or become invalid before your Flutter engine finishes loading.
Here is the logical flow:
- Receive
NSExtensionContext items.
- Load the file data from the temporary URL.
- Write the data to a file inside the App Group container.
- Pass the new path of the file in the App Group container to your Flutter engine via
PlatformChannel.
This approach decouples the ephemeral system URL from the persistent file your app needs to process. It ensures that even if the system cleans up the temporary cache, your app still has access to the file.
Common Pitfalls and Debugging Steps
Even with correct configuration, issues can arise due to caching or deployment errors. First, always perform a clean build. Delete the derived data folder and reinstall the app on the simulator or device. Old entitlements can sometimes persist in the build cache, leading to confusing behavior where the extension seems to work in debug mode but fails in release mode.
Second, check the console logs for the share extension specifically. In Xcode, you can select the running extension process from the scheme menu to view its logs separately. Look for errors related to
file:// URLs failing to load. If you see a "Permission Denied" error, double-check that the App Group identifier in the
Info.plist matches the one in the provisioning profile.
Third, ensure that your Flutter app is running in the background or foreground when the extension tries to communicate. If the app is killed, the platform channel may fail to establish a connection. Consider implementing a fallback mechanism where the extension saves the data to a local database or file that the main app can scan upon launch.
For more information on handling file paths securely, you might find our guide on fixing
PathAccessException when Saving Files helpful, as the underlying principles of sandboxed storage are similar.
Frequently Asked Questions
Why does my share extension crash when accessing the shared file?
It likely crashes because the temporary URL provided by the system has expired or was never accessible due to missing App Group permissions. Always copy the file to the App Group container immediately upon receiving the share intent before attempting to process it in Dart.
Can I use the same App Group ID for multiple apps?
No, each app bundle ID should have its own unique App Group identifier. Using the same group ID across different unrelated apps violates Apple’s security guidelines and may result in rejection during App Store review.
How do I test if my App Group is configured correctly?
You can test this by creating a simple text file in the App Group container from the main app and then trying to read it from the share extension. If the read operation succeeds, your entitlements are correctly configured.
Does the share_plus plugin handle the file copying automatically?
No, the
share_plus plugin primarily handles initiating shares from your app. When receiving shares in an extension, you must manually handle the
NSExtensionContext in native code (Swift/Objective-C) and transfer the file data to your Flutter engine.
What is the difference between NSFileCoordinator and manual copying?
NSFileCoordinator is useful for safely reading files that are being written by other processes, but for share extensions, manual copying to the App Group container is the standard pattern because the source file is often transient and needs to be persisted for the Flutter app to access it reliably.