Skip to content

Commit 95a12ee

Browse files
fix(share_plus): fallback for shareXFiles() to use download on web (#3388)
Co-authored-by: Miguel Beltran <[email protected]>
1 parent b5d24a0 commit 95a12ee

File tree

4 files changed

+107
-34
lines changed

4 files changed

+107
-34
lines changed

packages/share_plus/share_plus/README.md

+6
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,12 @@ package.
101101
Share.shareXFiles([XFile('assets/hello.txt')], text: 'Great picture');
102102
```
103103

104+
File downloading fallback mechanism for web can be disabled by setting:
105+
106+
```dart
107+
Share.downloadFallbackEnabled = false;
108+
```
109+
104110
#### Share Data
105111

106112
You can also share files that you dynamically generate from its data using [`XFile.fromData`](https://pub.dev/documentation/share_plus/latest/share_plus/XFile/XFile.fromData.html).

packages/share_plus/share_plus/example/lib/main.dart

+42-27
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ import 'package:share_plus/share_plus.dart';
1919
import 'image_previews.dart';
2020

2121
void main() {
22+
// Set `downloadFallbackEnabled` to `false`
23+
// to disable downloading files if `shareXFiles` fails on web.
24+
Share.downloadFallbackEnabled = true;
25+
2226
runApp(const DemoApp());
2327
}
2428

@@ -241,39 +245,50 @@ class DemoAppState extends State<DemoApp> {
241245
void _onShareXFileFromAssets(BuildContext context) async {
242246
final box = context.findRenderObject() as RenderBox?;
243247
final scaffoldMessenger = ScaffoldMessenger.of(context);
244-
final data = await rootBundle.load('assets/flutter_logo.png');
245-
final buffer = data.buffer;
246-
final shareResult = await Share.shareXFiles(
247-
[
248-
XFile.fromData(
249-
buffer.asUint8List(data.offsetInBytes, data.lengthInBytes),
250-
name: 'flutter_logo.png',
251-
mimeType: 'image/png',
252-
),
253-
],
254-
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size,
255-
);
256-
257-
scaffoldMessenger.showSnackBar(getResultSnackBar(shareResult));
248+
try {
249+
final data = await rootBundle.load('assets/flutter_logo.png');
250+
final buffer = data.buffer;
251+
final shareResult = await Share.shareXFiles(
252+
[
253+
XFile.fromData(
254+
buffer.asUint8List(data.offsetInBytes, data.lengthInBytes),
255+
name: 'flutter_logo.png',
256+
mimeType: 'image/png',
257+
),
258+
],
259+
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size,
260+
);
261+
scaffoldMessenger.showSnackBar(getResultSnackBar(shareResult));
262+
} catch (e) {
263+
scaffoldMessenger.showSnackBar(
264+
SnackBar(content: Text('Error: $e')),
265+
);
266+
}
258267
}
259268

260269
void _onShareTextAsXFile(BuildContext context) async {
261270
final box = context.findRenderObject() as RenderBox?;
262271
final scaffoldMessenger = ScaffoldMessenger.of(context);
263-
final data = utf8.encode(text);
264-
final shareResult = await Share.shareXFiles(
265-
[
266-
XFile.fromData(
267-
data,
268-
// name: fileName, // Notice, how setting the name here does not work.
269-
mimeType: 'text/plain',
270-
),
271-
],
272-
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size,
273-
fileNameOverrides: [fileName],
274-
);
275272

276-
scaffoldMessenger.showSnackBar(getResultSnackBar(shareResult));
273+
try {
274+
final shareResult = await Share.shareXFiles(
275+
[
276+
XFile.fromData(
277+
utf8.encode(text),
278+
// name: fileName, // Notice, how setting the name here does not work.
279+
mimeType: 'text/plain',
280+
),
281+
],
282+
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size,
283+
fileNameOverrides: [fileName],
284+
);
285+
286+
scaffoldMessenger.showSnackBar(getResultSnackBar(shareResult));
287+
} catch (e) {
288+
scaffoldMessenger.showSnackBar(
289+
SnackBar(content: Text('Error: $e')),
290+
);
291+
}
277292
}
278293

279294
SnackBar getResultSnackBar(ShareResult result) {

packages/share_plus/share_plus/lib/share_plus.dart

+3
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ export 'src/share_plus_windows.dart'
1818
class Share {
1919
static SharePlatform get _platform => SharePlatform.instance;
2020

21+
/// Whether to fall back to downloading files if [shareXFiles] fails on web.
22+
static bool downloadFallbackEnabled = true;
23+
2124
/// Summons the platform's share sheet to share uri.
2225
///
2326
/// Wraps the platform's native share dialog. Can share a URL.

packages/share_plus/share_plus/lib/src/share_plus_web.dart

+56-7
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'dart:ui';
66
import 'package:flutter_web_plugins/flutter_web_plugins.dart';
77
import 'package:meta/meta.dart';
88
import 'package:mime/mime.dart' show lookupMimeType;
9+
import 'package:share_plus/share_plus.dart';
910
import 'package:share_plus_platform_interface/share_plus_platform_interface.dart';
1011
import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart';
1112
import 'package:url_launcher_web/url_launcher_web.dart';
@@ -204,11 +205,19 @@ class SharePlusWebPlugin extends SharePlatform {
204205
error: e,
205206
);
206207

207-
throw Exception('Navigator.canShare() is unavailable');
208+
return _downloadIfFallbackEnabled(
209+
files,
210+
fileNameOverrides,
211+
'Navigator.canShare() is unavailable',
212+
);
208213
}
209214

210215
if (!canShare) {
211-
throw Exception('Navigator.canShare() is false');
216+
return _downloadIfFallbackEnabled(
217+
files,
218+
fileNameOverrides,
219+
'Navigator.canShare() is false',
220+
);
212221
}
213222

214223
try {
@@ -217,16 +226,56 @@ class SharePlusWebPlugin extends SharePlatform {
217226
// actions is success, but can't get the action name
218227
return ShareResult.unavailable;
219228
} on DOMException catch (e) {
220-
if (e.name case 'AbortError') {
229+
final name = e.name;
230+
final message = e.message;
231+
232+
if (name case 'AbortError') {
221233
return _resultDismissed;
222234
}
223235

224-
developer.log(
225-
'Failed to share files',
226-
error: '${e.name}: ${e.message}',
236+
return _downloadIfFallbackEnabled(
237+
files,
238+
fileNameOverrides,
239+
'Navigator.share() failed: $message',
227240
);
241+
}
242+
}
228243

229-
throw Exception('Navigator.share() failed: ${e.message}');
244+
Future<ShareResult> _downloadIfFallbackEnabled(
245+
List<XFile> files,
246+
List<String>? fileNameOverrides,
247+
String message,
248+
) {
249+
developer.log(message);
250+
if (Share.downloadFallbackEnabled) {
251+
return _download(files, fileNameOverrides);
252+
} else {
253+
throw Exception(message);
254+
}
255+
}
256+
257+
Future<ShareResult> _download(
258+
List<XFile> files,
259+
List<String>? fileNameOverrides,
260+
) async {
261+
developer.log('Download files as fallback');
262+
try {
263+
for (final (index, file) in files.indexed) {
264+
final bytes = await file.readAsBytes();
265+
266+
final anchor = document.createElement('a') as HTMLAnchorElement
267+
..href = Uri.dataFromBytes(bytes).toString()
268+
..style.display = 'none'
269+
..download = fileNameOverrides?.elementAt(index) ?? file.name;
270+
document.body!.children.add(anchor);
271+
anchor.click();
272+
anchor.remove();
273+
}
274+
275+
return ShareResult.unavailable;
276+
} catch (error) {
277+
developer.log('Failed to download files', error: error);
278+
throw Exception('Failed to to download files: $error');
230279
}
231280
}
232281

0 commit comments

Comments
 (0)