Skip to content

Commit

Permalink
refactor(share_plus)!: Share API cleanup (#2832)
Browse files Browse the repository at this point in the history
Co-authored-by: Volodymyr Buberenko <vbuberen@users.noreply.github.com>
  • Loading branch information
miquelbeltran and vbuberen committed Apr 15, 2024
1 parent 7259af2 commit fd0511c
Show file tree
Hide file tree
Showing 22 changed files with 245 additions and 652 deletions.
51 changes: 38 additions & 13 deletions packages/share_plus/share_plus/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,16 @@
A Flutter plugin to share content from your Flutter app via the platform's
share dialog.

Wraps the `ACTION_SEND` Intent on Android and `UIActivityViewController`
on iOS.
Wraps the `ACTION_SEND` Intent on Android, `UIActivityViewController`
on iOS, or equivalent platform content sharing methods.

## Platform Support

| Android | iOS | MacOS | Web | Linux | Windows |
| :-----: | :-: | :---: | :-: | :---: | :----: |
|||||||
| Method | Android | iOS | MacOS | Web | Linux | Windows |
| :-----------: | :-----: | :-: | :---: | :-: | :---: | :----: |
| `share` |||||||
| `shareUri` ||| | | | |
| `shareXFiles` ||||| ||

Also compatible with Windows and Linux by using "mailto" to share text via Email.

Expand All @@ -28,15 +30,15 @@ Sharing files is not supported on Linux.

To use this plugin, add `share_plus` as a [dependency in your pubspec.yaml file](https://plus.fluttercommunity.dev/docs/overview).

## Example

Import the library.

```dart
import 'package:share_plus/share_plus.dart';
```

Then invoke the static `share` method anywhere in your Dart code.
### Share Text

Invoke the static `share()` method anywhere in your Dart code.

```dart
Share.share('check out my website https://example.com');
Expand All @@ -49,16 +51,18 @@ sharing to email.
Share.share('check out my website https://example.com', subject: 'Look what I made!');
```

If you are interested in the action your user performed with the share sheet, you can instead use the `shareWithResult` method.
`share()` returns `status` object that allows to check the result of user action in the share sheet.

```dart
final result = await Share.shareWithResult('check out my website https://example.com');
final result = await Share.share('check out my website https://example.com');
if (result.status == ShareResultStatus.success) {
print('Thank you for sharing my website!');
}
```

### Share Files

To share one or multiple files, invoke the static `shareXFiles` method anywhere in your Dart code. The method returns a `ShareResult`. Optionally, you can pass `subject`, `text` and `sharePositionOrigin`.

```dart
Expand All @@ -77,7 +81,6 @@ if (result.status == ShareResultStatus.dismissed) {
}
```


On web, you can use `SharePlus.shareXFiles()`. This uses the [Web Share API](https://web.dev/web-share/)
if it's available. Otherwise it falls back to downloading the shared files.
See [Can I Use - Web Share API](https://caniuse.com/web-share) to understand
Expand All @@ -89,6 +92,24 @@ package.
Share.shareXFiles([XFile('assets/hello.txt')], text: 'Great picture');
```

### Share URI

iOS supports fetching metadata from a URI when shared using `UIActivityViewController`.
This special method is only properly supported on iOS.

```dart
Share.shareUri(uri: uri);
```

### Share Results

All three methods return a `ShareResult` object which contains the following information:

- `status`: a `ShareResultStatus`
- `raw`: a `String` describing the share result, e.g. the opening app ID.

Note: `status` will be `ShareResultStatus.unavailable` if the platform does not support identifying the user action.

## Known Issues

### Sharing data created with XFile.fromData
Expand All @@ -103,9 +124,13 @@ Alternatively, don't use `XFile.fromData` and instead write the data down to a `

### Mobile platforms (Android and iOS)

#### Facebook limitations (WhatsApp, Instagram, Facebook Messenger)
#### Meta (WhatsApp, Instagram, Facebook Messenger) and similar apps

Due to restrictions set up by Meta/Facebook this plugin isn't capable of sharing data reliably to Facebook related apps on Android and iOS. This includes eg. sharing text to the Facebook Messenger. If you require this functionality please check the native Facebook Sharing SDK ([https://developers.facebook.com/docs/sharing](https://developers.facebook.com/docs/sharing)) or search for other Flutter plugins implementing this SDK. More information can be found in [this issue](https://github.com/fluttercommunity/plus_plugins/issues/413).

Due to restrictions set up by Facebook this plugin isn't capable of sharing data reliably to Facebook related apps on Android and iOS. This includes eg. sharing text to the Facebook Messenger. If you require this functionality please check the native Facebook Sharing SDK ([https://developers.facebook.com/docs/sharing](https://developers.facebook.com/docs/sharing)) or search for other Flutter plugins implementing this SDK. More information can be found in [this issue](https://github.com/fluttercommunity/plus_plugins/issues/413).
Other apps may also give problems when attempting to share content to them.
We cannot warranty that a 3rd party app will properly implement the share functionality.
Therefore, all bugs reported regarding compatibility with a specific app will be closed.

#### Localization in Apple platforms

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package dev.fluttercommunity.plus.share

import android.os.Build
import io.flutter.BuildConfig
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import java.io.IOException
Expand All @@ -14,41 +15,42 @@ internal class MethodCallHandler(
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
expectMapArguments(call)

// The user used a *WithResult method
val isResultRequested = call.method.endsWith("WithResult")
// We don't attempt to return a result if the current API version doesn't support it
val isWithResult =
isResultRequested && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1
Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1

if (isWithResult && !manager.setCallback(result)) return
if (isWithResult)
manager.setCallback(result)

try {
when (call.method) {
"shareUri" -> {
share.share(
call.argument<Any>("uri") as String, subject = null, withResult = false
call.argument<Any>("uri") as String,
subject = null,
withResult = isWithResult,
)
success(isWithResult, isResultRequested, result)
success(isWithResult, result)
}

"share", "shareWithResult" -> {
"share" -> {
share.share(
call.argument<Any>("text") as String,
call.argument<Any>("subject") as String?,
isWithResult,
)
success(isWithResult, isResultRequested, result)
success(isWithResult, result)
}

"shareFiles", "shareFilesWithResult" -> {
"shareFiles" -> {
share.shareFiles(
call.argument<List<String>>("paths")!!,
call.argument<List<String>?>("mimeTypes"),
call.argument<String?>("text"),
call.argument<String?>("subject"),
isWithResult,
)
success(isWithResult, isResultRequested, result)
success(isWithResult, result)
}

else -> result.notImplemented()
Expand All @@ -61,15 +63,10 @@ internal class MethodCallHandler(

private fun success(
isWithResult: Boolean,
isResultRequested: Boolean,
result: MethodChannel.Result
) {
if (!isWithResult) {
if (isResultRequested) {
result.success("dev.fluttercommunity.plus/share/unavailable")
} else {
result.success(null)
}
result.success("dev.fluttercommunity.plus/share/unavailable")
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,22 @@ internal class ShareSuccessManager(private val context: Context) : ActivityResul
* Set result callback that will wait for the share-sheet to close and get either
* the componentname of the chosen option or an empty string on dismissal.
*/
fun setCallback(callback: MethodChannel.Result): Boolean {
fun setCallback(callback: MethodChannel.Result) {
return if (isCalledBack.compareAndSet(true, false)) {
// Prepare all state for new share
SharePlusPendingIntent.result = ""
isCalledBack.set(false)
this.callback = callback
true
} else {
callback.error(
"Share callback error",
"prior share-sheet did not call back, did you await it? Maybe use non-result variant",
null,
)
false
// Ensure no deadlocks.
// Return result of any waiting call.
// e.g. user called to `share` but did not await for result.
this.callback?.success(RESULT_UNAVAILABLE)

// Prepare all state for new share
SharePlusPendingIntent.result = ""
isCalledBack.set(false)
this.callback = callback
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,15 @@ void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();

testWidgets('Can launch share', (WidgetTester tester) async {
expect(Share.share('message', subject: 'title'), completes);
}, skip: Platform.isMacOS);

testWidgets('Can launch share in MacOS', (WidgetTester tester) async {
// Check isNotNull because we cannot wait for ShareResult
expect(Share.share('message', subject: 'title'), isNotNull);
}, skip: !Platform.isMacOS);

testWidgets('Can launch shareWithResult', (WidgetTester tester) async {
expect(Share.shareWithResult('message', subject: 'title'), isNotNull);
});

testWidgets('Can launch shareUri', (WidgetTester tester) async {
// Check isNotNull because we cannot wait for ShareResult
expect(Share.shareUri(Uri.parse('https://example.com')), isNotNull);
}, skip: !Platform.isAndroid && !Platform.isIOS);

testWidgets('Can shareXFile created using File.fromData()',
(WidgetTester tester) async {
final bytes = Uint8List.fromList([1, 2, 3, 4, 5, 6, 7, 8]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>11.0</string>
<string>12.0</string>
</dict>
</plist>
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,6 @@
535124FC9BB266447EC60189 /* Pods-RunnerTests.release.xcconfig */,
A8F8F8E1F770CE9853568BC6 /* Pods-RunnerTests.profile.xcconfig */,
);
name = Pods;
path = Pods;
sourceTree = "<group>";
};
Expand Down Expand Up @@ -215,7 +214,7 @@
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1300;
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
331C8080294A63A400263BE5 = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1300"
LastUpgradeVersion = "1510"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
10 changes: 6 additions & 4 deletions packages/share_plus/share_plus/example/ios/Runner/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,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>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
Expand All @@ -24,6 +26,8 @@
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
Expand All @@ -41,11 +45,9 @@
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>NSPhotoLibraryUsageDescription</key>
<string>Load photos for sharing functionality</string>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
</dict>
</plist>

0 comments on commit fd0511c

Please sign in to comment.