Cleaning Up With finally
Part of the Data and the Network section of Coddy's Flutter journey. Lesson 12 of 27.
Some work has to happen whether a call worked or failed: hide the spinner, turn a button back on. Writing it in both try and catch is easy to forget in one of them. Dart has a third block for it, finally:
try {
await saveName(name);
} catch (e) {
// show the error
} finally {
// always runs: after try, or after catch
}finally runs last, every time: after try when it worked, after catch when it failed. That makes it the one place to undo what you set up before the call.
Here the app greys out Save while it saves, with onPressed: saving ? null : save. A button whose onPressed is null cannot be pressed. If saving never goes back to false, the button stays grey for good.
Challenge
MediumSave with an empty field: the error shows, and Save stays grey, so you can never try again. Move saving = false out of the try block into a finally block, in its own setState.
Try it yourself
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: NameScreen(),
);
}
}
class NameScreen extends StatefulWidget {
const NameScreen({super.key});
@override
State<NameScreen> createState() => _NameScreenState();
}
class _NameScreenState extends State<NameScreen> {
final controller = TextEditingController();
String status = '';
bool saving = false;
@override
void dispose() {
controller.dispose();
super.dispose();
}
Future<void> saveName(String name) async {
await Future.delayed(const Duration(seconds: 1));
if (name.isEmpty) {
throw Exception('The name cannot be empty');
}
}
Future<void> save() async {
setState(() {
saving = true;
});
try {
await saveName(controller.text);
setState(() {
status = 'Saved: ${controller.text}';
saving = false;
});
} catch (e) {
setState(() {
status = '$e';
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextField(
controller: controller,
decoration: const InputDecoration(labelText: 'Name'),
),
const SizedBox(height: 16),
ElevatedButton(onPressed: saving ? null : save, child: const Text('Save')),
const SizedBox(height: 16),
Text(status, style: const TextStyle(fontSize: 18)),
],
),
),
);
}
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Data and the Network
3When Calls Fail
Catching Errors With tryThrowing an ErrorCleaning Up With finallyThe FutureBuilder Error StateA Try Again ButtonPractice on your own: Online Flutter compiler