Flutter – Upload file

Fileuploading. Works on all platforms (iOS, Android and web)

import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'package:file_picker/file_picker.dart';

class FilePickerTest extends StatefulWidget {
  const FilePickerTest({super.key});

  @override
  State<FilePickerTest> createState() => _FilePickerTestState();
}

class _FilePickerTestState extends State<FilePickerTest> {
  Uint8List? dataBytes;

  /*
   * Simpler Stream Handling: If your goal is to process the entire stream from start to finish in a simpler,
   * more readable way without needing immediate feedback, then await for is recommended.
   * It simplifies the code because it allows you to handle data in a sequential,
   * synchronous style rather than dealing with callbacks and the complexities of stream events.
   */
  Future<void> pickFilesMethod1({
    required Function callback,
    List<String>? allowedExtensions,
    bool allowMultiple = false,
  }) async {
    FilePickerResult? result = await FilePicker.platform.pickFiles(
      withReadStream: true,
      allowMultiple: allowMultiple,
      type: FileType.any,
      onFileLoading: (FilePickerStatus status) => debugPrint('status: $status'),
    );

    if (result != null) {
      for (PlatformFile file in result.files) {
        // Max filesize 5 MB
        if (file.size > 5000000) {
          debugPrint('Invalid file size: ${file.size} bytes');
          await callback([], 406); // Error code for large file
          return;
        }

        final Stream<List<int>> stream = file.readStream!;
        final List<int> bytes = [];

        try {
          // Use `await for` for simpler and more readable stream handling
          await for (final chunk in stream) {
            bytes.addAll(chunk);
          }

          // Successfully processed file
          await callback(bytes, 201);
        } catch (error) {
          debugPrint('An error occurred while reading the file: $error');
          await callback([], 500); // General error code
        }
      }
    }
  }

  /*
   * Real-Time Processing:
   * If your use case requires handling each data chunk as soon as it becomes available (for example,
   * in a live data stream or when showing a progress bar), then stream.listen() is appropriate.
   */
  Future<void> pickFilesMethod2({
    required Function callback,
    List<String>? allowedExtensions,
    bool allowMultiple = false,
  }) async {
    FilePickerResult? result = await FilePicker.platform.pickFiles(
      withReadStream: true,
      allowMultiple: allowMultiple,
      //type: (allowedExtensions != null) ? FileType.custom : FileType.any,
      //allowedExtensions: allowedExtensions,
      type: FileType.image,
      onFileLoading: (FilePickerStatus status) => debugPrint('status: $status'),
    );

    if (result != null) {
      for (PlatformFile file in result.files) {
        Stream<List<int>> stream = file.readStream!;
        List<int> bytes = [];
        stream.listen(
          (data) {
            bytes.addAll(data);
          },
          onError: (err) {
            debugPrint('An error: $err');
          },
          onDone: () async {
            if (file.size > 5000000) {
              //max 5Mbytes
              await callback([], 406);
              debugPrint('Invalid filesize');
              return;
            }
            callback(bytes, 201);
          },
          cancelOnError: true,
        );
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Container(
        decoration: BoxDecoration(
          border: Border.all(),
          borderRadius: const BorderRadius.all(
            Radius.circular(10),
          ),
        ),
        margin: const EdgeInsets.all(10),
        width: double.infinity,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ElevatedButton(
              onPressed: () => pickFilesMethod1(
                  callback: (bytes, status) => setState(() {
                        dataBytes = Uint8List.fromList(bytes);
                      })),
              child: const Icon(Icons.upload_file),
            ),
            const SizedBox(
              height: 20,
            ),
            if (dataBytes == null)
              const SizedBox(
                width: 500,
                child: Placeholder(),
              ),
            if (dataBytes != null)
              Image.memory(
                dataBytes!,
                width: 500,
              ),
          ],
        ),
      ),
    );
  }
}