skip to Main Content

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"),
),

2

Answers


  1. 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

    Login or Signup to reply.
  2. To show a CircularProgressIndicator while waiting for your CSV file to load, you can use showDialog to display the loading indicator at the start of your function. Once the file is loaded, you can close the loading dialog and then open the AlertDialog for sharing and downloading the file. Here’s how you can achieve this:

    Step-by-Step Implementation

    1. Display CircularProgressIndicator: At the start of your function, use showDialog to show a CircularProgressIndicator.
    2. Load the CSV File: Perform the file loading operation.
    3. Close CircularProgressIndicator: After the file has loaded, close the loading dialog.
    4. Open the AlertDialog: Show your resultant dialog with options to share or download the file.

    Here’s a complete example of how to implement this:

    FilledButton.icon(
      onPressed: () async {
        // Step 1: Show CircularProgressIndicator
        showDialog(
          context: context,
          barrierDismissible: false, // Prevent dialog from closing on touch outside
          builder: (context) {
            return Center(
              child: CircularProgressIndicator(),
            );
          },
        );
    
        // Step 2: Load the CSV file
        final String fileData = await getCsv();
    
        // Step 3: Close the CircularProgressIndicator
        Navigator.pop(context); // This will close the CircularProgressIndicator
    
        // Step 4: Show AlertDialog with file actions
        if (kIsWeb) {
          // Running on the web
          downloadFileOnWeb(fileData);
        } else {
          // NOT running on the web
          if (!context.mounted) return;
    
          // Show the resultant dialog
          showDialog<void>(
            context: context,
            builder: (context) => AlertDialog(
              title: Text("Report"),
              content: Text("The report is ready for download or sharing."),
              actions: [
                IconButton(
                  icon: Icon(Icons.download),
                  onPressed: () {
                    downloadFile(fileData);
                    Navigator.pop(context); // Close the dialog after action
                  },
                ),
                IconButton(
                  icon: Icon(Icons.share),
                  onPressed: () {
                    shareFile(fileData);
                    Navigator.pop(context); // Close the dialog after action
                  },
                ),
              ],
            ),
          );
        }
      },
      icon: Icon(Icons.file_open),
      label: Text("Report"),
    );
    

    For Your Reference

    • Barrier Dismissible: Setting barrierDismissible to false ensures that the loading dialog doesn’t close if the user taps outside of it, making the UI more robust.
    • Navigator Pop: Use Navigator.pop(context) to close the loading dialog once the file is ready.
    • Context Mounting Check: The context.mounted check ensures that the widget context is still valid when showing the second dialog, which is good practice in asynchronous operations.

    By following these steps, you can effectively show a loading indicator while waiting for your file to load and then proceed with displaying your action dialog.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search