miguelpruivo / flutter_file_picker

File picker plugin for Flutter, compatible with mobile (iOS & Android), Web, Desktop (Mac, Linux, Windows) platforms with Flutter Go support.
MIT License
1.32k stars 655 forks source link

[Feature] Web Support #229

Closed miguelpruivo closed 4 years ago

miguelpruivo commented 4 years ago

Motivation

Currently web support is one of most requested features for file_picker.

Have in mind that features are still being added and you need to be on Flutter branches that currently have web support as well (eg. beta, dev, master).

Update: early implementation of this is already available with file_picker_web

webertim commented 4 years ago

@miguelpruivo You should adjust the

ref: web_support

to ref: web-support

miguelpruivo commented 4 years ago

Hi, what's the reason behind this @webertim?

webertim commented 4 years ago

Hi, when I tried to include the package with the underscore it didn't work, because your branch name is actually web-support @miguelpruivo

miguelpruivo commented 4 years ago

Oh, my bad, thanks @webertim.

webertim commented 4 years ago

No problem :)

ethael commented 4 years ago

thanks a lot for all your effort put into this. do you have at least some rough estimations when this feature can be released?

miguelpruivo commented 4 years ago

@ethael I believe that shortly. You can already have a sneak peek from the web-support branch, it’s not yet available because I’m hardly trying to have it all bundled within the same package instead of 3 different ones — usually, they create an interface, a web plugin and a mobile one, but I’d rather have it all in one place and it requires more effort due to stubs and conditional imports.

ethael commented 4 years ago

After that pull request of mine I realized, that the web implementation is "not yet implemented". But thanks anyway :) Just for fun I tried to link that handle method to getFilePath. The picker is shown, but then a type mismatch. Let me please know when you'll have something testable, I will gladly test it if needed.

ethael commented 4 years ago

Miguel, I don't want to sound pushy, but do you have any release estimates in mind? I need to deliver a proof of concept during next week. I don't know if I should wrap your plugin by myself to add web support, or your work will be already available.

miguelpruivo commented 4 years ago

@ethael actually I was thinking on a first official release for it somewhere this week — was actually waiting for new Flutter stable to land which has been delayed last week.

At first, I’m not sure if all filters will be available but you can expect it to be official integrated with pub.dev package.

Is there anything in particular that you need?

ethael commented 4 years ago

@miguelpruivo That's some great news. I will wait for the release then.

I need only basic functionality: pick a file(s) and then load the content to memory for later processing and uploading.

miguelpruivo commented 4 years ago

I've been working on it @ethael, expecting to have something by this weekend. I don't want to split it in different packages (interface + web + io) and rather want to have all in one, but that puts an extra effort due to conditional imports between dart:html and dart:io, hence, the differences in the File object that they both export.

I'll keep you updated.

miguelpruivo commented 4 years ago

@ethael I've worked a bit on it and you can now use directly from the branch (web-support) with file filter and multi pick support (just like in io).

However, I'd like to further discuss this a bit more as I'm not totally convinced about the implementation vs. what users what will want with it.

Ideally, the plugin should abstract any dev from having to worry between web, io or desktop implementations, but, right now, and without immediately deprecating getFile() or getMultiFile() methods, that won't be possible as those resolve for either dart:io or dart:html.

Having conditionally imports help but then we loose type safety which I don't really want (that's how it is right now in the branch, by having those methods returning dynamic instead of File.

That leaves me to the conclusion that it should be a good idea to use Flutter's team approach of creating two separated packages: file_picker_web and this file_picker, connected through its own interface.

Just let me know what you think.

miguelpruivo commented 4 years ago

Ok, so I went ahead and used Flutter's team approach to add multiple platform support.

From now on, this project will use:

This resulted in 3 separated projects (what I was trying to avoid) but at the same time, in a better separation of responsibilities.

Right now, and until I have all the tests & documentation done, this will stay on web-support branch, but you should be able to use it immediately through:

file_picker_web: ^1.0.0

Also, I would like everyone to test it as much as possible so we can track any issue that might appear.

Thanks!

ethael commented 4 years ago

@miguelpruivo I have been successfully using that branch since thursday. I relied on getFilePath, but since that was not implemented I reimplemented my code to use getFile instead. Great to know it will be release soon as stable. Thanks a lot again.

Regarding platform specific object as a return value problem: I ran into the same problem lately and I went for custom dto return value with dynamic parameter inside, refering to that platform specific object, but all the common data were accessible via dto.

in your case it would be something like a FileDto, with dynamic file parameter inside, bude all metadata like name, path, size, accessDate, modificationDate, mimeType... would be accessible directly via dto object. so unless you need to run a method with platform specific file as an input parameter, you are good to go with dto alone. If you need it for a method, you won't need to declare it as a locale variable and you will put there fileDto.file directly.

this way we do not have any import clashes in the mulltiplatform app itself. but of course, separate plugins is standard way to go too, but I don't like it either.

ethael commented 4 years ago

I changed the imports to use as yiou requested:

  file_picker: 1.9.0+1
  file_picker_web: ^1.0.0

I ended up with:

errors.dart:146 Uncaught (in promise) Error: Expected a value of type 'String', but got one of type 'FileList'
    at Object.throw_ [as throw] (errors.dart:195)
    at Object.castError (errors.dart:44)
    at Object.cast [as as] (operations.dart:406)
    at Function.as (core_patch.dart:619)
    at getFile (file_picker.dart:48)
    at getFile.next (<anonymous>)
    at onValue (async_patch.dart:47)
    at _RootZone.runUnary (zone.dart:1439)
    at _FutureListener.thenAwait.handleValue (future_impl.dart:141)
    at handleValueCallback (future_impl.dart:682)
    at Function._propagateToListeners (future_impl.dart:711)
    at _Future.new.[_completeWithValue] (future_impl.dart:526)
    at async._AsyncCallbackEntry.new.callback (future_impl.dart:556)
    at Object._microtaskLoop (schedule_microtask.dart:43)
    at _startMicrotaskLoop (schedule_microtask.dart:52)
    at async_patch.dart:168

when running on the web

miguelpruivo commented 4 years ago

@ethael this should be an easy one. I’m returning a html.File object but how are you picking them right now?

ethael commented 4 years ago

the call was FilePicker.getFile() executed in debug mode from IDE (so in chrome browser) stacktrace is copied from chrome dev tools console flutter 1.17 (latest beta)

miguelpruivo commented 4 years ago

@ethael ok, I’ll take a look ASAP.

miguelpruivo commented 4 years ago

@ethael actually, just created an empty project and added the:

file_picker_web: 1.0.0

with the following code on the _increment() method:

    html.File f = await FilePicker.getFile();
    print(f.name);

and it works just fine and prints the name (you don't need to import the file_picker: 1.9.0).

Just make sure you are using File object from dart:html and not dart:io.

Let me know if it worked for you.

ethael commented 4 years ago

I had to stabilize this functionality for my project during last weekend, so I have an independent working implementation right now. But thanks for quick response. Appreciated. I did the test of your current solution bcause you requested. Ok I will take a look during afternoon and will re-test.

miguelpruivo commented 4 years ago

No problem, take your time @ethael. Thank you for the input.

ethael commented 4 years ago

@miguelpruivo I was not able to reproduce the problem again. Probably some kind of my fault (dependency cache or bad hot reload)

miguelpruivo commented 4 years ago

Great @ethael.

I’ll have to do some more testes before merging this into master. But I’d say that currently the feature itself is ready for a first release.

Also, when using Flutter Web you need to use a reader to retrieve the bytes from the File object. It might be handy to add a method for that (on file_picker_web) only.

Maadhav commented 4 years ago

@ethael actually, just created an empty project and added the:

file_picker_web: 1.0.0

with the following code on the _increment() method:

    html.File f = await FilePicker.getFile();
    print(f.name);

and it works just fine and prints the name (you don't need to import the file_picker: 1.9.0).

Just make sure you are using File object from dart:html and not dart:io.

Let me know if it worked for you.

Could you rather make the example code like this instead of importing both the plugins?

miguelpruivo commented 4 years ago

Sure @Maadhav, I also have plans to land it on master with a new update. Just wanted more people to test it out and also add some more nice to have features on it along with more web documentation.

Maadhav commented 4 years ago

Thanks

On Sun, May 17, 2020 at 6:01 PM Miguel Ruivo notifications@github.com wrote:

Sure @Maadhav https://github.com/Maadhav, I also have plans to land it on master with a new update. Just wanted more people to test it out and also add some more nice to have features on it along with more web documentation.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/miguelpruivo/flutter_file_picker/issues/229#issuecomment-629789458, or unsubscribe https://github.com/notifications/unsubscribe-auth/AFCMXY3OYRGOPRLHYNXPGDLRR7KI3ANCNFSM4L7ES5UQ .

-- Regards, Maadhav Sharma

Maxim-Filimonov commented 4 years ago

file_picker: git: url: https://github.com/miguelpruivo/flutter_file_picker ref: web-support

This doesn't seem to work anymore as project has been split into different directories. What is the right way to test out web implementation?

Maadhav commented 4 years ago

https://pub.dev/packages/file_picker_web Just add this to your pubspec.yaml file: file_picker_web: 1.0.0

miguelpruivo commented 4 years ago

@Maxim-Filimonov what @Maadhav said. Thanks!

Maxim-Filimonov commented 4 years ago

I just tried this:

  file_picker: ^1.10.0
  file_picker_web: ^1.0.0

Got an error on trying to execute the following code:

var file = await FilePicker.getFile();

Error:

Overflow on channel: miguelruivo.flutter.plugins.filepicker.  Messages on this channel are being discarded in FIFO fashion.  The engine may not be running or you need to adjust the buffer size if of the channel.
[FilePicker] Unsupported operation. Method not found. The exception thrown was: MissingPluginException(No implementation found for method any on channel miguelruivo.flutter.plugins.filepicker)
Error: MissingPluginException(No implementation found for method any on channel miguelruivo.flutter.plugins.filepicker)

It seem to be failing in _getPath method.

Maadhav commented 4 years ago

I just tried this:

  file_picker: ^1.10.0
  file_picker_web: ^1.0.0

Got an error on trying to execute the following code:

var file = await FilePicker.getFile();

Error:

Overflow on channel: miguelruivo.flutter.plugins.filepicker.  Messages on this channel are being discarded in FIFO fashion.  The engine may not be running or you need to adjust the buffer size if of the channel.
[FilePicker] Unsupported operation. Method not found. The exception thrown was: MissingPluginException(No implementation found for method any on channel miguelruivo.flutter.plugins.filepicker)
Error: MissingPluginException(No implementation found for method any on channel miguelruivo.flutter.plugins.filepicker)

It seem to be failing in _getPath method.

Did u try by stopping the application and re-run from scratch MissingPluginException is usually due to that

mechelon commented 4 years ago

Do I get this right - if I have an application that should run on mobile and web, I need to implementations of the FilePicker? Or should file_picker_web run as well on mobile? Because as of now, it doesn't because of the dependency on dart:html.

Maadhav commented 4 years ago

file_picker_web is completely for web and file_picker is for the rest platforms. The author has plans to implement file picker for all the platforms in one single package but for now, u could maybe use https://api.flutter.dev/flutter/foundation/kIsWeb-constant.html for implementing in ur app if u want to have features for all.

miguelpruivo commented 4 years ago

@mechelon you will struggle to have both plugins running on the same app, the same goes for any other plugin that will either depend of io or html packages. You'll have to stub and use conditional imports so they can be resolved at compile time whether you are running on mobile or web.

You won't be able to build apps for mobile with dart:html imports, neither for the web with dart:io. The easiest and cleanest solution is to provide each platform implementation of a common interface (which is the file_picker_interface for this plugin).

OuSatoru commented 4 years ago

I got the html.File _file with the plugin, then how to upload the _file to a server?

Maadhav commented 4 years ago

Of u are uploading to ur own server then use http or dio. I personally used firebase cloud storage for uploading, which makes it very easy to upload a file to a server.

GregoryConrad commented 4 years ago

@OuSatoru you can read an html file either as text or as bytes using an html.FileReader, which you can then pass along to whatever service you are using. Simple example that reads as text:

    final reader = new FileReader();
    reader.onError.listen((error) => completer.completeError(error));
    reader.readAsText(file);
    await reader.onLoad.first;
    completer.complete(reader.result as String);

Look at the FileReader documentation for other readAs_() methods it provides.

Maxim-Filimonov commented 4 years ago

Of u are uploading to ur own server then use http or dio. I personally used firebase cloud storage for uploading, which makes it very easy to upload a file to a server.

Do you have an example of using firebase cloud storage from web

Maxim-Filimonov commented 4 years ago

I just tried this:

  file_picker: ^1.10.0
  file_picker_web: ^1.0.0

Got an error on trying to execute the following code:

var file = await FilePicker.getFile();

Error:

Overflow on channel: miguelruivo.flutter.plugins.filepicker.  Messages on this channel are being discarded in FIFO fashion.  The engine may not be running or you need to adjust the buffer size if of the channel.
[FilePicker] Unsupported operation. Method not found. The exception thrown was: MissingPluginException(No implementation found for method any on channel miguelruivo.flutter.plugins.filepicker)
Error: MissingPluginException(No implementation found for method any on channel miguelruivo.flutter.plugins.filepicker)

It seem to be failing in _getPath method.

Did u try by stopping the application and re-run from scratch MissingPluginException is usually due to that

The problem was that I was importing filepicker from file_picker, not from file_picker web.

miguelpruivo commented 4 years ago

Web support was merged into master today 🎉

Anyhow, you can keep any further discussion here.

Thank you!

GregoryConrad commented 4 years ago

@miguelpruivo When do you think this will land on pub.dev?

miguelpruivo commented 4 years ago

@GregoryConrad it's been there for a while, you just need to use file_picker_web instead of file_picker. 🙂

GregoryConrad commented 4 years ago

@miguelpruivo Sorry, I meant when the file_picker package itself will be upgraded to add web support so there won't need to be a dependency on file_picker_web and file_picker. In the file_picker_web readme, the following is stated:

(This is only temporary: in the future we hope to make this package an "endorsed" implementation of file_picker, so that it is automatically included in your Flutter Web app when you depend on package:file_picker.)

I was wondering when this will happen.

miguelpruivo commented 4 years ago

@GregoryConrad this template was copied from official Flutter plugins, so I believe that as of now, it's a limitation of plugin's structure.

GregoryConrad commented 4 years ago

@miguelpruivo Excuse me if I am interpreting your comment wrong, but if this is a question of whether or not this is possible: I am pretty sure it is possible. This can be seen with other plugins, such as cloud_firestore. cloud_firestore has transitive dependencies to cloud_firestore_platform_interface and cloud_firestore_web and automatically uses the web/native versions as needed (without any configuration required by the user). I am by no means an expert, but it looks like something as simple as modifying the plugin's pubspec.yaml would do the trick. As an example, see cloud_firestore's pubspec.yaml. Is that what you meant?

GregoryConrad commented 4 years ago

These also look relevant:

miguelpruivo commented 4 years ago

@GregoryConrad that was my first approach when implementing web support, besides that, I've also tried a lot of other things such as conditional imports, the thing is: you can't mix dart:io and dart:html on the same plugin, that's the bottleneck.

This plugin requires you to use File instances and those, can indeed be stubbed to use either dart:io or dart:html, but there are a lot of limitations (believe me, I've tried it A LOT before taking this decision) and turns out, that makes it more complex to maintain, otherwise, I wonder why plugins such as video_player have exactly the same structure that I have right now and didn't opt for transitive dependencies.

TL;DR: It seems simple, but as soon as you start mixing dart:html with dart:io the things get messy. If you dig into cloud_firestore_web you'll see that it doesn't use dart:html in any place, that's why it probably works.

jlubeck commented 4 years ago

You probably have, but have you tried using the universal_io package?

miguelpruivo commented 4 years ago

@jlubeck no, why? But I have tried file_picker that supports a way more customization, also supports web, desktop (with go which is a plus for some) and io and is also very well documented. 👀