Commit 66386255 authored by Adam Barth's avatar Adam Barth Committed by GitHub

Factor WillPopScope out of Form (#8311)

This functionality is independently useful.

Fixes #8293
parent 434d6c3f
...@@ -6,6 +6,7 @@ import 'package:flutter/foundation.dart'; ...@@ -6,6 +6,7 @@ import 'package:flutter/foundation.dart';
import 'framework.dart'; import 'framework.dart';
import 'routes.dart'; import 'routes.dart';
import 'will_pop_scope.dart';
/// An optional container for grouping together multiple form field widgets /// An optional container for grouping together multiple form field widgets
/// (e.g. [Input] widgets). /// (e.g. [Input] widgets).
...@@ -64,35 +65,6 @@ class Form extends StatefulWidget { ...@@ -64,35 +65,6 @@ class Form extends StatefulWidget {
class FormState extends State<Form> { class FormState extends State<Form> {
int _generation = 0; int _generation = 0;
Set<FormFieldState<dynamic>> _fields = new Set<FormFieldState<dynamic>>(); Set<FormFieldState<dynamic>> _fields = new Set<FormFieldState<dynamic>>();
ModalRoute<dynamic> _route;
@override
void dependenciesChanged() {
super.dependenciesChanged();
if (_route != null && config.onWillPop != null)
_route.removeScopedWillPopCallback(config.onWillPop);
_route = ModalRoute.of(context);
if (_route != null && config.onWillPop != null)
_route.addScopedWillPopCallback(config.onWillPop);
}
@override
void didUpdateConfig(Form oldConfig) {
assert(_route == ModalRoute.of(context));
if (config.onWillPop != oldConfig.onWillPop && _route != null) {
if (oldConfig.onWillPop != null)
_route.removeScopedWillPopCallback(oldConfig.onWillPop);
if (config.onWillPop != null)
_route.addScopedWillPopCallback(config.onWillPop);
}
}
@override
void dispose() {
if (_route != null && config.onWillPop != null)
_route.removeScopedWillPopCallback(config.onWillPop);
super.dispose();
}
// Called when a form field has changed. This will cause all form fields // Called when a form field has changed. This will cause all form fields
// to rebuild, useful if form fields have interdependencies. // to rebuild, useful if form fields have interdependencies.
...@@ -114,10 +86,13 @@ class FormState extends State<Form> { ...@@ -114,10 +86,13 @@ class FormState extends State<Form> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (config.autovalidate) if (config.autovalidate)
_validate(); _validate();
return new _FormScope( return new WillPopScope(
formState: this, onWillPop: config.onWillPop,
generation: _generation, child: new _FormScope(
child: config.child formState: this,
generation: _generation,
child: config.child,
),
); );
} }
......
...@@ -688,10 +688,29 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T ...@@ -688,10 +688,29 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
/// Enables this route to veto attempts by the user to dismiss it. /// Enables this route to veto attempts by the user to dismiss it.
/// ///
/// This callback is typically added by a stateful descendant of the modal route. /// This callback is typically added using a [WillPopScope] widget. That
/// A stateful widget shown in a modal route, like the child passed to /// widget finds the enclosing [ModalRoute] and uses this function to register
/// [showDialog], can look up its modal route and then add a callback in its /// this callback:
/// `dependenciesChanged` method: ///
/// ```dart
/// Widget build(BuildContext context) {
/// return new WillPopScope(
/// onWillPop: askTheUserIfTheyAreSure,
/// child: ...,
/// );
/// }
/// ```
///
/// This callback runs asynchronously and it's possible that it will be called
/// after its route has been disposed. The callback should check [mounted]
/// before doing anything.
///
/// A typical application of this callback would be to warn the user about
/// unsaved [Form] data if the user attempts to back out of the form. In that
/// case, use the [Form.onWillPop] property to register the callback.
///
/// To register a callback manually, look up the enclosing [ModalRoute] in a
/// [State.dependenciesChanged] callback:
/// ///
/// ```dart /// ```dart
/// ModalRoute<dynamic> _route; /// ModalRoute<dynamic> _route;
...@@ -705,32 +724,28 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T ...@@ -705,32 +724,28 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
/// } /// }
/// ``` /// ```
/// ///
/// A typical application of this callback would be to warn the user about /// If you register a callback manually, be sure to remove the callback with
/// unsaved [Form] data if the user attempts to back out of the form. /// [removeScopedWillPopCallback] by the time the widget has been disposed. A
/// /// stateful widget can do this in its dispose method (continuing the previous
/// This callback runs asynchronously and it's possible that it will be called /// example):
/// after its route has been disposed. The callback should check [mounted] before
/// doing anything.
///
/// A widget that adds a scopedWillPopCallback must ensure that the callback
/// is removed with [removeScopedWillPopCallback] by the time the widget has
/// been disposed. A stateful widget can do this in its dispose method
/// (continuing the previous example):
/// ///
/// ```dart /// ```dart
/// @override /// @override
/// void dispose() { /// void dispose() {
/// _route?.removeScopedWillPopCallback(askTheUserIfTheyAreSure); /// _route?.removeScopedWillPopCallback(askTheUserIfTheyAreSure);
/// _route = null;
/// super.dispose(); /// super.dispose();
/// } /// }
/// ``` /// ```
/// ///
/// See also: /// See also:
/// ///
/// * [Form], which provides an `onWillPop` callback that uses this mechanism. /// * [WillPopScope], which manages the registration and unregistration
/// * [willPop], which runs the callbacks added with this method. /// process automatically.
/// * [removeScopedWillPopCallback], which removes a callback from the list /// * [Form], which provides an `onWillPop` callback that uses this mechanism.
/// that [willPop] checks. /// * [willPop], which runs the callbacks added with this method.
/// * [removeScopedWillPopCallback], which removes a callback from the list
/// that [willPop] checks.
void addScopedWillPopCallback(WillPopCallback callback) { void addScopedWillPopCallback(WillPopCallback callback) {
assert(_scopeKey.currentState != null); assert(_scopeKey.currentState != null);
_scopeKey.currentState.addWillPopCallback(callback); _scopeKey.currentState.addWillPopCallback(callback);
......
// Copyright 2016 The Chromium 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/foundation.dart';
import 'framework.dart';
import 'routes.dart';
/// Registers a callback to veto attempts by the user to dismiss the enclosing
/// [ModalRoute].
///
/// See also:
///
/// * [ModalRoute.addScopedWillPopCallback] and [ModalScope.removeScopedWillPopCallback],
/// which this widget uses to register and unregister [onWillPop].
class WillPopScope extends StatefulWidget {
/// Creates a widget that registers a callback to veto attempts by the user to
/// dismiss the enclosing [ModalRoute].
///
/// The [child] argument must not be null.
WillPopScope({
Key key,
@required this.child,
@required this.onWillPop,
}) : super(key: key) {
assert(child != null);
}
/// The widget below this widget in the tree.
final Widget child;
/// Called to veto attempts by the user to dismiss the enclosing [ModalRoute].
///
/// If the callback returns a Future that resolves to false, the enclosing
/// route will not be popped.
WillPopCallback onWillPop;
@override
_WillPopScopeState createState() => new _WillPopScopeState();
}
class _WillPopScopeState extends State<WillPopScope> {
ModalRoute<dynamic> _route;
@override
void dependenciesChanged() {
super.dependenciesChanged();
if (config.onWillPop != null)
_route?.removeScopedWillPopCallback(config.onWillPop);
_route = ModalRoute.of(context);
if (config.onWillPop != null)
_route?.addScopedWillPopCallback(config.onWillPop);
}
@override
void didUpdateConfig(WillPopScope oldConfig) {
assert(_route == ModalRoute.of(context));
if (config.onWillPop != oldConfig.onWillPop && _route != null) {
if (oldConfig.onWillPop != null)
_route.removeScopedWillPopCallback(oldConfig.onWillPop);
if (config.onWillPop != null)
_route.addScopedWillPopCallback(config.onWillPop);
}
}
@override
void dispose() {
if (config.onWillPop != null)
_route?.removeScopedWillPopCallback(config.onWillPop);
super.dispose();
}
@override
Widget build(BuildContext context) => config.child;
}
...@@ -66,5 +66,6 @@ export 'src/widgets/title.dart'; ...@@ -66,5 +66,6 @@ export 'src/widgets/title.dart';
export 'src/widgets/transitions.dart'; export 'src/widgets/transitions.dart';
export 'src/widgets/unique_widget.dart'; export 'src/widgets/unique_widget.dart';
export 'src/widgets/viewport.dart'; export 'src/widgets/viewport.dart';
export 'src/widgets/will_pop_scope.dart';
export 'package:vector_math/vector_math_64.dart' show Matrix4; export 'package:vector_math/vector_math_64.dart' show Matrix4;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment