Search code examples
flutterdartasync-awaitflutter-alertdialogflutter-circularprogressindicator

Show CircularProgressIndicator before showDialog


In my Flutter application I need to allow admin users to download a CSV file with all the data entered into Firebase.

To do this, admins must press a button. If they do it from the web, the file is downloaded via the browser, while if they are on another device, a dialog opens asking if they want to download or share the file.

Creating the file takes some time, so I would like to show a CircularProgressIndicator while waiting for the AlertDialog to open. I don't know how to do this though, because I typically show the CircularProgressIndicator when calling a Future function via FutureBuilder, but showDialog doesn't return a Widget.

So how can I show a CircularProgressIndicator before opening AlertDialog?

Here is my code, thanks in advance!

FilledButton.icon(
  onPressed: () async {
    // This function creates a CSV file by retrieving data from Firebase,
    // and takes some time to run
    final String fileData = await getCsv();

    if (kIsWeb) {
      // running on the web
      downloadFileOnWeb(fileData);
    } else {
      // NOT running on the web
      if (!context.mounted) return;
      return showDialog<void>(
        context: context,
        builder: (context) => AlertDialog(
          content: const Text("Get report"),
          actions: [
            IconButton(
              icon: const Icon(Icons.download),
              onPressed: () => downloadFile(fileData),
            ),
            IconButton(
              icon: const Icon(Icons.share),
              onPressed: () => shareFile(fileData),
            ),
          ],
        ),
      );
    }
  },
  icon: const Icon(Icons.file_open),
  label: const Text("Report"),
),

Solution

  • Why don't you simply use a bool to keep track of the loading state?

    For example:

    In your state class, create:

    class _CsvPageState extends State<CsvPage> {
      bool _isLoading = false;
    

    and use it in your function:

      Future<void> _handlePress() async {
        setState(() {
          _isLoading = true; // set it to true to show the loading indicator
        });
    
        final String fileData = await getCsv(); // your task, i.e, fetching the CSV data.
    
        setState(() {
          _isLoading = false; // after the fetching is done, set it back to false
        });
    
        _showDownloadDialog();
      }
    

    and in your build, show different widgets based on the state;:

    FilledButton.icon(
          onPressed: _isLoading ? null : _handlePress,
          icon: _isLoading // If loading, show a loading indicator
              ? const SizedBox(child: CircularProgressIndicator())
              : const Icon(Icons.file_open),
          label: const Text("Report"),
        );
    

    complete runnable snippet:

    import 'package:flutter/material.dart';
    
    void main() {
      runApp(const MyApp());
    }
    
    class MyApp extends StatelessWidget {
      const MyApp({Key? key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Stackoverflow Answers',
          home: const MyHomePage(),
        );
      }
    }
    
    class MyHomePage extends StatelessWidget {
      const MyHomePage({Key? key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: const Center(
            child: CsvPage(),
          ),
        );
      }
    }
    
    class CsvPage extends StatefulWidget {
      const CsvPage({Key? key}) : super(key: key);
    
      @override
      _CsvPageState createState() => _CsvPageState();
    }
    
    class _CsvPageState extends State<CsvPage> {
      bool _isLoading = false;
    
      Future<void> _handlePress() async {
        setState(() {
          _isLoading = true;
        });
    
        final String fileData = await getCsv(); // Fetch the CSV data
    
        setState(() {
          _isLoading = false;
        });
    
        _showDownloadDialog(fileData);
      }
    
      void _showDownloadDialog(String fileData) {
        showDialog<void>(
          context: context,
          builder: (context) => AlertDialog(
            content: const Text("Get report"),
            actions: [
              IconButton(
                icon: const Icon(Icons.download),
                onPressed: () => Navigator.of(context).pop(),
              ),
              IconButton(
                icon: const Icon(Icons.share),
                onPressed: () => Navigator.of(context).pop(),
              ),
            ],
          ),
        );
      }
    
      @override
      Widget build(BuildContext context) {
        return FilledButton.icon(
          onPressed: _isLoading ? null : _handlePress,
          icon: _isLoading // If loading, show a loading indicator
              ? const SizedBox(child: CircularProgressIndicator())
              : const Icon(Icons.file_open),
          label: const Text("Report"),
        );
      }
    }
    
    Future<String> getCsv() async {
      // Simulate a delay to fetch the CSV data
      await Future.delayed(const Duration(seconds: 3));
      return 'Bla bla bla, do your CSV data fetching';
    }
    

    See also