This article shows you how to create a loading dialog from scratch in Flutter.
Table of Contents
Overview
There are many scenarios where you have to perform an asynchronous computation (future), such as making HTTP requests, processing files, fetching data from a local database, etc. Because a future cannot provide a result immediately, the user has to wait for a while. In this case, we can display a loading dialog to let him or her know what is going on. Another benefit of the loading dialog is that it can prevent the user from interacting with the app (editing text fields, hitting buttons, or something like that) while the future is not done yet.
In Flutter, a normal dialog can be closed manually when the user taps somewhere outside it. However, a loading dialog should NOT be closed like that. It should only go away automatically when the future finishes., like so:
// show the loading dialog
showDialog(
// The user CANNOT close this dialog by pressing outsite it
barrierDismissible: false,
context: context,
builder: (_) {
return Dialog(
/*
put a CircularProgressIndicator() here
*/
);
}
);
// the future
await someFuture();
// close the dialog automatically
Navigator.of(context).pop();
For more clarity, please see the complete example below.
Example
Preview
The tiny app we are going to build has a button in the center of the screen. When the button gets pressed, a loading dialog will show up. After 3 seconds, the loading dialog will automatically disappear.
The Code
// main.dart
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(
// Remove the debug banner
debugShowCheckedModeBanner: false,
title: 'KindaCode.com',
theme: ThemeData(
primarySwatch: Colors.indigo,
),
home: const HomePage());
}
}
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
void _fetchData(BuildContext context, [bool mounted = true]) async {
// show the loading dialog
showDialog(
// The user CANNOT close this dialog by pressing outsite it
barrierDismissible: false,
context: context,
builder: (_) {
return Dialog(
// The background color
backgroundColor: Colors.white,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 20),
child: Column(
mainAxisSize: MainAxisSize.min,
children: const [
// The loading indicator
CircularProgressIndicator(),
SizedBox(
height: 15,
),
// Some text
Text('Loading...')
],
),
),
);
});
// Your asynchronous computation here (fetching data from an API, processing files, inserting something to the database, etc)
await Future.delayed(const Duration(seconds: 3));
// Close the dialog programmatically
// We use "mounted" variable to get rid of the "Do not use BuildContexts across async gaps" warning
if (!mounted) return;
Navigator.of(context).pop();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('KindaCode.com')),
body: Center(
child: ElevatedButton(
onPressed: () => _fetchData(context),
child: const Text('Load Data'),
),
),
);
}
}
Conclusion
You’ve learned how to make a loading dialog from native ingredients provided by Flutter. No third-party packages are required. If you’d like to explore more new and amazing things about modern Flutter development, take a look at the following articles:
- Using NavigationRail and BottomNavigationBar in Flutter
- Flutter FutureBuilder example (null safety)
- 2 Ways to Fetch Data from APIs in Flutter
- Flutter + Firebase Storage: Upload, Retrieve, and Delete files
- Best Libraries for Making HTTP Requests in Flutter
You can also check out our Flutter category page or Dart category page for the latest tutorials and examples.