Close Menu
    DevStackTipsDevStackTips
    • Home
    • News & Updates
      1. Tech & Work
      2. View All

      This week in AI updates: Mistral’s new Le Chat features, ChatGPT updates, and more (September 5, 2025)

      September 6, 2025

      Designing For TV: Principles, Patterns And Practical Guidance (Part 2)

      September 5, 2025

      Neo4j introduces new graph architecture that allows operational and analytics workloads to be run together

      September 5, 2025

      Beyond the benchmarks: Understanding the coding personalities of different LLMs

      September 5, 2025

      Hitachi Energy Pledges $1B to Strengthen US Grid, Build Largest Transformer Plant in Virginia

      September 5, 2025

      How to debug a web app with Playwright MCP and GitHub Copilot

      September 5, 2025

      Between Strategy and Story: Thierry Chopain’s Creative Path

      September 5, 2025

      What You Need to Know About CSS Color Interpolation

      September 5, 2025
    • Development
      1. Algorithms & Data Structures
      2. Artificial Intelligence
      3. Back-End Development
      4. Databases
      5. Front-End Development
      6. Libraries & Frameworks
      7. Machine Learning
      8. Security
      9. Software Engineering
      10. Tools & IDEs
      11. Web Design
      12. Web Development
      13. Web Security
      14. Programming Languages
        • PHP
        • JavaScript
      Featured

      Why browsers throttle JavaScript timers (and what to do about it)

      September 6, 2025
      Recent

      Why browsers throttle JavaScript timers (and what to do about it)

      September 6, 2025

      How to create Google Gemini AI component in Total.js Flow

      September 6, 2025

      Drupal 11’s AI Features: What They Actually Mean for Your Team

      September 5, 2025
    • Operating Systems
      1. Windows
      2. Linux
      3. macOS
      Featured

      Harnessing GitOps on Linux for Seamless, Git-First Infrastructure Management

      September 6, 2025
      Recent

      Harnessing GitOps on Linux for Seamless, Git-First Infrastructure Management

      September 6, 2025

      How DevOps Teams Are Redefining Reliability with NixOS and OSTree-Powered Linux

      September 5, 2025

      Distribution Release: Linux Mint 22.2

      September 4, 2025
    • Learning Resources
      • Books
      • Cheatsheets
      • Tutorials & Guides
    Home»Development»How to Build an Upload Service in Flutter Web with Firebase

    How to Build an Upload Service in Flutter Web with Firebase

    September 6, 2025

    Uploading files is one of the most common requirements in modern web applications. Whether it’s profile pictures, documents, or bulk uploads, users expect a smooth and reliable experience. With Flutter Web and Firebase Storage, you can implement this functionality in a clean and scalable way.

    In this article, you’ll learn how to build a reusable upload service that:

    1. Uploads single and multiple files to Firebase Storage

    2. Returns file download URLs

    3. Uses Dependency Injection (DI) with injectable to keep the code modular, testable, and easy to maintain

    By the end, you will have a production-ready upload service for your Flutter Web project.

    Table of Contents:

    1. Why File Uploads Matter in Flutter Web

    2. Upload Flow Overview

    3. Prerequisites

    4. How to Define the Upload Data Model and Service Interface

    5. How to Implement the Upload Service

    6. How to Handle Errors

    7. Dependency Injection with injectable

    8. How to Use the Upload Service

    9. Best Practices

    10. Conclusion

    11. References

    Why File Uploads Matter in Flutter Web

    When building for the web, users often expect features like uploading a profile picture, submitting documents, or sharing media. Unlike mobile, the web environment requires handling files via browser APIs, which then need to be integrated with backend services like Firebase for persistence.

    Upload Flow Overview

    Here’s a high-level look at how the upload process works:

    1. The user selects a file or image using a browser file picker.

    2. Flutter reads the file as a Uint8List.

    3. The file is uploaded to Firebase Storage.

    4. A download URL is generated and stored in Firestore (or used directly).

    Prerequisites

    Before starting, ensure you have the following:

    1. A Flutter Web project

       flutter config --enable-web
       flutter create my_web_project
       <span class="hljs-built_in">cd</span> my_web_project
      
    2. Firebase set up in your Flutter app: Follow Add Firebase to your Flutter app (Web) and include the Firebase SDK snippet in index.html.

    3. Firebase Storage enabled in the Firebase Console: Go to Build > Storage > Get Started and allow read/write access for testing. Example rules:

       service firebase.storage {
         match /b/{bucket}/o {
           match /{allPaths=**} {
             allow read, write: if <span class="hljs-literal">true</span>;
           }
         }
       }
      

      Don’t use these rules in production.

    4. Required dependencies in your pubspec.yaml:

       <span class="hljs-attr">dependencies:</span>
         <span class="hljs-attr">firebase_core:</span> <span class="hljs-string">^3.13.0</span>
         <span class="hljs-attr">firebase_storage:</span> <span class="hljs-string">^12.4.2</span>
         <span class="hljs-attr">injectable:</span> <span class="hljs-string">^2.3.2</span>
         <span class="hljs-attr">get_it:</span> <span class="hljs-string">^8.0.3</span>
      
       <span class="hljs-attr">dev_dependencies:</span>
         <span class="hljs-attr">build_runner:</span> <span class="hljs-string">^2.4.13</span>
         <span class="hljs-attr">injectable_generator:</span> <span class="hljs-string">^2.4.1</span>
      

      Run flutter pub get to install.

    How to Define the Upload Data Model and Service Interface

    We begin with a data model to represent the file and a service interface to define the upload contract.

    <span class="hljs-keyword">import</span> <span class="hljs-string">'dart:typed_data'</span>;
    
    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UploadData</span> </span>{
      <span class="hljs-keyword">final</span> Uint8List fileData;   <span class="hljs-comment">// File in binary format</span>
      <span class="hljs-keyword">final</span> <span class="hljs-built_in">String</span> folderName;    <span class="hljs-comment">// Folder path in Firebase Storage</span>
      <span class="hljs-keyword">final</span> <span class="hljs-built_in">String</span> fileName;      <span class="hljs-comment">// File name</span>
    
      <span class="hljs-keyword">const</span> UploadData({
        <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.fileData,
        <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.fileName,
        <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.folderName,
      });
    }
    

    Next, create an abstract service that defines what the upload logic should do.

    <span class="hljs-keyword">abstract</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">IUploadService</span> </span>{
      Future<<span class="hljs-built_in">String</span>> uploadDoc({
        <span class="hljs-keyword">required</span> UploadData file,
      });
    
      Future<<span class="hljs-built_in">List</span><<span class="hljs-built_in">String</span>>> uploadMultipleDoc({
        <span class="hljs-keyword">required</span> <span class="hljs-built_in">List</span><UploadData> files,
      });
    }
    

    Here’s what’s happening in this code:

    • uploadDoc: Uploads one file and returns its download URL

    • uploadMultipleDoc: Uploads multiple files in parallel and returns a list of URLs

    Diagram: Interface Design

    How to Implement the Upload Service

    Now let’s implement the upload logic with Firebase Storage.

    <span class="hljs-keyword">import</span> <span class="hljs-string">'package:firebase_storage/firebase_storage.dart'</span>;
    <span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/foundation.dart'</span>;
    <span class="hljs-keyword">import</span> <span class="hljs-string">'package:injectable/injectable.dart'</span>;
    <span class="hljs-keyword">import</span> <span class="hljs-string">'i_upload_service.dart'</span>;
    <span class="hljs-keyword">import</span> <span class="hljs-string">'custom_error.dart'</span>;
    
    <span class="hljs-meta">@LazySingleton</span>(<span class="hljs-keyword">as</span>: IUploadService)
    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UploadService</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">IUploadService</span> </span>{
      <span class="hljs-keyword">final</span> FirebaseStorage firebaseStorage;
    
      UploadService({<span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.firebaseStorage});
    
      <span class="hljs-meta">@override</span>
      Future<<span class="hljs-built_in">String</span>> uploadDoc({<span class="hljs-keyword">required</span> UploadData file}) <span class="hljs-keyword">async</span> {
        <span class="hljs-keyword">try</span> {
          <span class="hljs-keyword">var</span> storageRef = firebaseStorage.ref(<span class="hljs-string">'<span class="hljs-subst">${file.folderName}</span>/<span class="hljs-subst">${file.fileName}</span>'</span>);
          <span class="hljs-keyword">var</span> uploadTask = storageRef.putData(file.fileData);
          TaskSnapshot snapshot = <span class="hljs-keyword">await</span> uploadTask;
          <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> snapshot.ref.getDownloadURL();
        } <span class="hljs-keyword">on</span> FirebaseException <span class="hljs-keyword">catch</span> (e) {
          <span class="hljs-keyword">throw</span> CustomError(
            errorMsg: <span class="hljs-string">"Firebase upload failed: <span class="hljs-subst">${e.message}</span>"</span>,
            code: e.code,
            plugin: e.plugin,
          );
        } <span class="hljs-keyword">catch</span> (e) {
          <span class="hljs-keyword">if</span> (kDebugMode) <span class="hljs-built_in">print</span>(<span class="hljs-string">"Unexpected error: <span class="hljs-subst">$e</span>"</span>);
          <span class="hljs-keyword">rethrow</span>;
        }
      }
    
      <span class="hljs-meta">@override</span>
      Future<<span class="hljs-built_in">List</span><<span class="hljs-built_in">String</span>>> uploadMultipleDoc({<span class="hljs-keyword">required</span> <span class="hljs-built_in">List</span><UploadData> files}) <span class="hljs-keyword">async</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> Future.wait(
          files.map((file) => uploadDoc(file: file)),
        );
      }
    }
    

    This code defines a service class for uploading documents to Firebase Storage in a Flutter app. Let’s break it down step by step so you see exactly what’s happening:

    1. Imports

    1. firebase_storage: provides Firebase Storage SDK for uploading and managing files.

    2. flutter/foundation.dart: gives access to constants like kDebugMode for debug logging.

    3. injectable.dart: enables dependency injection using the injectable + getIt package.

    4. i_upload_service.dart: defines the abstract contract/interface for the upload service.

    5. custom_error.dart: defines a custom error class to standardize error handling.

    2. Dependency Injection Setup

    <span class="hljs-meta">@LazySingleton</span>(<span class="hljs-keyword">as</span>: IUploadService)
    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UploadService</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">IUploadService</span> </span>{
    
    1. @LazySingleton(as: IUploadService) registers UploadService as the implementation of IUploadService.

    2. This means anywhere in the app where IUploadService is requested, getIt will provide an instance of UploadService.

    3. It’s a singleton, so only one instance is created and reused across the app.

    3. Constructor

    <span class="hljs-keyword">final</span> FirebaseStorage firebaseStorage;
    
    UploadService({<span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.firebaseStorage});
    
    1. The class requires a FirebaseStorage instance, which will also be injected automatically.

    2. This makes the service easier to test and replace.

    4. Upload a Single File

    <span class="hljs-meta">@override</span>
    Future<<span class="hljs-built_in">String</span>> uploadDoc({<span class="hljs-keyword">required</span> UploadData file}) <span class="hljs-keyword">async</span> {
      <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">var</span> storageRef = firebaseStorage.ref(<span class="hljs-string">'<span class="hljs-subst">${file.folderName}</span>/<span class="hljs-subst">${file.fileName}</span>'</span>);
        <span class="hljs-keyword">var</span> uploadTask = storageRef.putData(file.fileData);
        TaskSnapshot snapshot = <span class="hljs-keyword">await</span> uploadTask;
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> snapshot.ref.getDownloadURL();
      } <span class="hljs-keyword">on</span> FirebaseException <span class="hljs-keyword">catch</span> (e) {
        <span class="hljs-keyword">throw</span> CustomError(
          errorMsg: <span class="hljs-string">"Firebase upload failed: <span class="hljs-subst">${e.message}</span>"</span>,
          code: e.code,
          plugin: e.plugin,
        );
      } <span class="hljs-keyword">catch</span> (e) {
        <span class="hljs-keyword">if</span> (kDebugMode) <span class="hljs-built_in">print</span>(<span class="hljs-string">"Unexpected error: <span class="hljs-subst">$e</span>"</span>);
        <span class="hljs-keyword">rethrow</span>;
      }
    }
    

    What this code does:

    1. Creates a reference in Firebase Storage at the path folderName/fileName

    2. Uploads the raw file bytes (file.fileData) using putData.

    3. Waits for the upload to complete and retrieves a TaskSnapshot.

    4. From the snapshot, gets the download URL of the uploaded file and returns it.

    5. If a FirebaseException occurs, it wraps the error inside a custom CustomError.

    6. Any other unexpected error is logged (only in debug mode) and rethrown.

    5. Upload Multiple Files

    <span class="hljs-meta">@override</span>
    Future<<span class="hljs-built_in">List</span><<span class="hljs-built_in">String</span>>> uploadMultipleDoc({<span class="hljs-keyword">required</span> <span class="hljs-built_in">List</span><UploadData> files}) <span class="hljs-keyword">async</span> {
      <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> Future.wait(
        files.map((file) => uploadDoc(file: file)),
      );
    }
    

    What the code does:

    1. Accepts a list of UploadData objects.

    2. For each file, it calls uploadDoc (the single upload function).

    3. Future.wait runs all uploads in parallel, waits for them to complete, and returns a list of download URLs.

    This class is a Firebase Storage upload service. It can upload single or multiple documents. It follows dependency injection principles for testability and scalability. It uses error handling with CustomError to provide cleaner error messages. Multiple uploads are executed in parallel for efficiency.

    Upload Flow with Firebase Storage

    How to Handle Errors

    Instead of relying on raw print statements, it’s better to use a structured error class. A structured error class organizes all error information, like the message, code, and source, into a single object. This makes error handling consistent, reusable, and easy to manage. You can inspect, log, or display errors programmatically, which is much more maintainable than scattered prints.

    <span class="hljs-keyword">import</span> <span class="hljs-string">'package:equatable/equatable.dart'</span>;
    
    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CustomError</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Equatable</span> </span>{
      <span class="hljs-keyword">final</span> <span class="hljs-built_in">String</span> errorMsg;
      <span class="hljs-keyword">final</span> <span class="hljs-built_in">String</span> code;
      <span class="hljs-keyword">final</span> <span class="hljs-built_in">String</span> plugin;
    
      <span class="hljs-keyword">const</span> CustomError({
        <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.errorMsg,
        <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.code,
        <span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.plugin,
      });
    
      <span class="hljs-meta">@override</span>
      <span class="hljs-built_in">List</span><<span class="hljs-built_in">Object?</span>> <span class="hljs-keyword">get</span> props => [errorMsg, code, plugin];
    
      <span class="hljs-meta">@override</span>
      <span class="hljs-built_in">String</span> toString() {
        <span class="hljs-keyword">return</span> <span class="hljs-string">'CustomError(errorMsg: <span class="hljs-subst">$errorMsg</span>, code: <span class="hljs-subst">$code</span>, plugin: <span class="hljs-subst">$plugin</span>)'</span>;
      }
    }
    

    Why you should use this approach:

    • Ensures consistency across the project.

    • Makes errors reusable anywhere in the app.

    • Allows programmatic handling (for example, act differently based on the error code).

    • Provides clear debugging information through toString().

    • Scales well as your app grows.

    Dependency Injection with injectable

    In a typical app, you might manually create service instances like UploadService or FirebaseStorage wherever you need them. But as your app grows, manually creating and passing dependencies becomes messy, error-prone, and hard to test.

    This is where Dependency Injection (DI) comes in. DI allows you to declare dependencies once, and let a framework handle creating and providing them wherever they’re needed. The injectable package in Flutter works with getIt to automate this process.

    Instead of creating UploadService manually, you configure it with injectable so that your app automatically gets the correct instance when needed, following the singleton or lazy-loading patterns.

    Step 1: Annotate your service

    <span class="hljs-meta">@LazySingleton</span>(<span class="hljs-keyword">as</span>: IUploadService)
    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UploadService</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">IUploadService</span> </span>{
      <span class="hljs-comment">// your upload logic here</span>
    }
    

    @LazySingleton(as: IUploadService) tells injectable:

    • Lazy: Only create the instance when it’s first used.

    • Singleton: Reuse the same instance throughout the app.

    • as: IUploadService: Expose the service via its interface, making testing and swapping implementations easier.

    Step 2: Run the generator

    flutter pub run build_runner build
    

    This command generates code that wiring all your injectable dependencies together, so you don’t have to manually instantiate them.

    Step 3: Create an injectable module

    <span class="hljs-keyword">import</span> <span class="hljs-string">'package:firebase_storage/firebase_storage.dart'</span>;
    <span class="hljs-keyword">import</span> <span class="hljs-string">'package:injectable/injectable.dart'</span>;
    
    <span class="hljs-meta">@module</span>
    <span class="hljs-keyword">abstract</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">InjectableModule</span> </span>{
      <span class="hljs-meta">@lazySingleton</span>
      FirebaseStorage <span class="hljs-keyword">get</span> firebaseStorage => FirebaseStorage.instance;
    }
    

    This code is setting up dependency injection for FirebaseStorage using the injectable package. Let me break it down:

    1. @module: The @module annotation tells injectable that this class will act as a provider of external dependencies (things you don’t create manually but get from libraries, SDKs, or APIs).

      In this case, FirebaseStorage is coming from the Firebase SDK, so you don’t construct it yourself. You just get an instance from the SDK.

    2. abstract class InjectableModule: This is a special module class that contains dependency definitions. Since it’s abstract, it won’t be instantiated directly. Instead, injectable generates code to handle the injection.

    3. @lazySingleton: This annotation tells injectable that the dependency should be created only once and reused throughout the app (singleton pattern).

      • Lazy means it won’t be created until it’s actually needed.

      • Singleton means the same instance will be reused everywhere after the first creation.

    4. FirebaseStorage get firebaseStorage => FirebaseStorage.instance;: This line defines what dependency to provide. Here it’s saying:

      • Whenever something in the app needs a FirebaseStorage instance, inject FirebaseStorage.instance.

      • This way, you don’t manually create or pass around FirebaseStorage yourself – injectable plus getIt handle that automatically.

    In practice, this ensures that everywhere in your app where you need FirebaseStorage, you can simply inject it via constructor injection (for example, in your UploadService) without manually instantiating it.

    Step 4: Resolve the service anywhere

    <span class="hljs-keyword">final</span> uploadService = getIt<IUploadService>();
    

    Why we do this

    By using injectable:

    1. You stop manually instantiating dependencies everywhere.

    2. Your services are easier to test, because you can swap implementations via interfaces.

    3. You ensure singleton patterns and lazy loading without extra boilerplate.

    4. Your app becomes more maintainable, especially as it grows.

    In practice:
    Anywhere in your app where UploadService needs FirebaseStorage, you just inject it via the constructor:

    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UploadService</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">IUploadService</span> </span>{
      <span class="hljs-keyword">final</span> FirebaseStorage _firebaseStorage;
    
      UploadService(<span class="hljs-keyword">this</span>._firebaseStorage);
    
      <span class="hljs-comment">// Use _firebaseStorage here</span>
    }
    

    Injectable + getIt takes care of providing the correct _firebaseStorage instance automatically.

    Dependency Injection with getIt & injectable

    How to Use the Upload Service

    The Upload Service is a modular, reusable service in your app that handles uploading files to Firebase Storage. By using this service, you abstract away direct Firebase interactions, keep your code clean, and leverage dependency injection to access the service anywhere in your app.

    The Upload Service provides several options:

    • Single file upload – Upload one file at a time and get its download URL.

    • Multiple file upload – Upload a batch of files in one go and receive a list of download URLs.

    • Error handling – Any issues during upload (like network errors or permission problems) are caught and can be handled gracefully.

    Below, we’ll go step by step through how to use these options in practice.

    Example: Upload a single file.

    Future<<span class="hljs-keyword">void</span>> uploadFile(Uint8List fileData) <span class="hljs-keyword">async</span> {
      <span class="hljs-keyword">final</span> file = UploadData(
        fileData: fileData,
        fileName: <span class="hljs-string">'example.txt'</span>,
        folderName: <span class="hljs-string">'documents'</span>,
      );
    
      <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">final</span> uploadService = getIt<IUploadService>();
        <span class="hljs-keyword">final</span> downloadUrl = <span class="hljs-keyword">await</span> uploadService.uploadDoc(file: file);
        <span class="hljs-built_in">print</span>(<span class="hljs-string">'Uploaded successfully: <span class="hljs-subst">$downloadUrl</span>'</span>);
      } <span class="hljs-keyword">catch</span> (e) {
        <span class="hljs-built_in">print</span>(<span class="hljs-string">'Upload failed: <span class="hljs-subst">$e</span>'</span>);
      }
    }
    

    This function uploadFile is a wrapper that prepares a file for upload and delegates the actual uploading to your UploadService via dependency injection. Let me break it down step by step:

    Future<<span class="hljs-keyword">void</span>> uploadFile(Uint8List fileData) <span class="hljs-keyword">async</span> {
      <span class="hljs-keyword">final</span> file = UploadData(
        fileData: fileData,
        fileName: <span class="hljs-string">'example.txt'</span>,
        folderName: <span class="hljs-string">'documents'</span>,
      );
    
    1. First, it takes a file as raw bytes (Uint8List fileData).

    2. Then it wraps this data in an UploadData object, giving it a fileName (example.txt) and a folderName (documents). This essentially creates metadata about the file, so your upload service knows what to call it and where to store it in Firebase Storage.

      <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">final</span> uploadService = getIt<IUploadService>();
        <span class="hljs-keyword">final</span> downloadUrl = <span class="hljs-keyword">await</span> uploadService.uploadDoc(file: file);
        <span class="hljs-built_in">print</span>(<span class="hljs-string">'Uploaded successfully: <span class="hljs-subst">$downloadUrl</span>'</span>);
      } <span class="hljs-keyword">catch</span> (e) {
        <span class="hljs-built_in">print</span>(<span class="hljs-string">'Upload failed: <span class="hljs-subst">$e</span>'</span>);
      }
    }
    
    1. Next, it retrieves the IUploadService instance using getIt (your dependency injection container). Thanks to the binding you defined earlier (UploadService registered as IUploadService), getIt knows to give you the correct implementation.

    2. It calls uploadService.uploadDoc(file: file) which triggers the actual upload to Firebase Storage. If successful, Firebase returns a download URL of the uploaded file.

    3. The function then prints out:

      • "Uploaded successfully: <downloadUrl>" if the upload worked.

      • "Upload failed: <error>" if an error occurred (for example, no internet or Firebase permission issues).

    In simple terms:

    1. Input: Raw file data (bytes).

    2. Process: Wraps it in an UploadData object → sends it to Firebase via UploadService.

    3. Output: Prints the public download URL if upload succeeds, or prints an error message if it fails.

    Example: Upload multiple files.

    Future<<span class="hljs-keyword">void</span>> uploadMultiple(<span class="hljs-built_in">List</span><Uint8List> filesData) <span class="hljs-keyword">async</span> {
      <span class="hljs-keyword">final</span> uploadService = getIt<IUploadService>();
    
      <span class="hljs-keyword">final</span> files = filesData.map((data) => UploadData(
        fileData: data,
        fileName: <span class="hljs-string">'<span class="hljs-subst">${DateTime.now().millisecondsSinceEpoch}</span>.txt'</span>,
        folderName: <span class="hljs-string">'batch_docs'</span>,
      )).toList();
    
      <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">final</span> urls = <span class="hljs-keyword">await</span> uploadService.uploadMultipleDoc(files: files);
        <span class="hljs-built_in">print</span>(<span class="hljs-string">'All files uploaded: <span class="hljs-subst">$urls</span>'</span>);
      } <span class="hljs-keyword">catch</span> (e) {
        <span class="hljs-built_in">print</span>(<span class="hljs-string">'Batch upload failed: <span class="hljs-subst">$e</span>'</span>);
      }
    }
    

    This function handles batch uploading of multiple files to Firebase Storage using the IUploadService. Let’s break it down step by step:

    1. Access the upload service

    <span class="hljs-keyword">final</span> uploadService = getIt<IUploadService>();
    

    Here, getIt retrieves the registered IUploadService instance through dependency injection. This service abstracts all the logic of uploading files, so you don’t deal with Firebase APIs directly in this method.

    2. Prepare the list of files

    <span class="hljs-keyword">final</span> files = filesData.map((data) => UploadData(
      fileData: data,
      fileName: <span class="hljs-string">'<span class="hljs-subst">${DateTime.now().millisecondsSinceEpoch}</span>.txt'</span>,
      folderName: <span class="hljs-string">'batch_docs'</span>,
    )).toList();
    

    filesData is a list of raw file contents (Uint8List). For each file in the list, it creates an UploadData object.

    The filename is generated dynamically using the current timestamp (DateTime.now().millisecondsSinceEpoch), ensuring each file has a unique name.

    All files are placed in the "batch_docs" folder in Firebase Storage. This way, you have a structured list of files ready for uploading.

    3. Upload Multiple File Mechanism

    <span class="hljs-keyword">final</span> urls = <span class="hljs-keyword">await</span> uploadService.uploadMultipleDoc(files: files);
    

    The uploadService is asked to upload all files in one go using its uploadMultipleDoc method. It uploads each file to Firebase Storage. Once done, it returns a list of download URLs, one for each uploaded file.

    4. Handle success or failure

    <span class="hljs-built_in">print</span>(<span class="hljs-string">'All files uploaded: <span class="hljs-subst">$urls</span>'</span>);
    

    On success, it prints out the URLs of all uploaded files (so you can later use them, for example, to display or share the documents).

    <span class="hljs-built_in">print</span>(<span class="hljs-string">'Batch upload failed: <span class="hljs-subst">$e</span>'</span>);
    

    If something goes wrong, it catches the exception and logs the error message.

    In short, this function takes multiple raw files, wraps them into UploadData objects, uploads them all to Firebase Storage using the service layer, and prints the resulting download URLs.

    Best Practices

    1. Validate file size before uploading to avoid oversized files.

    2. Restrict file types (for example, only image/*) to improve security.

    3. Store metadata (like user ID, timestamp) in Firestore along with the download URL.

    4. Use unique paths (uploads/userId/filename) to prevent collisions.

    Conclusion

    You now have a reusable and modular upload service for Flutter Web that supports single and multiple file uploads, handles errors in a structured way, and uses Dependency Injection for clean architecture.

    This foundation makes it easy to extend the service further, for example by adding file deletion, upload progress tracking, or authenticated uploads.

    References

    1. Firebase Storage for Flutter

    2. Firebase Storage API Reference

    3. injectable Package

    4. get_it Package

    Source: freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More 

    Facebook Twitter Reddit Email Copy Link
    Previous ArticleArrays, Slices, and Maps in Go: a Quick Guide to Collection Types
    Next Article How to Build an AI Study Planner Agent using Gemini in Python

    Related Posts

    Development

    How to focus on building your skills when everything’s so distracting with Ania Kubów [Podcast #187]

    September 6, 2025
    Development

    Introducing freeCodeCamp Daily Python and JavaScript Challenges – Solve a New Programming Puzzle Every Day

    September 6, 2025
    Leave A Reply Cancel Reply

    For security, use of Google's reCAPTCHA service is required which is subject to the Google Privacy Policy and Terms of Use.

    Continue Reading

    CVE-2025-6286 – PHPGurukul COVID19 Testing Management System Open Redirect Vulnerability

    Common Vulnerabilities and Exposures (CVEs)

    Unpatched Wazuh servers targeted by Mirai botnets (CVE-2025-24016)

    Security

    CVE-2025-4435 – TarFile Errorlevel Extraction Vulnerability

    Common Vulnerabilities and Exposures (CVEs)

    How to Switch Screens on Windows: The Complete Guide to Multi-Monitor Productivity 

    Operating Systems

    Highlights

    Building a Context-Aware Multi-Agent AI System Using Nomic Embeddings and Gemini LLM

    July 27, 2025

    In this tutorial, we walk through the complete implementation of an advanced AI agent system…

    Hackers Attacking Network Edge Devices to Compromise SMB Organizations

    April 22, 2025

    PHP 8.5 Introduces an INI Diff Option

    July 10, 2025

    AMD Radeon RX 9060 XT GPU Now Available In 8GB & 16GB VRAM Options

    June 6, 2025
    © DevStackTips 2025. All rights reserved.
    • Contact
    • Privacy Policy

    Type above and press Enter to search. Press Esc to cancel.