Upload files to server with Flutter Web


Icons made by DinosoftLabs from www.flaticon.com

For the last couple of weeks, I've been looking for a way to upload files with Flutter Web. I even opened two Github issues. I searched in StackOverflow and I only found examples with the dart:io library, which is not compatible with Flutter Web. Finally I found a way to send my files to an API, and it seemed to me that it wouldn't be bad to share my solution with others, just in case. That's why in this article I will give you a step-by-step guide on how to Upload Files to a server with Flutter Web



1. Start Visual Studio Code. Go to menu “View -> Command Palette” and select the “Flutter: New Project” option:


2. Create a folder called widgets inside the lib folder and create a web_upload.dart file. This is where we are going to work:


3. Also, create a folder apps, and inside it create a main_app.dart file:


4. Remove anything from the main.dart file, import the main_app.dart file and call your main app:

import 'package:flutter/material.dart';
import 'package:web_upload/apps/main_app.dart';

void main() => runApp(UploadApp());

5. This is great! Our Flutter Web App is coming to life! It might look like a lot of files/steps but this will be a structure of our app. Open the main_app.dart and add a stateless widget that will return our main widget app:

import 'package:flutter/material.dart';
import 'package:web_upload/widgets/web_upload.dart';

class UploadApp extends StatelessWidget{

  @override
  Widget build(BuildContext context) {
     
    return MaterialApp(
    
       title: 'My App',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        fontFamily: 'Quicksand',
        primarySwatch: Colors.purple,
      ),
      home:  FileUploadApp(),
      
       

    );
  } 
 }

6. Open the web_upload.dart widget and create the FileUploadApp Stateful widget (a form will be in it). Copy the following code, and as you can see, we are returning a SafeArea with a Form in the body:

import 'package:flutter/material.dart';

class FileUploadApp extends StatefulWidget {
  @override
  createState() => _FileUploadAppState();
}

class _FileUploadAppState extends State {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build

    return SafeArea(
      child: Scaffold(
        appBar: AppBar(
          title: Text('A Flutter Web file picker'),
        ),
        body: Container(
          child: new Form(
            child: Padding(
            padding: const EdgeInsets.only(top: 16.0, left: 28),
              child: new Container(
                width: 350,
              child: Column(
                  children: [
                     
                  ]
              )
              ),
                ),
          ),
        ),
      ),
    );
  } }

7. Sweet! Let’s see it working in the web browser:


8. Inside our widget, create a better widget structure by adding a column, and inside it a Widget, which will hold the upload button:

Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          
                        ])

9. Add a material button, which will be used to open up a dialog to select a file:

MaterialButton(
                            color: Colors.pink,
                            elevation: 8,
                            highlightElevation: 2,
                            shape: RoundedRectangleBorder(
                                borderRadius: BorderRadius.circular(8)),
                            textColor: Colors.white,
                            child: Text('Select a file'),
                            onPressed: () {},
                          ),


10. In order to be able to select a file in Flutter Web, import the HTML library, Async library, Convert library and also the Typed Data library:

import 'dart:html' as html;
import 'dart:typed_data';
import 'dart:async';
import 'dart:convert';
import 'package:http_parser/http_parser.dart';

11. Create an async function startWebFilePicker and call the File Upload Input Element:

startWebFilePicker() async {
  html.InputElement uploadInput = html.FileUploadInputElement();
  uploadInput.multiple = true;
  uploadInput.draggable = true;
  uploadInput.click();
}

12. Go back to the material button created in  step 9, and in the onPressed event, call the startWebFilePicker function:

onPressed: () {
   startWebFilePicker();
   },

14. If you start the Web App right now, and click on the select file button, a file picker dialog will open:


15.So far, what we have done will only open the dialog but will not select the file. Let’s create a List variable that will hold the file as indexable collection of objects with a length, and also a fixed-length list variable:


List<int> _selectedFile;
Uint8List _bytesData;

16. Now, go back to the startWebFilePicker async function, and add an onChange listener in which we will get the file content as data URL:

uploadInput.onChange.listen((e) {
  final files = uploadInput.files;
  final file = files[0];
  final reader = new html.FileReader();
});

17. Create a _handleResult function which will be called in the startWebFilePicker’s onChange listener so that it can convert the file as set is as bytes data:

void _handleResult(Object result) {
  setState(() {
          _bytesData = Base64Decoder().convert(result.toString().split(",").last);
          _selectedFile = _bytesData;
  });

Note: With this method, to convert from Base64 to byte data, you MUST use .tostring().split(",").last otherwise you WILL NOT be able to send the file.

18. Inside the startWebFilePicker function, add a reader to load the file and call the data handler (our _handleResult function):

reader.onLoadEnd.listen((e) {
        _handleResult(reader.result);
      });
      reader.readAsDataUrl(file);

19. The file selection is now ready. Let’s prepare our app to send the file to the server. First, prepare the form by creating a global form key:

GlobalKey _formKey = new GlobalKey();

20. In the form, add the global key to the form and also set the autovalidation to true:

child: new Form(
              autovalidate: true,
              key: _formKey,

21. Below the Upload button, add a Divider and another button that will call a function to send the file to the server on its onPressed event:

Divider(color: Colors.teal,),
    RaisedButton(
    color: Colors.purple,
    elevation: 8.0,
    textColor: Colors.white,
    onPressed: () {
    makeRequest();
    },
    child: Text('Send file to server'),
  ),

22. Create a future async function called makeRequest (added to the send button in step 21):

Future makeRequest() async {
  
}

22. Before getting into the makeRequest function, you have to use the HTTP library. Open your pubspecs.yaml file and add it as a dependency:

  http^0.12.0+2

23. Import the library in the web_upload.dart:

import 'package:http/http.dart' as http;

24. Now, in the makeRequest function, set your API url in a variable:

var url = Uri.parse("http://192.168.23.10/upload_api/web/app_dev.php/api/save-file/");

25.  Create a request variable which will have the MultiPart Request:

var request = new http.MultipartRequest("POST", url);

26. Alright, this part is tricky since there almost no documentation about how to handle files with Flutter Web. In step 17 the file was converted from base64 to a stream of bytes, and this was done because this is the only way right now to upload files. To send the file you need to use request.files.add and also use the HTTP library from step 22 like this:

request.files.add(await http.MultipartFile.fromBytes(
        'file', _selectedFile,
        contentType: new MediaType('application''octet-stream'),
        filename: "file_up"));

http.MultipartFile.fromBytes

No, you cannot use dart:io, and you cannot use .fromPath

You must specify the content type. You could either detect the mime type of the file or just do it the KISS way like I did above

Note: I’m using a hard-coded word as the filename, you can extract the name in the startWebFilePicker function

27. Finally send the form and wait for a response:

request.send().then((response) {
      print("test");
      print(response.statusCode);
      if (response.statusCode == 200) print("Uploaded!");
    });

28. Finally, add a simple dialog to let the user know the file was uploaded:

showDialog(
        barrierDismissible: false,
        context: context,
        child: new AlertDialog(
          title: new Text("Details"),
          //content: new Text("Hello World"),
          content: new SingleChildScrollView(
            child: new ListBody(
              children: [
                new Text("Upload successfull"),
              ],
            ),
          ),
          actions: [
            new FlatButton(
              child: new Text('Aceptar'),
              onPressed: () {
                Navigator.pushAndRemoveUntil(
                  context,
                  MaterialPageRoute(builder: (context) => UploadApp()),
                  (Route<dynamic> route) => false,
                );
              },
            ),
          ],
        ));

29. That’s it! You now can upload files using Flutter Web!







You can get the source code here: https://github.com/rjcalifornia/web_upload

If you want to know how the files are being handled in the backend, I used Symfony and MySQL.

Get the source code for the API here: https://github.com/rjcalifornia/file-upload-api



12 comments

Claudio said...

Nice tutorial, help me a lot, thx.

mail said...

Please create tutorial and submit to YOUTUBE.

Unknown said...

Hi, great blog. Could you please show my how we extract the file name and type? That is the only thing I'm struggling with now. Thank you.

Khang Luong said...

Hello there,
Thanks. Your blog is really helpful.
But when I try to upload a big file, the line
_bytesData = Base64Decoder().convert(result.toString().split(",").last)
is easily crashed.
My way is read from ArrayBuffer instead of DataUrl
reader.readAsDataUrl(file) -> reader.readAsArrayBuffer(file)
And when handling the result, I will cash it to Uint8List.
_selectedFile = result as Uint8List;
Think it is better than converting to string and split it.

inventando said...

Thanks. I was looking for this 2 days.

Unknown said...

thanks a ton for sharing! Worked well.

Anonymous said...

thanks a lot. it works.

Unknown said...

thank you so much for your sharing.
hope you all the best in your life too.

Unknown said...

Thanks for sharing. However, Do you have the instructions on how to set up the backend?

Unknown said...

I try to set up the backend and got the following error. "failed to open stream: No such file or directory in".

Do you know why?
contact me at camaronbrowne@gmail.com

Unknown said...

Hi, Can someone give me PHP code example before this => move_uploaded_file($_FILES["image"]["tmp_name"], basename($_FILES["image"]["name"])).

Unknown said...

You are a life saver. I have been searching online for 3 days, and this is the only tutorial that really helped me. Sharing is caring ! Be blessed!

Powered by Blogger.