image

26 Dec 2025

9K

35K

Flutter & Firebase Storage: Download Files with Progress

Providing a smooth user experience is paramount in modern mobile applications. When dealing with file operations, especially downloads, users expect real-time feedback. This article will guide you through implementing file downloads from Firebase Storage in your Flutter application, complete with a progress indicator, ensuring your users are always informed about the download status.

Why Progress Indicators Matter

Downloading files, especially large ones, can take time. Without any feedback, users might assume the app has frozen or that the operation has failed, leading to frustration and potentially abandoning your app. A progress indicator, coupled with status messages, provides transparency and reassurance, significantly enhancing the user experience.

Prerequisites

  • A Flutter project set up.
  • A Firebase project configured for your Flutter app.
  • Firebase Storage enabled in your Firebase project.
  • Firebase Storage rules configured to allow read access to the files you intend to download. For testing, you might use:
    
            rules_version = '2';
            service firebase.storage {
              match /b/{bucket}/o {
                match /{allPaths=**} {
                  allow read: if true;
                  allow write: if request.auth != null; // Restrict write access
                }
              }
            }
            

Setting Up Your Flutter Project

1. Add Dependencies

First, add the necessary packages to your pubspec.yaml file:


dependencies:
  flutter:
    sdk: flutter
  firebase_core: ^2.x.x # Use the latest stable version
  firebase_storage: ^11.x.x # Use the latest stable version
  path_provider: ^2.x.x # For getting local file paths

Run flutter pub get to fetch the new dependencies.

2. Initialize Firebase

Ensure Firebase is initialized before your app runs. A typical setup looks like this in your main.dart:


import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'firebase_options.dart'; // Generated by FlutterFire CLI

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Firebase Download',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const FileDownloadScreen(),
    );
  }
}

Make sure you have run flutterfire configure to generate firebase_options.dart.

Implementing File Download with Progress

Now, let's create a StatefulWidget to handle the download logic and display the progress.


import 'package:flutter/material.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:path_provider/path_provider.dart';
import 'dart:io';

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

  @override
  State createState() => _FileDownloadScreenState();
}

class _FileDownloadScreenState extends State {
  double _downloadProgress = 0.0;
  bool _isDownloading = false;
  String? _downloadStatus;
  File? _localFile; // To store the downloaded file locally

  // Replace this with the actual path to your file in Firebase Storage
  final String _firebaseFilePath = "documents/sample.pdf"; // Example: a PDF file
  final String _localFileName = "downloaded_sample.pdf"; // Name for the local file

  Future _startDownload() async {
    setState(() {
      _isDownloading = true;
      _downloadProgress = 0.0;
      _downloadStatus = "Starting download...";
    });

    try {
      final storageRef = FirebaseStorage.instance.ref();
      final fileRef = storageRef.child(_firebaseFilePath);

      // Get the application documents directory to save the file
      final directory = await getApplicationDocumentsDirectory();
      _localFile = File('${directory.path}/$_localFileName');

      // Create a DownloadTask
      final DownloadTask task = fileRef.writeToFile(_localFile!);

      // Listen to the task's snapshotEvents to get progress updates
      task.snapshotEvents.listen((TaskSnapshot snapshot) {
        setState(() {
          _downloadProgress = snapshot.bytesTransferred / snapshot.totalBytes;
          _downloadStatus = "Downloading: ${(snapshot.bytesTransferred / (1024 * 1024)).toStringAsFixed(2)}MB / ${(snapshot.totalBytes / (1024 * 1024)).toStringAsFixed(2)}MB";
        });

        if (snapshot.state == TaskState.success) {
          setState(() {
            _isDownloading = false;
            _downloadStatus = "Download complete! File saved to: ${_localFile!.path}";
          });
        } else if (snapshot.state == TaskState.error) {
          setState(() {
            _isDownloading = false;
            _downloadStatus = "Download failed: ${snapshot.runtimeType}";
          });
        }
      });
    } on FirebaseException catch (e) {
      setState(() {
        _isDownloading = false;
        _downloadStatus = "Firebase Error: ${e.message}";
      });
    } catch (e) {
      setState(() {
        _isDownloading = false;
        _downloadStatus = "Error: $e";
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('File Download Progress')),
      body: Center(
        child: Padding(
          padding: const EdgeInsets.all(16.0),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              // Display progress bar and percentage if downloading
              if (_isDownloading) ...[
                LinearProgressIndicator(value: _downloadProgress),
                const SizedBox(height: 10),
                Text('${(_downloadProgress * 100).toStringAsFixed(1)}%'),
                const SizedBox(height: 10),
              ],
              // Display download status messages
              if (_downloadStatus != null) Text(_downloadStatus!),
              const SizedBox(height: 20),
              // Button to start download
              ElevatedButton(
                onPressed: _isDownloading ? null : _startDownload, // Disable button while downloading
                child: const Text('Download File'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Explanation:

  1. State Variables:
    • _downloadProgress (double): Stores the current download progress (0.0 to 1.0).
    • _isDownloading (bool): A flag to indicate if a download is currently active.
    • _downloadStatus (String?): Holds messages like "Starting download...", "Downloading: XMB / YMB", or "Download complete!".
    • _localFile (File?): A reference to the file where the downloaded data will be saved on the device.
  2. _startDownload() Method:
    • It initializes the state to reflect that a download has started.
    • It obtains a reference to the specific file in Firebase Storage using FirebaseStorage.instance.ref().child(_firebaseFilePath).
    • path_provider is used to get the device's application documents directory, where the file will be saved.
    • fileRef.writeToFile(_localFile!) initiates the download. This method returns a DownloadTask.
    • We listen to task.snapshotEvents. This is a Stream that emits TaskSnapshot objects whenever the task's state changes or progress is made.
    • Inside the listener, we update _downloadProgress and _downloadStatus using setState, which triggers a UI rebuild to show the latest progress.
    • It checks snapshot.state for TaskState.success or TaskState.error to update the final status.
    • Error handling with try-catch blocks for FirebaseException and other general exceptions is included for robustness.
  3. build Method:
    • A LinearProgressIndicator displays the visual progress based on _downloadProgress.
    • A Text widget shows the percentage.
    • Another Text widget displays the _downloadStatus.
    • An ElevatedButton triggers the _startDownload method. It is disabled while a download is in progress to prevent multiple simultaneous downloads.

Conclusion

By following these steps, you can effectively download files from Firebase Storage in your Flutter application while providing clear and informative progress updates to your users. This approach enhances the user experience by keeping them informed, reducing perceived waiting times, and building trust in your application.

Related Articles

May 14, 2026

Building a Multi-Event Countdown Timer Widget with Reminders, Notifications, Repeat, and Custom Labels in Flutter

Building a Multi-Event Countdown Timer Widget with Reminders, Notifications, Repeat, and Custom Labels in Flutter Countdown timers are essential in many applic

May 11, 2026

Unleashing Dynamic UIs: Flutter's Animation Prowess

Unleashing Dynamic UIs: Flutter's Animation Prowess for Slide & Scale Effects Flutter's declarative UI framework, combined with its powerful animation capabilit

May 11, 2026

Building a Product Detail Page Widget in Flutter with Related Items, Review Carousel, Promo Badges, and Quick Buy

Building a Product Detail Page Widget in Flutter with Related Items, Review Carousel, Promo Badges, and Quick Buy A well-designed Product Detail Page (PDP) is