| // 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. |
| |
| // This sample demonstrates how to use a PopScope to wrap a widget that |
| // may pop the page with a result. |
| |
| import 'package:flutter/material.dart'; |
| |
| void main() => runApp(const NavigatorPopHandlerApp()); |
| |
| class NavigatorPopHandlerApp extends StatelessWidget { |
| const NavigatorPopHandlerApp({super.key}); |
| |
| @override |
| Widget build(BuildContext context) { |
| return MaterialApp( |
| initialRoute: '/home', |
| onGenerateRoute: (RouteSettings settings) { |
| return switch (settings.name) { |
| '/two' => MaterialPageRoute<FormData>( |
| builder: (BuildContext context) => const _PageTwo(), |
| ), |
| _ => MaterialPageRoute<void>( |
| builder: (BuildContext context) => const _HomePage(), |
| ), |
| }; |
| }, |
| ); |
| } |
| } |
| |
| class _HomePage extends StatefulWidget { |
| const _HomePage(); |
| |
| @override |
| State<_HomePage> createState() => _HomePageState(); |
| } |
| |
| class _HomePageState extends State<_HomePage> { |
| FormData? _formData; |
| |
| @override |
| Widget build(BuildContext context) { |
| return Scaffold( |
| body: Center( |
| child: Column( |
| mainAxisAlignment: MainAxisAlignment.center, |
| children: <Widget>[ |
| const Text('Page One'), |
| if (_formData != null) |
| Text('Hello ${_formData!.name}, whose favorite food is ${_formData!.favoriteFood}.'), |
| TextButton( |
| onPressed: () async { |
| final FormData formData = |
| await Navigator.of(context).pushNamed<FormData?>('/two') |
| ?? const FormData(); |
| if (formData != _formData) { |
| setState(() { |
| _formData = formData; |
| }); |
| } |
| }, |
| child: const Text('Next page'), |
| ), |
| ], |
| ), |
| ), |
| ); |
| } |
| } |
| |
| class _PopScopeWrapper extends StatelessWidget { |
| const _PopScopeWrapper({required this.child}); |
| |
| final Widget child; |
| |
| Future<bool?> _showBackDialog(BuildContext context) { |
| return showDialog<bool>( |
| context: context, |
| builder: (BuildContext context) { |
| return AlertDialog( |
| title: const Text('Are you sure?'), |
| content: const Text( |
| 'Are you sure you want to leave this page?', |
| ), |
| actions: <Widget>[ |
| TextButton( |
| style: TextButton.styleFrom( |
| textStyle: Theme.of(context).textTheme.labelLarge, |
| ), |
| child: const Text('Never mind'), |
| onPressed: () { |
| Navigator.pop(context, false); |
| }, |
| ), |
| TextButton( |
| style: TextButton.styleFrom( |
| textStyle: Theme.of(context).textTheme.labelLarge, |
| ), |
| child: const Text('Leave'), |
| onPressed: () { |
| Navigator.pop(context, true); |
| }, |
| ), |
| ], |
| ); |
| }, |
| ); |
| } |
| |
| @override |
| Widget build(BuildContext context) { |
| return PopScope<FormData>( |
| canPop: false, |
| // The result argument contains the pop result that is defined in `_PageTwo`. |
| onPopInvokedWithResult: (bool didPop, FormData? result) async { |
| if (didPop) { |
| return; |
| } |
| final bool shouldPop = await _showBackDialog(context) ?? false; |
| if (context.mounted && shouldPop) { |
| Navigator.pop(context, result); |
| } |
| }, |
| child: child, |
| ); |
| } |
| } |
| |
| // This is a PopScope wrapper over _PageTwoBody |
| class _PageTwo extends StatelessWidget { |
| const _PageTwo(); |
| |
| @override |
| Widget build(BuildContext context) { |
| return const _PopScopeWrapper( |
| child: _PageTwoBody(), |
| ); |
| } |
| |
| } |
| |
| class _PageTwoBody extends StatefulWidget { |
| const _PageTwoBody(); |
| |
| @override |
| State<_PageTwoBody> createState() => _PageTwoBodyState(); |
| } |
| |
| class _PageTwoBodyState extends State<_PageTwoBody> { |
| FormData _formData = const FormData(); |
| |
| @override |
| Widget build(BuildContext context) { |
| return Scaffold( |
| body: Center( |
| child: Column( |
| mainAxisAlignment: MainAxisAlignment.center, |
| children: <Widget>[ |
| const Text('Page Two'), |
| Form( |
| child: Column( |
| children: <Widget>[ |
| TextFormField( |
| decoration: const InputDecoration( |
| hintText: 'Enter your name.', |
| ), |
| onChanged: (String value) { |
| _formData = _formData.copyWith( |
| name: value, |
| ); |
| }, |
| ), |
| TextFormField( |
| decoration: const InputDecoration( |
| hintText: 'Enter your favorite food.', |
| ), |
| onChanged: (String value) { |
| _formData = _formData.copyWith( |
| favoriteFood: value, |
| ); |
| }, |
| ), |
| ], |
| ), |
| ), |
| TextButton( |
| onPressed: () async { |
| Navigator.maybePop(context, _formData); |
| }, |
| child: const Text('Go back'), |
| ), |
| ], |
| ), |
| ), |
| ); |
| } |
| } |
| |
| @immutable |
| class FormData { |
| const FormData({ |
| this.name = '', |
| this.favoriteFood = '', |
| }); |
| |
| final String name; |
| final String favoriteFood; |
| |
| FormData copyWith({String? name, String? favoriteFood}) { |
| return FormData( |
| name: name ?? this.name, |
| favoriteFood: favoriteFood ?? this.favoriteFood, |
| ); |
| } |
| |
| @override |
| bool operator ==(Object other) { |
| if (identical(this, other)) { |
| return true; |
| } |
| if (other.runtimeType != runtimeType) { |
| return false; |
| } |
| return other is FormData |
| && other.name == name |
| && other.favoriteFood == favoriteFood; |
| } |
| |
| @override |
| int get hashCode => Object.hash(name, favoriteFood); |
| } |