Scaricare file con barra di avanzamento in Flutter usando Dio e path_provider
GuideIntermedio35 min Flutter 3.x

Scaricare file con barra di avanzamento in Flutter usando Dio e path_provider

In molte app capita di dover scaricare file (PDF, immagini, archivi) e mostrare all'utente lo stato di avanzamento del download.

In questo tutorial vedremo come implementare un downloader completo usando il pacchetto dio per la richiesta HTTP con callback di progresso e path_provider per ottenere una cartella valida dove salvare i file.

Alla fine avrai un widget riutilizzabile che mostra una LinearProgressIndicator aggiornata in tempo reale e gestisce correttamente gli errori e la cancellazione del download.

  1. 1

    Aggiungere le dipendenze

    Aggiungi al file pubspec.yaml i pacchetti dio (per il download con callback di progresso) e path_provider (per ottenere una directory valida su cui salvare).

    Esegui poi flutter pub get dal terminale.

    dependencies:
      flutter:
        sdk: flutter
      dio: ^5.4.0
      path_provider: ^2.1.2

    Risultato atteso

    I pacchetti vengono scaricati e sono pronti all'uso nel progetto.

  2. 2

    Creare il servizio di download

    Isoliamo la logica di download in una classe dedicata DownloadService. Usiamo getApplicationDocumentsDirectory() da path_provider per ottenere la cartella dove salvare il file e passiamo un onReceiveProgress per ricevere gli aggiornamenti.

    Usiamo anche un CancelToken per poter interrompere il download.

    import 'package:dio/dio.dart';
    import 'package:path_provider/path_provider.dart';
    
    class DownloadService {
      final Dio _dio = Dio();
    
      Future<String> downloadFile({
        required String url,
        required String fileName,
        required void Function(int received, int total) onProgress,
        CancelToken? cancelToken,
      }) async {
        final dir = await getApplicationDocumentsDirectory();
        final savePath = '${dir.path}/$fileName';
    
        await _dio.download(
          url,
          savePath,
          cancelToken: cancelToken,
          onReceiveProgress: (received, total) {
            if (total != -1) {
              onProgress(received, total);
            }
          },
        );
    
        return savePath;
      }
    }

    Risultato atteso

    Hai una classe riutilizzabile che scarica un file e notifica il progresso.

  3. 3

    Costruire lo stato del widget

    Creiamo un StatefulWidget che tiene traccia del valore di progresso (0.0 - 1.0), dello stato del download e di un eventuale CancelToken.

    Calcoliamo la percentuale dividendo i byte ricevuti per il totale.

    import 'package:flutter/material.dart';
    import 'package:dio/dio.dart';
    
    class DownloadPage extends StatefulWidget {
      const DownloadPage({super.key});
    
      @override
      State<DownloadPage> createState() => _DownloadPageState();
    }
    
    class _DownloadPageState extends State<DownloadPage> {
      final DownloadService _service = DownloadService();
      double _progress = 0.0;
      bool _downloading = false;
      String? _filePath;
      CancelToken? _cancelToken;
    
      @override
      Widget build(BuildContext context) {
        return const Placeholder(); // definito nel prossimo passo
      }
    }

    Risultato atteso

    La struttura del widget è pronta per ospitare la logica e l'interfaccia.

  4. 4

    Implementare avvio e cancellazione del download

    Aggiungiamo i metodi _startDownload e _cancelDownload.

    Nel primo aggiorniamo lo stato ad ogni callback di progresso; gestiamo l'eccezione DioException per distinguere una cancellazione volontaria da un errore reale.

    Future<void> _startDownload() async {
      setState(() {
        _downloading = true;
        _progress = 0.0;
        _filePath = null;
      });
      _cancelToken = CancelToken();
    
      try {
        final path = await _service.downloadFile(
          url: 'https://speed.hetzner.de/100MB.bin',
          fileName: 'esempio.bin',
          cancelToken: _cancelToken,
          onProgress: (received, total) {
            setState(() => _progress = received / total);
          },
        );
        setState(() => _filePath = path);
      } on DioException catch (e) {
        if (CancelToken.isCancel(e)) {
          _showSnack('Download annullato');
        } else {
          _showSnack('Errore durante il download');
        }
      } finally {
        setState(() => _downloading = false);
      }
    }
    
    void _cancelDownload() {
      _cancelToken?.cancel();
    }
    
    void _showSnack(String msg) {
      ScaffoldMessenger.of(context)
          .showSnackBar(SnackBar(content: Text(msg)));
    }

    Risultato atteso

    Il download può essere avviato e interrotto, gestendo correttamente gli errori.

  5. 5

    Costruire l'interfaccia con la barra di avanzamento

    Ora completiamo il metodo build mostrando una LinearProgressIndicator legata a _progress, la percentuale in testo e i pulsanti per avviare o annullare.

    Quando il download è completo mostriamo il percorso del file salvato.

    @override
    Widget build(BuildContext context) {
      return Scaffold(
        appBar: AppBar(title: const Text('Download file')),
        body: Padding(
          padding: const EdgeInsets.all(24),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              if (_downloading) ...[
                LinearProgressIndicator(value: _progress),
                const SizedBox(height: 12),
                Text('${(_progress * 100).toStringAsFixed(0)}%'),
                const SizedBox(height: 24),
                ElevatedButton(
                  onPressed: _cancelDownload,
                  child: const Text('Annulla'),
                ),
              ] else ...[
                ElevatedButton(
                  onPressed: _startDownload,
                  child: const Text('Avvia download'),
                ),
              ],
              if (_filePath != null) ...[
                const SizedBox(height: 24),
                Text('File salvato in:\n$_filePath',
                    textAlign: TextAlign.center),
              ],
            ],
          ),
        ),
      );
    }

    Risultato atteso

    L'app mostra una barra di avanzamento che si aggiorna in tempo reale durante il download.

  6. 6

    Testare e gestire i permessi

    Avvia l'app e premi Avvia download: vedrai la percentuale crescere fino al 100% e il percorso del file salvato.

    Salvando in getApplicationDocumentsDirectory() (spazio privato dell'app) non servono permessi runtime su Android e iOS. Se invece vuoi salvare in cartelle pubbliche (es. Download) su Android, dovrai usare permission_handler e gestire lo storage scoped.

    Suggerimento: per download di grandi dimensioni evita di ricostruire il widget ad ogni byte; puoi usare un ValueNotifier<double> per aggiornare solo la barra ed evitare rebuild superflui.

    Risultato atteso

    Il download funziona end-to-end e conosci le implicazioni sui permessi e sulle performance.

CondividiXLinkedInFacebookWhatsApp

Commenti (0)

Ancora nessun commento. Inizia tu!