| // Copyright 2014 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'package:flutter/material.dart'; |
| import 'package:flutter/services.dart'; |
| |
| /// This sample demonstrates showing a confirmation dialog when the user |
| /// attempts to navigate away from a page with unsaved [Form] data. |
| |
| void main() => runApp(const FormApp()); |
| |
| class FormApp extends StatelessWidget { |
| const FormApp({ |
| super.key, |
| }); |
| |
| @override |
| Widget build(BuildContext context) { |
| return MaterialApp( |
| home: Scaffold( |
| appBar: AppBar( |
| title: const Text('Confirmation Dialog Example'), |
| ), |
| body: Center( |
| child: _SaveableForm(), |
| ), |
| ), |
| ); |
| } |
| } |
| |
| class _SaveableForm extends StatefulWidget { |
| @override |
| State<_SaveableForm> createState() => _SaveableFormState(); |
| } |
| |
| class _SaveableFormState extends State<_SaveableForm> { |
| final TextEditingController _controller = TextEditingController(); |
| String _savedValue = ''; |
| bool _isDirty = false; |
| |
| @override |
| void initState() { |
| super.initState(); |
| _controller.addListener(_onChanged); |
| } |
| |
| @override |
| void dispose() { |
| _controller.removeListener(_onChanged); |
| super.dispose(); |
| } |
| |
| void _onChanged() { |
| final bool nextIsDirty = _savedValue != _controller.text; |
| if (nextIsDirty == _isDirty) { |
| return; |
| } |
| setState(() { |
| _isDirty = nextIsDirty; |
| }); |
| } |
| |
| /// Shows a dialog and resolves to true when the user has indicated that they |
| /// want to pop. |
| /// |
| /// A return value of null indicates a desire not to pop, such as when the |
| /// user has dismissed the modal without tapping a button. |
| Future<bool?> _showDialog() { |
| return showDialog<bool>( |
| context: context, |
| builder: (BuildContext context) { |
| return AlertDialog( |
| title: const Text('Are you sure?'), |
| content: const Text('Any unsaved changes will be lost!'), |
| actions: <Widget>[ |
| TextButton( |
| child: const Text('Yes, discard my changes'), |
| onPressed: () { |
| Navigator.pop(context, true); |
| }, |
| ), |
| TextButton( |
| child: const Text('No, continue editing'), |
| onPressed: () { |
| Navigator.pop(context, false); |
| }, |
| ), |
| ], |
| ); |
| }, |
| ); |
| } |
| |
| void _save(String? value) { |
| final String nextSavedValue = value ?? ''; |
| setState(() { |
| _savedValue = nextSavedValue; |
| _isDirty = nextSavedValue != _controller.text; |
| }); |
| } |
| |
| @override |
| Widget build(BuildContext context) { |
| return Center( |
| child: Column( |
| mainAxisAlignment: MainAxisAlignment.center, |
| children: <Widget>[ |
| const Text('If the field below is unsaved, a confirmation dialog will be shown on back.'), |
| const SizedBox(height: 20.0), |
| Form( |
| canPop: !_isDirty, |
| onPopInvokedWithResult: (bool didPop, Object? result) async { |
| if (didPop) { |
| return; |
| } |
| final bool shouldPop = await _showDialog() ?? false; |
| if (shouldPop) { |
| // Since this is the root route, quit the app where possible by |
| // invoking the SystemNavigator. If this wasn't the root route, |
| // then Navigator.maybePop could be used instead. |
| // See https://github.com/flutter/flutter/issues/11490 |
| SystemNavigator.pop(); |
| } |
| }, |
| autovalidateMode: AutovalidateMode.always, |
| child: Column( |
| mainAxisAlignment: MainAxisAlignment.center, |
| children: <Widget>[ |
| TextFormField( |
| controller: _controller, |
| onFieldSubmitted: (String? value) { |
| _save(value); |
| }, |
| ), |
| TextButton( |
| onPressed: () { |
| _save(_controller.text); |
| }, |
| child: Row( |
| children: <Widget>[ |
| const Text('Save'), |
| if (_controller.text.isNotEmpty) |
| Icon( |
| _isDirty ? Icons.warning : Icons.check, |
| ), |
| ], |
| ), |
| ), |
| ], |
| ), |
| ), |
| TextButton( |
| onPressed: () async { |
| final bool shouldPop = !_isDirty || (await _showDialog() ?? false); |
| if (!shouldPop) { |
| return; |
| } |
| // Since this is the root route, quit the app where possible by |
| // invoking the SystemNavigator. If this wasn't the root route, |
| // then Navigator.maybePop could be used instead. |
| // See https://github.com/flutter/flutter/issues/11490 |
| SystemNavigator.pop(); |
| }, |
| child: const Text('Go back'), |
| ), |
| ], |
| ), |
| ); |
| } |
| } |