plus_plugins
plus_plugins copied to clipboard
[Request]: Use the `XFile.name` as the shared file name
Plugin
share_plus
Use case
Our app lets users open files (usually PDFs), and some of these files have special characters that don't play nicely with temp-file paths.
For example, My First / Last File.pdf causes an error when writing to temp storage.
To combat this, you need to sanitize the file name to play nicely with the native file system. But, when share_plus shares this file (via Share.shareXFiles), it uses the file name from the path instead of the XFile's name property.
According to the XFile documentation, name is intended to be used for this exact scenario.
Proposal
I propose that the function Share.shareXFiles utilizes XFile.name for the file name, if available. If not available, then default to the path file name.
I second to this. I am creating the file with XFile.fromData() and this is the only option for me to have a user friendly file name.
Same here. Getting image from web without any file extension causes the share with .BIN
Is there at least any workaround for this currently?
same issue
Any updates on this ?
Same here, seems like the bug still isnt fixed
Please fix this
Any way to change the name using XFile.fromdata() ? Since name: doesn't work.
any updates?
Same request from me, now Share.shareXFiles causes files to have uuid like file name
Same problem here
it would be better to use file instead of XFile.fromData, this way you can name the file and delete it after sharing to keep the temporary directory clean, here is a work around:
Future<File> writeFile(List<int>? excel, String fileName) async {
final directory = await getApplicationDocumentsDirectory();
final path = directory.path;
final file = File('$path/$fileName');
return file.writeAsBytes(excel!);
}
if it's an exported excel file:
List<int>? excel = exportToExcel();
String fileName = 'example.xlsx'; // Desired file name
File file = await writeFile(excel, fileName);
await Share.shareXFiles([XFile(file.path)]);
Same here! And I don't like the idea of writing file into persistent storage then read from it...Why bother the round trip? BTW XFile works flawlessly on macOS and iPhone for some reason but not on Android and Chrome?!
Come on Google u need to take care of your own children as well!
Not sure who is that referring to, but this is a project run by volunteers, and doesn't belong to Google.
Anyway, these types of comments are unprofessional and go against our code of conduct, so please do not use them in this project.
OK, I have removed that piece of the comment. With that said, I believe the Android implementation for XFile.fromData should also respect the filename and extension supplied by the user instead of generating a UUID-like filename along with a "reconstructed" extension that, If I have to guess, is based on the interpreted result of the MIME type of the real filename. This is technically fine, but xxxxx.jpeg will be matched to image/jpe by the mime package and the generated filename might have an extension of .jpe, which has all kinds of compatibility issues with the platforms that were designed to handle .jpeg and .jpg.
In this case, if the shared file on Android is of extension .jpe, then in some Android Distributions (not sure about Google Pixel, but did run into this issue on Xiaomi's MIUI), image / SMS apps might fail to recognize that it's an image and thus will not show up in the share menu.
When I got time, I will definitely open a PR to address this issue!
I think I have identified the issue and might be able to fix it by modify these lines to achieve the effect the OP (and us) wants!
xxxxx.jpegwill be matched toimage/jpeby themimepackage
I see, this is reported here: https://github.com/dart-lang/mime/issues/55 at least the PR is getting some movement, I have subscribed to updates.
If anyone tries to do a dependency_override to the fixed branch, let me know how it went!
Once mime is updated, we will update the dependency too.
Ah, I mean I came up with a workaround that respects the extension from user's filename and then fallback to the MIME way in case it fails.
By the way, may I ask why was the filename discarded and UUID was used instead?Is it for collision prevention or ASCII-compliance because of some legacy issues that I am not aware of?
By the way, may I ask why was the filename discarded and UUID was used instead?Is it for collision prevention or ASCII-compliance because of some legacy issues that I am not aware of?
I don't remember, sorry. Maybe the git log or the original pr have a clue.
haha no worries! I am trying to digest #1188 to see if I can get the full picture of the original issue. But based on my test against a real android phone (namely image_picker that yields files as XFiles) using XFile the intent URLs do not contain regular filenames but rather something like content://xxx, which requires us to use path_provider to deal with it, given that we know which platform the user is running our code on...
Should be closed by #2713 pending to be released.
Thanks a lot @LBYPatrick for the contribution!
...Haha you are welcome! Though, I have noticed that our test cases for flutter test do not contain proper tests for simulating the case where we intentionally tamper the path but set XFile.name and XFile.mimeType properly (Which is what image_picker would sometimes do in Android). In other words, my code might not be well covered by the test cases unless we run the CI integration tests upon opening PRs...I will follow up with another PR to see if I can pull it off!
Unfortunately I am not familiar with the testing pipeline so I am having a hard time writing such test. With that said, I noticed that getTemporaryDirectory() is not available at all under the test environment, so we cannot direct test it with flutter test but only with the integration tests:
I thought about exposing the tempRoot variable for us to dictate where temporary files should go solely for testing purposes, but doing so will also expose this variable to the users, which I don't know if this will confuse the users or allow them to do things we do not expect them to (i.e. saving temporary files to some invalid path). Or do you think there's another way we can simulate this case and somehow extract the intermediate outputs of _getFile or _getFiles? They are package-private methods and are guarded behind the user-facing share methods, and I am just...not sure about it.
And here's my half-baked test code:
test(
'simulate the edge case where XFile.path is not set but XFile.name is (usually from XFile.fromData)',
() async {
await withFile("tempfile-83649f.png", (File fd) async {
final bytes = await fd.readAsBytes();
//This file may not have path in it but has XFile.name
final XFile badFile = XFile.fromData(bytes, name: "tempfile-83649f.png");
final res = await sharePlatform.shareXFiles([badFile]);
verify(mockChannel.invokeMethod('shareFilesWithResult', <String, dynamic>{
'paths': ["*/tempfile-83649f.png"],
'mimeTypes': ['image/png'],
}));
});
There is an issue with the raw data usage. The Cross file package doesn't return the name parameter when using 'XFile.fromData'. Because of that, this issue should be open.
Agreed with @ibrahimtelman that raw data names do not work still
Same issue here, name parameter is ignored when using 'XFile.fromData'
cross_file ignores filename and has documented it as such, see cross_file/lib/src/types/io.dart:43 See also https://github.com/flutter/flutter/issues/147361#issuecomment-2076724612 and https://github.com/flutter/packages/pull/4416
I have created an issue to document this better: https://github.com/fluttercommunity/plus_plugins/issues/3032
In the mean time you can share a file with your custom filename by doing something like this
var filename = 'myFileName.extension';
String content = 'Hello Share';
var tempRoot = (await getTemporaryDirectory()).path;
final tempSubfolderPath = "$tempRoot/${const Uuid().v4()}";
await Directory(tempSubfolderPath).create(recursive: true);
final path = "$tempSubfolderPath/$filename";
await File(path).writeAsString(content); // alternatively use writeAsBytes to write bytes
var xFile = XFile(path);
Share.shareXFiles([xFile]);