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