Skip to content
philenius edited this page Apr 12, 2023 · 18 revisions

Q: Why am I able to select other file types even if I use a filter? Or why doesn't it filter at all?

I'm using FileType.custom with some extensions (eg. [csv, jpg]) and the file explorer doesn't respect it by allowing me picking other types.

A: When custom extensions are passed, those are translated into MIME types, then, an intent is sent to system requesting a file explorer to handle those and let you pick the files with the given restrictions — typically, the native one.

However, some Android OS versions (custom roms) not always have the native file explorer, neither you can't prevent the user to install a 3rd party on he’s own (such as ESExplorer). Those file explorers may be capable to handle the request but not always will (actually, unfortunately most of times won't) honour the MIME restrictions as they need to implement that feature and there's no way you can play around it. So, there's no warranty that fileExtensions will be respected and when you experience this issue, before reporting, try on an official emulator and see if it still happens.

So, because of that, either the package would have to throw an error when the user picked extensions that aren't suppose to, or, the developer can add this extra protection on their side which seems the more appropriate as you can iterate over the picked files and display a custom alert or similar.

Workaround: use type=FileType.any and make sure that you do not specify allowed extension (allowedExtensions must be null), which allows the user to pick any kind of file without filtering, and then validate the picked file yourself within your app (e.g. if the returned file doesn’t match the required extension show a warning and open the file picker again). For example:

FilePickerResult? result = await FilePicker.platform.pickFiles(
  type: FileType.any,
);

Read more about it here.

Examples of related issues: #24


Q: Why can't I access original file path?

A: This is one of the most asked questions and it actually was enough to make me create this FAQ section, as this doesn't represent an actual issue to fit Troubleshooting page.

This is no longer possible since SDK 30 (Android) and on iOS it has never been possible. To work with the absolute files (this is, not copies) you'll have to manage through persistent file access (URI on Android and secure bookmarks on iOS).

This is only possible in the native way, since you can't open Dart File instances pointing to those objects, it always require an absolute path which is only reliable and possible, as of now, through copy (aka caching).

Original paths were possible until file_picker 2.0.0 on Android, however, in iOS they were never possible at all since iOS wants you to make a cached copy and work on it. But, 2.0.0 introduced scoped storage support (Android 10) and with and per Android doc recommendations, files should be accessed in two ways:

  1. Pick files for CRUD operations (read, delete, edit) through files URI and use it directly — this is what you actually want but unfortunately isn’t supported by Flutter as it needs an absolute path to open a File descriptor;

  2. Cache the file temporarily for upload or similar, or just copy into your app’s persistent storage so you can later access it — this is what’s being done currently and even though you may have an additional step moving/copying the file after first picking, makes it safer and reliable to access any allowed file on any Android device.

You can check this blog post from Commonsware for more info.

TL;DR: after picking the file the first time, just move it to your app storage scope and use it from there onwards (you can use getApplicationDocumentsDirectory() method from path_provider to get your apps local storage.

Have in mind that currently file_picker is optimised to the point where if you pick twice the same file (with the same name) you’ll be probably picking the previous cached file, as it only will be cached the first time is picked.


Q: How do I know if user cancelled the picker?

A: On dart:io devices devices (Android, iOS and Desktop) the FilePickerResult will automatically return null if user opts to cancel/dismiss the picker.

However, on web, this is currently a limitation and there's no way to know whether the user cancelled it or not because there aren't any events fired from the platform end.


Q: How do I access the path on Web?

A: Paths aren't inaccessible from browsers since those provide fake paths. If you want to create a File instance to upload it somewhere, like Firebase Storage, you can do so with the bytes directly.

// get file
final result = await FilePicker.platform.pickFiles(type: FileType.any, allowMultiple: false);

if (result != null && result.files.isNotEmpty) {
  final fileBytes = result.files.first.bytes;
  final fileName = result.files.first.name;
  
  // upload file
  await FirebaseStorage.instance.ref('uploads/$fileName').putData(fileBytes);
}

Thank you @devj3ns for providing the example.


Q: How do do I use withReadStream?

A: If you need to pick a heavy file to upload it somewhere, you can take advantage with withReadStream and read it in chunks. Check the example below.

import 'package:file_picker/file_picker.dart';
import 'package:http/http.dart' as http;
import 'package:http_parser/http_parser.dart';
import 'package:mime/mime.dart';

void main() async {
  final result = await FilePicker.platform.pickFiles(
    type: FileType.custom,
    allowedExtensions: [
      'jpg',
      'png',
      'mp4',
      'webm',
    ],
    withData: false,
    withReadStream: true,
  );

  if (result == null || result.files.isEmpty) {
    throw Exception('No files picked or file picker was canceled');
  }

  final file = result.files.first;
  final filePath = file.path;
  final mimeType = filePath != null ? lookupMimeType(filePath) : null;
  final contentType = mimeType != null ? MediaType.parse(mimeType) : null;

  final fileReadStream = file.readStream;
  if (fileReadStream == null) {
    throw Exception('Cannot read file from null stream');
  }
  final stream = http.ByteStream(fileReadStream);

  final uri = Uri.https('siasky.net', '/skynet/skyfile');
  final request = http.MultipartRequest('POST', uri);
  final multipartFile = http.MultipartFile(
    'file',
    stream,
    file.size,
    filename: file.name,
    contentType: contentType,
  );
  request.files.add(multipartFile);

  final httpClient = http.Client();
  final response = await httpClient.send(request);

  if (response.statusCode != 200) {
    throw Exception('HTTP ${response.statusCode}');
  }

  final body = await response.stream.transform(utf8.decoder).join();

  print(body);
}

Thank you @redsolver for providing the example.


Q: When using file_picker on macOS or Linux, the dialog does not always get in focus, so there is an extra click to first make the dialog in focus before you can actually choose a file. Can you make the dialog focus automatically or provide a better user experience / UX in general?

A: Unfortunately, file_picker for macOS and Linux are based on approaches with limited capabilities. In both cases, it works by executing shell commands (which spawn a separate process to show the dialog). On macOS, we execute a shell command named osascript, which can execute AppleScript commands. On Linux, we execute one of the shell commands qarma, zenity, or kdialog, depending on which one is available. This approach limits our capabilities (e.g. we cannot change the language of the file picker dialog, always make it in focus, etc.).

As an alternative, you could try Google's official Dart package named file_selector (https://pub.dev/packages/file_selector) which calls APIs of the operating system instead of executing shell commands.

Examples of related issues: #1252, #1242, #1145, #1059