<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ firebase Storage - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ firebase Storage - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Mon, 27 Jul 2026 17:32:41 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/firebase-storage/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Build an Upload Service in Flutter Web with Firebase ]]>
                </title>
                <description>
                    <![CDATA[ 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... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-an-upload-service-in-flutter-web-with-firebase/</link>
                <guid isPermaLink="false">68bb0031e67497d3bf64ec82</guid>
                
                    <category>
                        <![CDATA[ Flutter ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Dart ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Firebase ]]>
                    </category>
                
                    <category>
                        <![CDATA[ firebase Storage ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Atuoha Anthony ]]>
                </dc:creator>
                <pubDate>Fri, 05 Sep 2025 15:22:25 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1757085725508/3ff2d66d-5b3b-4784-904b-de7b464de41b.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>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.</p>
<p>In this article, you’ll learn how to build a reusable upload service that:</p>
<ol>
<li><p>Uploads single and multiple files to Firebase Storage</p>
</li>
<li><p>Returns file download URLs</p>
</li>
<li><p>Uses Dependency Injection (DI) with <code>injectable</code> to keep the code modular, testable, and easy to maintain</p>
</li>
</ol>
<p>By the end, you will have a production-ready upload service for your Flutter Web project.</p>
<h3 id="heading-table-of-contents">Table of Contents:</h3>
<ol>
<li><p><a class="post-section-overview" href="#heading-why-file-uploads-matter-in-flutter-web">Why File Uploads Matter in Flutter Web</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-upload-flow-overview">Upload Flow Overview</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-define-the-upload-data-model-and-service-interface">How to Define the Upload Data Model and Service Interface</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-implement-the-upload-service">How to Implement the Upload Service</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-handle-errors">How to Handle Errors</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-dependency-injection-with-injectable">Dependency Injection with injectable</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-use-the-upload-service">How to Use the Upload Service</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-best-practices">Best Practices</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-references">References</a></p>
</li>
</ol>
<h2 id="heading-why-file-uploads-matter-in-flutter-web">Why File Uploads Matter in Flutter Web</h2>
<p>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.</p>
<h2 id="heading-upload-flow-overview">Upload Flow Overview</h2>
<p>Here’s a high-level look at how the upload process works:</p>
<ol>
<li><p>The user selects a file or image using a browser file picker.</p>
</li>
<li><p>Flutter reads the file as a <code>Uint8List</code>.</p>
</li>
<li><p>The file is uploaded to Firebase Storage.</p>
</li>
<li><p>A download URL is generated and stored in Firestore (or used directly).</p>
</li>
</ol>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before starting, ensure you have the following:</p>
<ol>
<li><p>A Flutter Web project</p>
<pre><code class="lang-bash"> flutter config --enable-web
 flutter create my_web_project
 <span class="hljs-built_in">cd</span> my_web_project
</code></pre>
</li>
<li><p>Firebase set up in your Flutter app: Follow <a target="_blank" href="https://firebase.google.com/docs/flutter/setup?platform=web">Add Firebase to your Flutter app (Web)</a> and include the Firebase SDK snippet in <code>index.html</code>.</p>
</li>
<li><p>Firebase Storage enabled in the Firebase Console: Go to Build &gt; Storage &gt; Get Started and allow read/write access for testing. Example rules:</p>
<pre><code class="lang-json"> service firebase.storage {
   match /b/{bucket}/o {
     match /{allPaths=**} {
       allow read, write: if <span class="hljs-literal">true</span>;
     }
   }
 }
</code></pre>
<p> Don’t use these rules in production.</p>
</li>
<li><p>Required dependencies in your <code>pubspec.yaml</code>:</p>
<pre><code class="lang-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>
</code></pre>
<p> Run <code>flutter pub get</code> to install.</p>
</li>
</ol>
<h2 id="heading-how-to-define-the-upload-data-model-and-service-interface">How to Define the Upload Data Model and Service Interface</h2>
<p>We begin with a data model to represent the file and a service interface to define the upload contract.</p>
<pre><code class="lang-dart"><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,
  });
}
</code></pre>
<p>Next, create an abstract service that defines what the upload logic should do.</p>
<pre><code class="lang-dart"><span class="hljs-keyword">abstract</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">IUploadService</span> </span>{
  Future&lt;<span class="hljs-built_in">String</span>&gt; uploadDoc({
    <span class="hljs-keyword">required</span> UploadData file,
  });

  Future&lt;<span class="hljs-built_in">List</span>&lt;<span class="hljs-built_in">String</span>&gt;&gt; uploadMultipleDoc({
    <span class="hljs-keyword">required</span> <span class="hljs-built_in">List</span>&lt;UploadData&gt; files,
  });
}
</code></pre>
<p>Here’s what’s happening in this code:</p>
<ul>
<li><p><code>uploadDoc</code>: Uploads one file and returns its download URL</p>
</li>
<li><p><code>uploadMultipleDoc</code>: Uploads multiple files in parallel and returns a list of URLs</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756777666277/35243f4a-4f0f-40b5-92b1-f1f1a5b3474e.png" alt="Diagram: Interface Design" class="image--center mx-auto" width="342" height="323" loading="lazy"></p>
<h2 id="heading-how-to-implement-the-upload-service">How to Implement the Upload Service</h2>
<p>Now let’s implement the upload logic with Firebase Storage.</p>
<pre><code class="lang-dart"><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&lt;<span class="hljs-built_in">String</span>&gt; 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&lt;<span class="hljs-built_in">List</span>&lt;<span class="hljs-built_in">String</span>&gt;&gt; uploadMultipleDoc({<span class="hljs-keyword">required</span> <span class="hljs-built_in">List</span>&lt;UploadData&gt; files}) <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> Future.wait(
      files.map((file) =&gt; uploadDoc(file: file)),
    );
  }
}
</code></pre>
<p>This code defines a <strong>service class</strong> 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:</p>
<p><strong>1. Imports</strong></p>
<ol>
<li><p><code>firebase_storage</code>: provides Firebase Storage SDK for uploading and managing files.</p>
</li>
<li><p><code>flutter/foundation.dart</code>: gives access to constants like <code>kDebugMode</code> for debug logging.</p>
</li>
<li><p><code>injectable.dart</code>: enables dependency injection using the injectable + getIt package.</p>
</li>
<li><p><code>i_upload_service.dart</code>: defines the abstract contract/interface for the upload service.</p>
</li>
<li><p><code>custom_error.dart</code>: defines a custom error class to standardize error handling.</p>
</li>
</ol>
<p><strong>2. Dependency Injection Setup</strong></p>
<pre><code class="lang-dart"><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>{
</code></pre>
<ol>
<li><p><code>@LazySingleton(as: IUploadService)</code> registers <code>UploadService</code> as the implementation of <code>IUploadService</code>.</p>
</li>
<li><p>This means anywhere in the app where <code>IUploadService</code> is requested, getIt will provide an instance of <code>UploadService</code>.</p>
</li>
<li><p>It’s a singleton, so only one instance is created and reused across the app.</p>
</li>
</ol>
<p><strong>3. Constructor</strong></p>
<pre><code class="lang-dart"><span class="hljs-keyword">final</span> FirebaseStorage firebaseStorage;

UploadService({<span class="hljs-keyword">required</span> <span class="hljs-keyword">this</span>.firebaseStorage});
</code></pre>
<ol>
<li><p>The class requires a <code>FirebaseStorage</code> instance, which will also be injected automatically.</p>
</li>
<li><p>This makes the service easier to test and replace.</p>
</li>
</ol>
<p><strong>4. Upload a Single File</strong></p>
<pre><code class="lang-dart"><span class="hljs-meta">@override</span>
Future&lt;<span class="hljs-built_in">String</span>&gt; 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>;
  }
}
</code></pre>
<p>What this code does:</p>
<ol>
<li><p>Creates a reference in Firebase Storage at the path <code>folderName/fileName</code></p>
</li>
<li><p>Uploads the raw file bytes (<code>file.fileData</code>) using <code>putData</code>.</p>
</li>
<li><p>Waits for the upload to complete and retrieves a <code>TaskSnapshot</code>.</p>
</li>
<li><p>From the snapshot, gets the download URL of the uploaded file and returns it.</p>
</li>
<li><p>If a <code>FirebaseException</code> occurs, it wraps the error inside a custom <code>CustomError</code>.</p>
</li>
<li><p>Any other unexpected error is logged (only in debug mode) and rethrown.</p>
</li>
</ol>
<p><strong>5. Upload Multiple Files</strong></p>
<pre><code class="lang-dart"><span class="hljs-meta">@override</span>
Future&lt;<span class="hljs-built_in">List</span>&lt;<span class="hljs-built_in">String</span>&gt;&gt; uploadMultipleDoc({<span class="hljs-keyword">required</span> <span class="hljs-built_in">List</span>&lt;UploadData&gt; files}) <span class="hljs-keyword">async</span> {
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> Future.wait(
    files.map((file) =&gt; uploadDoc(file: file)),
  );
}
</code></pre>
<p>What the code does:</p>
<ol>
<li><p>Accepts a list of <code>UploadData</code> objects.</p>
</li>
<li><p>For each file, it calls <code>uploadDoc</code> (the single upload function).</p>
</li>
<li><p><code>Future.wait</code> runs all uploads <strong>in parallel</strong>, waits for them to complete, and returns a list of download URLs.</p>
</li>
</ol>
<p>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 <code>CustomError</code> to provide cleaner error messages. Multiple uploads are executed in parallel for efficiency.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756776717488/16d18b9c-309c-43a3-b595-74dabe7d7b3c.png" alt="Upload Flow with Firebase Storage" class="image--center mx-auto" width="408" height="317" loading="lazy"></p>
<h2 id="heading-how-to-handle-errors">How to Handle Errors</h2>
<p>Instead of relying on raw <code>print</code> statements, it’s better to use a <strong>structured error class</strong>. 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.</p>
<pre><code class="lang-dart"><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>&lt;<span class="hljs-built_in">Object?</span>&gt; <span class="hljs-keyword">get</span> props =&gt; [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>;
  }
}
</code></pre>
<p>Why you should use this approach:</p>
<ul>
<li><p>Ensures consistency across the project.</p>
</li>
<li><p>Makes errors reusable anywhere in the app.</p>
</li>
<li><p>Allows programmatic handling (for example, act differently based on the error code).</p>
</li>
<li><p>Provides clear debugging information through <code>toString()</code>.</p>
</li>
<li><p>Scales well as your app grows.</p>
</li>
</ul>
<h2 id="heading-dependency-injection-with-injectable">Dependency Injection with injectable</h2>
<p>In a typical app, you might manually create service instances like <code>UploadService</code> or <code>FirebaseStorage</code> wherever you need them. But as your app grows, manually creating and passing dependencies becomes messy, error-prone, and hard to test.</p>
<p>This is where <strong>Dependency Injection</strong> (DI) comes in. DI allows you to declare dependencies once, and let a framework handle creating and providing them wherever they’re needed. The <code>injectable</code> package in Flutter works with <code>getIt</code> to automate this process.</p>
<p>Instead of creating <code>UploadService</code> manually, you configure it with injectable so that your app automatically gets the correct instance when needed, following the singleton or lazy-loading patterns.</p>
<h3 id="heading-step-1-annotate-your-service">Step 1: Annotate your service</h3>
<pre><code class="lang-dart"><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>
}
</code></pre>
<p><code>@LazySingleton(as: IUploadService)</code> tells injectable:</p>
<ul>
<li><p><strong>Lazy</strong>: Only create the instance when it’s first used.</p>
</li>
<li><p><strong>Singleton</strong>: Reuse the same instance throughout the app.</p>
</li>
<li><p><strong>as: IUploadService</strong>: Expose the service via its interface, making testing and swapping implementations easier.</p>
</li>
</ul>
<h3 id="heading-step-2-run-the-generator">Step 2: Run the generator</h3>
<pre><code class="lang-dart">flutter pub run build_runner build
</code></pre>
<p>This command generates code that wiring all your injectable dependencies together, so you don’t have to manually instantiate them.</p>
<h3 id="heading-step-3-create-an-injectable-module">Step 3: Create an injectable module</h3>
<pre><code class="lang-dart"><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 =&gt; FirebaseStorage.instance;
}
</code></pre>
<p>This code is setting up dependency injection for <code>FirebaseStorage</code> using the <code>injectable</code> package. Let me break it down:</p>
<ol>
<li><p><code>@module</code>: The <code>@module</code> annotation tells <code>injectable</code> that this class will act as a provider of external dependencies (things you don’t create manually but get from libraries, SDKs, or APIs).</p>
<p> In this case, <code>FirebaseStorage</code> is coming from the Firebase SDK, so you don’t construct it yourself. You just get an instance from the SDK.</p>
</li>
<li><p><code>abstract class InjectableModule</code>: This is a special module class that contains dependency definitions. Since it’s abstract, it won’t be instantiated directly. Instead, <code>injectable</code> generates code to handle the injection.</p>
</li>
<li><p><code>@lazySingleton</code>: This annotation tells <code>injectable</code> that the dependency should be created <strong>only once</strong> and reused throughout the app (singleton pattern).</p>
<ul>
<li><p><strong>Lazy</strong> means it won’t be created until it’s actually needed.</p>
</li>
<li><p><strong>Singleton</strong> means the same instance will be reused everywhere after the first creation.</p>
</li>
</ul>
</li>
<li><p><code>FirebaseStorage get firebaseStorage =&gt; FirebaseStorage.instance;</code>: This line defines what dependency to provide. Here it’s saying:</p>
<ul>
<li><p>Whenever something in the app needs a <code>FirebaseStorage</code> instance, inject <code>FirebaseStorage.instance</code>.</p>
</li>
<li><p>This way, you don’t manually create or pass around <code>FirebaseStorage</code> yourself – <code>injectable</code> plus <code>getIt</code> handle that automatically.</p>
</li>
</ul>
</li>
</ol>
<p>In practice, this ensures that everywhere in your app where you need <code>FirebaseStorage</code>, you can simply inject it via constructor injection (for example, in your <code>UploadService</code>) without manually instantiating it.</p>
<h3 id="heading-step-4-resolve-the-service-anywhere">Step 4: Resolve the service anywhere</h3>
<pre><code class="lang-dart"><span class="hljs-keyword">final</span> uploadService = getIt&lt;IUploadService&gt;();
</code></pre>
<h4 id="heading-why-we-do-this"><strong>Why we do this</strong></h4>
<p>By using injectable:</p>
<ol>
<li><p>You stop manually instantiating dependencies everywhere.</p>
</li>
<li><p>Your services are easier to test, because you can swap implementations via interfaces.</p>
</li>
<li><p>You ensure singleton patterns and lazy loading without extra boilerplate.</p>
</li>
<li><p>Your app becomes more maintainable, especially as it grows.</p>
</li>
</ol>
<p><strong>In practice:</strong><br>Anywhere in your app where <code>UploadService</code> needs <code>FirebaseStorage</code>, you just inject it via the constructor:</p>
<pre><code class="lang-dart"><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>
}
</code></pre>
<p>Injectable + getIt takes care of providing the correct <code>_firebaseStorage</code> instance automatically.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756776761337/7a32fc39-9147-4e78-ba82-150074cdb066.png" alt="Dependency Injection with getIt &amp; injectable" class="image--center mx-auto" width="408" height="317" loading="lazy"></p>
<h2 id="heading-how-to-use-the-upload-service">How to Use the Upload Service</h2>
<p>The <strong>Upload Service</strong> 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.</p>
<p>The Upload Service provides several options:</p>
<ul>
<li><p><strong>Single file upload</strong> – Upload one file at a time and get its download URL.</p>
</li>
<li><p><strong>Multiple file upload</strong> – Upload a batch of files in one go and receive a list of download URLs.</p>
</li>
<li><p><strong>Error handling</strong> – Any issues during upload (like network errors or permission problems) are caught and can be handled gracefully.</p>
</li>
</ul>
<p>Below, we’ll go step by step through how to use these options in practice.</p>
<h3 id="heading-example-upload-a-single-file"><strong>Example: Upload a single file.</strong></h3>
<pre><code class="lang-dart">Future&lt;<span class="hljs-keyword">void</span>&gt; 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&lt;IUploadService&gt;();
    <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>);
  }
}
</code></pre>
<p>This function <code>uploadFile</code> is a wrapper that prepares a file for upload and delegates the actual uploading to your <code>UploadService</code> via dependency injection. Let me break it down step by step:</p>
<pre><code class="lang-dart">Future&lt;<span class="hljs-keyword">void</span>&gt; 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>,
  );
</code></pre>
<ol>
<li><p>First, it takes a file as raw bytes (<code>Uint8List fileData</code>).</p>
</li>
<li><p>Then it wraps this data in an <code>UploadData</code> object, giving it a <code>fileName</code> (<code>example.txt</code>) and a <code>folderName</code> (<code>documents</code>). This essentially creates metadata about the file, so your upload service knows what to call it and where to store it in Firebase Storage.</p>
</li>
</ol>
<pre><code class="lang-dart">  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">final</span> uploadService = getIt&lt;IUploadService&gt;();
    <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>);
  }
}
</code></pre>
<ol start="3">
<li><p>Next, it retrieves the <code>IUploadService</code> instance using <code>getIt</code> (your dependency injection container). Thanks to the binding you defined earlier (<code>UploadService</code> registered as <code>IUploadService</code>), <code>getIt</code> knows to give you the correct implementation.</p>
</li>
<li><p>It calls <code>uploadService.uploadDoc(file: file)</code> which triggers the actual upload to Firebase Storage. If successful, Firebase returns a download URL of the uploaded file.</p>
</li>
<li><p>The function then prints out:</p>
<ul>
<li><p><code>"Uploaded successfully: &lt;downloadUrl&gt;"</code> if the upload worked.</p>
</li>
<li><p><code>"Upload failed: &lt;error&gt;"</code> if an error occurred (for example, no internet or Firebase permission issues).</p>
</li>
</ul>
</li>
</ol>
<p>In simple terms:</p>
<ol>
<li><p><strong>Input</strong>: Raw file data (bytes).</p>
</li>
<li><p><strong>Process</strong>: Wraps it in an <code>UploadData</code> object → sends it to Firebase via <code>UploadService</code>.</p>
</li>
<li><p><strong>Output</strong>: Prints the public download URL if upload succeeds, or prints an error message if it fails.</p>
</li>
</ol>
<h3 id="heading-example-upload-multiple-files"><strong>Example: Upload multiple files.</strong></h3>
<pre><code class="lang-dart">Future&lt;<span class="hljs-keyword">void</span>&gt; uploadMultiple(<span class="hljs-built_in">List</span>&lt;Uint8List&gt; filesData) <span class="hljs-keyword">async</span> {
  <span class="hljs-keyword">final</span> uploadService = getIt&lt;IUploadService&gt;();

  <span class="hljs-keyword">final</span> files = filesData.map((data) =&gt; 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>);
  }
}
</code></pre>
<p>This function handles batch uploading of multiple files to Firebase Storage using the <code>IUploadService</code>. Let’s break it down step by step:</p>
<h4 id="heading-1-access-the-upload-service">1. Access the upload service</h4>
<pre><code class="lang-dart"><span class="hljs-keyword">final</span> uploadService = getIt&lt;IUploadService&gt;();
</code></pre>
<p>Here, <code>getIt</code> retrieves the registered <code>IUploadService</code> 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.</p>
<h4 id="heading-2-prepare-the-list-of-files">2. Prepare the list of files</h4>
<pre><code class="lang-dart"><span class="hljs-keyword">final</span> files = filesData.map((data) =&gt; 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();
</code></pre>
<p><code>filesData</code> is a list of raw file contents (<code>Uint8List</code>). For each file in the list, it creates an <code>UploadData</code> object.</p>
<p>The filename is generated dynamically using the current timestamp (<code>DateTime.now().millisecondsSinceEpoch</code>), ensuring each file has a unique name.</p>
<p>All files are placed in the <code>"batch_docs"</code> folder in Firebase Storage. This way, you have a structured list of files ready for uploading.</p>
<h4 id="heading-3-upload-multiple-file-mechanism">3. Upload Multiple File Mechanism</h4>
<pre><code class="lang-dart"><span class="hljs-keyword">final</span> urls = <span class="hljs-keyword">await</span> uploadService.uploadMultipleDoc(files: files);
</code></pre>
<p>The <code>uploadService</code> is asked to upload all files in one go using its <code>uploadMultipleDoc</code> method. It uploads each file to Firebase Storage. Once done, it returns a list of download URLs, one for each uploaded file.</p>
<h4 id="heading-4-handle-success-or-failure">4. Handle success or failure</h4>
<pre><code class="lang-dart"><span class="hljs-built_in">print</span>(<span class="hljs-string">'All files uploaded: <span class="hljs-subst">$urls</span>'</span>);
</code></pre>
<p>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).</p>
<pre><code class="lang-dart"><span class="hljs-built_in">print</span>(<span class="hljs-string">'Batch upload failed: <span class="hljs-subst">$e</span>'</span>);
</code></pre>
<p>If something goes wrong, it catches the exception and logs the error message.</p>
<p>In short, this function takes multiple raw files, wraps them into <code>UploadData</code> objects, uploads them all to Firebase Storage using the service layer, and prints the resulting download URLs.</p>
<h2 id="heading-best-practices">Best Practices</h2>
<ol>
<li><p>Validate file size before uploading to avoid oversized files.</p>
</li>
<li><p>Restrict file types (for example, only <code>image/*</code>) to improve security.</p>
</li>
<li><p>Store metadata (like user ID, timestamp) in Firestore along with the download URL.</p>
</li>
<li><p>Use unique paths (<code>uploads/userId/filename</code>) to prevent collisions.</p>
</li>
</ol>
<h2 id="heading-conclusion">Conclusion</h2>
<p>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.</p>
<p>This foundation makes it easy to extend the service further, for example by adding file deletion, upload progress tracking, or authenticated uploads.</p>
<h3 id="heading-references">References</h3>
<ol>
<li><p><a target="_blank" href="https://firebase.google.com/docs/storage/flutter/start">Firebase Storage for Flutter</a></p>
</li>
<li><p><a target="_blank" href="https://pub.dev/documentation/firebase_storage/latest/firebase_storage/firebase_storage-library.html">Firebase Storage API Reference</a></p>
</li>
<li><p><a target="_blank" href="https://pub.dev/packages/injectable">injectable Package</a></p>
</li>
<li><p><a target="_blank" href="https://pub.dev/packages/get_it">get_it Package</a></p>
</li>
</ol>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
