Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
0ce9917f
Commit
0ce9917f
authored
Jan 18, 2017
by
Hans Muller
Committed by
GitHub
Jan 18, 2017
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Support for vetoing an attempt to pop the current route (#7488)
parent
17bc1888
Changes
11
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
479 additions
and
9 deletions
+479
-9
text_field_demo.dart
examples/flutter_gallery/lib/demo/text_field_demo.dart
+31
-0
smoke_test.dart
examples/flutter_gallery/test/smoke_test.dart
+1
-0
update_test.dart
examples/flutter_gallery/test/update_test.dart
+2
-0
page.dart
packages/flutter/lib/src/material/page.dart
+13
-0
scaffold.dart
packages/flutter/lib/src/material/scaffold.dart
+6
-1
app.dart
packages/flutter/lib/src/widgets/app.dart
+5
-2
binding.dart
packages/flutter/lib/src/widgets/binding.dart
+4
-4
form.dart
packages/flutter/lib/src/widgets/form.dart
+33
-2
navigator.dart
packages/flutter/lib/src/widgets/navigator.dart
+44
-0
routes.dart
packages/flutter/lib/src/widgets/routes.dart
+94
-0
will_pop_test.dart
packages/flutter/test/material/will_pop_test.dart
+246
-0
No files found.
examples/flutter_gallery/lib/demo/text_field_demo.dart
View file @
0ce9917f
...
...
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'dart:async'
;
import
'package:flutter/material.dart'
;
class
TextFieldDemo
extends
StatefulWidget
{
...
...
@@ -31,6 +33,7 @@ class TextFieldDemoState extends State<TextFieldDemo> {
}
bool
_autovalidate
=
false
;
bool
_formWasEdited
=
false
;
GlobalKey
<
FormState
>
_formKey
=
new
GlobalKey
<
FormState
>();
GlobalKey
<
FormFieldState
<
InputValue
>>
_passwordFieldKey
=
new
GlobalKey
<
FormFieldState
<
InputValue
>>();
void
_handleSubmitted
()
{
...
...
@@ -45,6 +48,7 @@ class TextFieldDemoState extends State<TextFieldDemo> {
}
String
_validateName
(
InputValue
value
)
{
_formWasEdited
=
true
;
if
(
value
.
text
.
isEmpty
)
return
'Name is required.'
;
RegExp
nameExp
=
new
RegExp
(
r'^[A-za-z ]+$'
);
...
...
@@ -54,6 +58,7 @@ class TextFieldDemoState extends State<TextFieldDemo> {
}
String
_validatePhoneNumber
(
InputValue
value
)
{
_formWasEdited
=
true
;
RegExp
phoneExp
=
new
RegExp
(
r'^\d\d\d-\d\d\d\-\d\d\d\d$'
);
if
(!
phoneExp
.
hasMatch
(
value
.
text
))
return
'###-###-#### - Please enter a valid phone number.'
;
...
...
@@ -61,6 +66,7 @@ class TextFieldDemoState extends State<TextFieldDemo> {
}
String
_validatePassword
(
InputValue
value
)
{
_formWasEdited
=
true
;
FormFieldState
<
InputValue
>
passwordField
=
_passwordFieldKey
.
currentState
;
if
(
passwordField
.
value
==
null
||
passwordField
.
value
.
text
.
isEmpty
)
return
'Please choose a password.'
;
...
...
@@ -69,6 +75,30 @@ class TextFieldDemoState extends State<TextFieldDemo> {
return
null
;
}
Future
<
bool
>
_warnUserAboutInvalidData
()
{
final
FormState
form
=
_formKey
.
currentState
;
if
(!
_formWasEdited
||
form
.
validate
())
return
new
Future
<
bool
>.
value
(
true
);
return
showDialog
/*<bool>*/
(
context:
context
,
child:
new
AlertDialog
(
title:
new
Text
(
'This form has errors'
),
content:
new
Text
(
'Really leave this form?'
),
actions:
<
Widget
>
[
new
FlatButton
(
child:
new
Text
(
'YES'
),
onPressed:
()
{
Navigator
.
of
(
context
).
pop
(
true
);
},
),
new
FlatButton
(
child:
new
Text
(
'NO'
),
onPressed:
()
{
Navigator
.
of
(
context
).
pop
(
false
);
},
),
],
),
);
}
@override
Widget
build
(
BuildContext
context
)
{
return
new
Scaffold
(
...
...
@@ -79,6 +109,7 @@ class TextFieldDemoState extends State<TextFieldDemo> {
body:
new
Form
(
key:
_formKey
,
autovalidate:
_autovalidate
,
onWillPop:
_warnUserAboutInvalidData
,
child:
new
Block
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
16.0
),
children:
<
Widget
>[
...
...
examples/flutter_gallery/test/smoke_test.dart
View file @
0ce9917f
...
...
@@ -44,6 +44,7 @@ Future<Null> smokeDemo(WidgetTester tester, String routeName) async {
expect
(
backButton
,
findsOneWidget
);
await
tester
.
tap
(
backButton
);
await
tester
.
pump
();
// Start the pop "back" operation.
await
tester
.
pump
();
// Complete the willPop() Future.
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
// Wait until it has finished.
return
null
;
}
...
...
examples/flutter_gallery/test/update_test.dart
View file @
0ce9917f
...
...
@@ -33,7 +33,9 @@ void main() {
expect
(
backButton
,
findsOneWidget
);
await
tester
.
tap
(
backButton
);
await
tester
.
pump
();
// Start the pop "back" operation.
await
tester
.
pump
();
// Complete the willPop() Future.
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
// transition is complete
//await tester.pumpUntilNoTransientCallbacks(const Duration(seconds: 1));
expect
(
find
.
text
(
'UPDATE'
),
findsNothing
);
});
...
...
packages/flutter/lib/src/material/page.dart
View file @
0ce9917f
...
...
@@ -200,8 +200,21 @@ class MaterialPageRoute<T> extends PageRoute<T> {
_CupertinoBackGestureController
_backGestureController
;
/// Support for dismissing this route with a horizontal swipe is enabled
/// for [TargetPlatform.iOS]. If attempts to dismiss this route might be
/// vetoed because a [WillPopCallback] was defined for the route then the
/// platform-specific back gesture is disabled.
///
/// See also:
///
/// * [hasScopedWillPopCallback], which is true if a `willPop` callback
/// is defined for this route.
@override
NavigationGestureController
startPopGesture
(
NavigatorState
navigator
)
{
// If attempts to dismiss this route might be vetoed, then do not
// allow the user to dismiss the route with a swipe.
if
(
hasScopedWillPopCallback
)
return
null
;
if
(
controller
.
status
!=
AnimationStatus
.
completed
)
return
null
;
assert
(
_backGestureController
==
null
);
...
...
packages/flutter/lib/src/material/scaffold.dart
View file @
0ce9917f
...
...
@@ -770,6 +770,11 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin {
bool
_shouldShowBackArrow
;
Future
<
Null
>
_back
()
async
{
if
(
await
Navigator
.
willPop
(
context
)
&&
mounted
)
Navigator
.
pop
(
context
);
}
Widget
_getModifiedAppBar
({
EdgeInsets
padding
,
int
elevation
})
{
AppBar
appBar
=
config
.
appBar
;
if
(
appBar
==
null
)
...
...
@@ -800,7 +805,7 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin {
leading
=
new
IconButton
(
icon:
new
Icon
(
backIcon
),
alignment:
FractionalOffset
.
centerLeft
,
onPressed:
()
=>
Navigator
.
pop
(
context
)
,
onPressed:
_back
,
tooltip:
'Back'
// TODO(ianh): Figure out how to localize this string
);
}
...
...
packages/flutter/lib/src/widgets/app.dart
View file @
0ce9917f
...
...
@@ -141,12 +141,15 @@ class _WidgetsAppState extends State<WidgetsApp> implements WidgetsBindingObserv
super
.
dispose
();
}
// On Android: the user has pressed the back button.
@override
bool
didPopRoute
()
{
Future
<
bool
>
didPopRoute
()
async
{
assert
(
mounted
);
NavigatorState
navigator
=
_navigator
.
currentState
;
assert
(
navigator
!=
null
);
return
navigator
.
pop
();
if
(!
await
navigator
.
willPop
())
return
true
;
return
mounted
&&
navigator
.
pop
();
}
@override
...
...
packages/flutter/lib/src/widgets/binding.dart
View file @
0ce9917f
...
...
@@ -34,7 +34,7 @@ abstract class WidgetsBindingObserver {
/// box, and false otherwise. The [WidgetsApp] widget uses this
/// mechanism to notify the [Navigator] widget that it should pop
/// its current route if possible.
bool
didPopRoute
()
=>
false
;
Future
<
bool
>
didPopRoute
()
=>
new
Future
<
bool
>.
value
(
false
)
;
/// Called when the application's dimensions change. For example,
/// when a phone is rotated.
...
...
@@ -158,9 +158,9 @@ abstract class WidgetsBinding extends BindingBase implements GestureBinding, Ren
/// [WidgetsApp] uses this in conjunction with a [Navigator] to
/// cause the back button to close dialog boxes, return from modal
/// pages, and so forth.
void
handlePopRoute
()
{
for
(
WidgetsBindingObserver
observer
in
_observers
)
{
if
(
observer
.
didPopRoute
())
Future
<
Null
>
handlePopRoute
()
async
{
for
(
WidgetsBindingObserver
observer
in
new
List
<
WidgetsBindingObserver
>.
from
(
_observers
)
)
{
if
(
await
observer
.
didPopRoute
())
return
;
}
SystemNavigator
.
pop
();
...
...
packages/flutter/lib/src/widgets/form.dart
View file @
0ce9917f
...
...
@@ -5,6 +5,7 @@
import
'package:flutter/foundation.dart'
;
import
'framework.dart'
;
import
'routes.dart'
;
/// An optional container for grouping together multiple form field widgets
/// (e.g. [Input] widgets).
...
...
@@ -23,6 +24,7 @@ class Form extends StatefulWidget {
Key
key
,
@required
this
.
child
,
this
.
autovalidate
:
false
,
this
.
onWillPop
,
})
:
super
(
key:
key
)
{
assert
(
child
!=
null
);
}
...
...
@@ -48,6 +50,13 @@ class Form extends StatefulWidget {
/// [FormState.validate] to validate.
final
bool
autovalidate
;
/// Enables the form to veto attempts by the user to dismiss the [ModalRoute]
/// that contains the form.
///
/// If the callback returns a Future that resolves to false, the form's route
/// will not be popped.
WillPopCallback
onWillPop
;
@override
FormState
createState
()
=>
new
FormState
();
}
...
...
@@ -56,8 +65,30 @@ class FormState extends State<Form> {
int
_generation
=
0
;
Set
<
FormFieldState
<
dynamic
>>
_fields
=
new
Set
<
FormFieldState
<
dynamic
>>();
/// Called when a form field has changed. This will cause all form fields
/// to rebuild, useful if form fields have interdependencies.
@override
void
dependenciesChanged
()
{
super
.
dependenciesChanged
();
final
ModalRoute
<
dynamic
>
route
=
ModalRoute
.
of
(
context
);
if
(
route
!=
null
&&
config
.
onWillPop
!=
null
)
{
// Avoid adding our callback twice by removing it first.
route
.
removeScopedWillPopCallback
(
config
.
onWillPop
);
route
.
addScopedWillPopCallback
(
config
.
onWillPop
);
}
}
@override
void
didUpdateConfig
(
Form
oldConfig
)
{
final
ModalRoute
<
dynamic
>
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
);
}
}
// Called when a form field has changed. This will cause all form fields
// to rebuild, useful if form fields have interdependencies.
void
_fieldDidChange
()
{
setState
(()
{
++
_generation
;
...
...
packages/flutter/lib/src/widgets/navigator.dart
View file @
0ce9917f
...
...
@@ -66,6 +66,15 @@ abstract class Route<T> {
@mustCallSuper
void
didReplace
(
Route
<
dynamic
>
oldRoute
)
{
}
/// Returns false if this route wants to veto a [Navigator.pop]. This method is
/// called by [Naviagtor.willPop].
///
/// See also:
///
/// * [Form], which provides an `onWillPop` callback that uses this mechanism.
Future
<
bool
>
willPop
()
async
=>
true
;
/// A request was made to pop this route. If the route can handle it
/// internally (e.g. because it has its own stack of internal state) then
/// return false, otherwise return true. Returning false will prevent the
...
...
@@ -109,6 +118,11 @@ abstract class Route<T> {
/// back gesture), this should return a controller object that can be used to
/// control the transition animation's progress. Otherwise, it should return
/// null.
///
/// If attempts to dismiss this route might be vetoed, for example because
/// a [WillPopCallback] was defined for the route, then it may make sense
/// to disable the pop gesture. For example, the iOS back gesture is disabled
/// when [ModalRoute.hasScopedWillCallback] is true.
NavigationGestureController
startPopGesture
(
NavigatorState
navigator
)
{
return
null
;
}
...
...
@@ -461,6 +475,20 @@ class Navigator extends StatefulWidget {
return
Navigator
.
of
(
context
).
push
(
route
);
}
/// Returns the value of the current route's `willPop` method. This method is
/// typically called before a user-initiated [pop]. For example on Android it's
/// called by the binding for the system's back button.
///
/// See also:
///
/// * [Form], which provides an `onWillPop` callback that enables the form
/// to veto a [pop] initiated by the app's back button.
/// * [ModalRoute], which provides a `scopedWillPopCallback` that can be used
/// to define the route's `willPop` method.
static
Future
<
bool
>
willPop
(
BuildContext
context
)
{
return
Navigator
.
of
(
context
).
willPop
();
}
/// Pop a route off the navigator that most tightly encloses the given context.
///
/// Tries to removes the current route, calling its didPop() method. If that
...
...
@@ -743,6 +771,22 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
assert
(()
{
_debugLocked
=
false
;
return
true
;
});
}
/// Returns the value of the current route's `willPop` method. This method is
/// typically called before a user-initiated [pop]. For example on Android it's
/// called by the binding for the system's back button.
///
/// See also:
///
/// * [Form], which provides an `onWillPop` callback that enables the form
/// to veto a [pop] initiated by the app's back button.
/// * [ModalRoute], which has as a `willPop` method that can be defined
/// by a list of [WillPopCallback]s.
Future
<
bool
>
willPop
()
async
{
final
Route
<
dynamic
>
route
=
_history
.
last
;
assert
(
route
.
_navigator
==
this
);
return
route
.
willPop
();
}
/// Removes the top route in the [Navigator]'s history.
///
/// If an argument is provided, that argument will be the return value of the
...
...
packages/flutter/lib/src/widgets/routes.dart
View file @
0ce9917f
...
...
@@ -363,6 +363,12 @@ class _ModalScopeStatus extends InheritedWidget {
}
}
/// Signature for a callback that verifies that it's OK to call [Navigator.pop].
///
/// Used by [Form.onWillPop], [ModalRoute.addScopedWillPopCallback], and
/// [ModalRoute.removeScopedWillPopCallback].
typedef
Future
<
bool
>
WillPopCallback
();
class
_ModalScope
extends
StatefulWidget
{
_ModalScope
({
Key
key
,
...
...
@@ -378,6 +384,9 @@ class _ModalScope extends StatefulWidget {
}
class
_ModalScopeState
extends
State
<
_ModalScope
>
{
// See addScopedWillPopCallback, removeScopedWillPopCallback in ModalRoute.
List
<
WillPopCallback
>
willPopCallbacks
=
<
WillPopCallback
>[];
@override
void
initState
()
{
super
.
initState
();
...
...
@@ -394,9 +403,20 @@ class _ModalScopeState extends State<_ModalScope> {
void
dispose
()
{
config
.
route
.
animation
?.
removeStatusListener
(
_animationStatusChanged
);
config
.
route
.
forwardAnimation
?.
removeStatusListener
(
_animationStatusChanged
);
willPopCallbacks
=
null
;
super
.
dispose
();
}
void
addWillPopCallback
(
WillPopCallback
callback
)
{
assert
(
mounted
);
willPopCallbacks
.
add
(
callback
);
}
void
removeWillPopCallback
(
WillPopCallback
callback
)
{
assert
(
mounted
);
willPopCallbacks
.
remove
(
callback
);
}
void
_animationStatusChanged
(
AnimationStatus
status
)
{
setState
(()
{
// The animation's states are our build state, and they changed already.
...
...
@@ -618,6 +638,80 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
Animation
<
double
>
get
forwardAnimation
=>
_forwardAnimationProxy
;
ProxyAnimation
_forwardAnimationProxy
;
/// Return the value of the first callback added with
/// [addScopedWillPopCallback] that returns false. Otherwise return true.
///
/// Typically this method is not overridden because applications usually
/// don't create modal routes directly, they use higher level primitives
/// like [showDialog]. The scoped [WillPopCallback] list makes it possible
/// for ModalRoute descendants to collectively define the value of `willPop`.
///
/// See also:
///
/// * [Form], which provides an `onWillPop` callback that uses this mechanism.
/// * [addScopedWillPopCallback], which adds a callback to the list this
/// method checks.
/// * [removeScopedWillPopCallback], which removes a callback from the list
/// this method checks.
@override
Future
<
bool
>
willPop
()
async
{
final
_ModalScopeState
scope
=
_scopeKey
.
currentState
;
assert
(
scope
!=
null
);
for
(
WillPopCallback
callback
in
new
List
<
WillPopCallback
>.
from
(
scope
.
willPopCallbacks
))
{
if
(!
await
callback
())
return
false
;
}
return
true
;
}
/// 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.
/// A stateful widget shown in a modal route, like the child passed to
/// [showDialog], can look up its modal route and then add a callback in its
/// `dependenciesChanged` method:
///
/// ```dart
/// @override
/// void dependenciesChanged() {
/// super.dependenciesChanged();
/// ModalRoute.of(context).addScopedWillPopCallback(askTheUserIfTheyAreSure);
/// }
/// ```
///
/// 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.
///
/// 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.
///
/// See also:
///
/// * [Form], which provides an `onWillPop` callback that uses this mechanism.
/// * [willPop], which runs the callbacks added with this method.
/// * [removeScopedWillPopCallback], which removes a callback from the list
/// that [willPop] checks.
void
addScopedWillPopCallback
(
WillPopCallback
callback
)
{
assert
(
_scopeKey
.
currentState
!=
null
);
_scopeKey
.
currentState
.
addWillPopCallback
(
callback
);
}
/// Remove one of the callbacks run by [willPop].
///
/// See also:
///
/// * [Form], which provides an `onWillPop` callback that uses this mechanism.
/// * [addScopedWillPopCallback], which adds callback to the list
/// checked by [willPop].
void
removeScopedWillPopCallback
(
WillPopCallback
callback
)
{
assert
(
_scopeKey
.
currentState
!=
null
);
_scopeKey
.
currentState
.
removeWillPopCallback
(
callback
);
}
bool
get
hasScopedWillPopCallback
{
return
_scopeKey
.
currentState
==
null
||
_scopeKey
.
currentState
.
willPopCallbacks
.
length
>
0
;
}
// Internals
...
...
packages/flutter/test/material/will_pop_test.dart
0 → 100644
View file @
0ce9917f
// 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_test/flutter_test.dart'
;
import
'package:flutter/material.dart'
;
bool
willPopValue
=
false
;
class
SamplePage
extends
StatefulWidget
{
@override
SamplePageState
createState
()
=>
new
SamplePageState
();
}
class
SamplePageState
extends
State
<
SamplePage
>
{
@override
void
dependenciesChanged
()
{
super
.
dependenciesChanged
();
final
ModalRoute
<
Null
>
route
=
ModalRoute
.
of
(
context
);
if
(
route
.
isCurrent
)
route
.
addScopedWillPopCallback
(()
async
=>
willPopValue
);
}
@override
Widget
build
(
BuildContext
context
)
{
return
new
Scaffold
(
appBar:
new
AppBar
(
title:
new
Text
(
'Sample Page'
)),
);
}
}
int
willPopCount
=
0
;
class
SampleForm
extends
StatelessWidget
{
SampleForm
({
Key
key
,
this
.
callback
})
:
super
(
key:
key
);
final
WillPopCallback
callback
;
@override
Widget
build
(
BuildContext
context
)
{
return
new
Scaffold
(
appBar:
new
AppBar
(
title:
new
Text
(
'Sample Form'
)),
body:
new
SizedBox
.
expand
(
child:
new
Form
(
onWillPop:
()
{
willPopCount
+=
1
;
return
callback
();
},
child:
new
InputFormField
(),
),
),
);
}
}
void
main
(
)
{
testWidgets
(
'ModalRoute scopedWillPopupCallback can inhibit back button'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
new
MaterialApp
(
home:
new
Scaffold
(
appBar:
new
AppBar
(
title:
new
Text
(
'Home'
)),
body:
new
Builder
(
builder:
(
BuildContext
context
)
{
return
new
Center
(
child:
new
FlatButton
(
child:
new
Text
(
'X'
),
onPressed:
()
{
showDialog
(
context:
context
,
child:
new
SamplePage
(),
);
},
),
);
},
),
),
),
);
await
tester
.
tap
(
find
.
text
(
'X'
));
await
tester
.
pump
();
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
expect
(
find
.
text
(
'Sample Page'
),
findsOneWidget
);
willPopValue
=
false
;
await
tester
.
tap
(
find
.
byTooltip
(
'Back'
));
await
tester
.
pump
();
await
tester
.
pump
();
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
expect
(
find
.
text
(
'Sample Page'
),
findsOneWidget
);
willPopValue
=
true
;
await
tester
.
tap
(
find
.
byTooltip
(
'Back'
));
await
tester
.
pump
();
await
tester
.
pump
();
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
expect
(
find
.
text
(
'Sample Page'
),
findsNothing
);
});
testWidgets
(
'Form.willPop can inhibit back button'
,
(
WidgetTester
tester
)
async
{
Widget
buildFrame
()
{
return
new
MaterialApp
(
home:
new
Scaffold
(
appBar:
new
AppBar
(
title:
new
Text
(
'Home'
)),
body:
new
Builder
(
builder:
(
BuildContext
context
)
{
return
new
Center
(
child:
new
FlatButton
(
child:
new
Text
(
'X'
),
onPressed:
()
{
Navigator
.
of
(
context
).
push
(
new
MaterialPageRoute
<
Null
>(
builder:
(
BuildContext
context
)
{
return
new
SampleForm
(
callback:
()
=>
new
Future
<
bool
>.
value
(
willPopValue
),
);
}
));
},
),
);
},
),
),
);
}
await
tester
.
pumpWidget
(
buildFrame
());
await
tester
.
tap
(
find
.
text
(
'X'
));
await
tester
.
pump
();
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
expect
(
find
.
text
(
'Sample Form'
),
findsOneWidget
);
willPopValue
=
false
;
willPopCount
=
0
;
await
tester
.
tap
(
find
.
byTooltip
(
'Back'
));
await
tester
.
pump
();
// Start the pop "back" operation.
await
tester
.
pump
();
// Complete the willPop() Future.
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
// Wait until it has finished.
expect
(
find
.
text
(
'Sample Form'
),
findsOneWidget
);
expect
(
willPopCount
,
1
);
willPopValue
=
true
;
willPopCount
=
0
;
await
tester
.
tap
(
find
.
byTooltip
(
'Back'
));
await
tester
.
pump
();
// Start the pop "back" operation.
await
tester
.
pump
();
// Complete the willPop() Future.
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
// Wait until it has finished.
expect
(
find
.
text
(
'Sample Form'
),
findsNothing
);
expect
(
willPopCount
,
1
);
});
testWidgets
(
'Form.willPop callbacks do not accumulate'
,
(
WidgetTester
tester
)
async
{
Future
<
bool
>
showYesNoAlert
(
BuildContext
context
)
{
return
showDialog
/*<bool>*/
(
context:
context
,
child:
new
AlertDialog
(
actions:
<
Widget
>
[
new
FlatButton
(
child:
new
Text
(
'YES'
),
onPressed:
()
{
Navigator
.
of
(
context
).
pop
(
true
);
},
),
new
FlatButton
(
child:
new
Text
(
'NO'
),
onPressed:
()
{
Navigator
.
of
(
context
).
pop
(
false
);
},
),
],
),
);
}
Widget
buildFrame
()
{
return
new
MaterialApp
(
home:
new
Scaffold
(
appBar:
new
AppBar
(
title:
new
Text
(
'Home'
)),
body:
new
Builder
(
builder:
(
BuildContext
context
)
{
return
new
Center
(
child:
new
FlatButton
(
child:
new
Text
(
'X'
),
onPressed:
()
{
Navigator
.
of
(
context
).
push
(
new
MaterialPageRoute
<
Null
>(
builder:
(
BuildContext
context
)
{
return
new
SampleForm
(
callback:
()
=>
showYesNoAlert
(
context
),
);
}
));
},
),
);
},
),
),
);
}
await
tester
.
pumpWidget
(
buildFrame
());
await
tester
.
tap
(
find
.
text
(
'X'
));
await
tester
.
pump
();
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
expect
(
find
.
text
(
'Sample Form'
),
findsOneWidget
);
// Press the Scaffold's back button. This causes the willPop callback
// to run, which shows the YES/NO Alert Dialog. Veto the back operation
// by pressing the Alert's NO button.
await
tester
.
tap
(
find
.
byTooltip
(
'Back'
));
await
tester
.
pump
();
// Start the pop "back" operation.
await
tester
.
pump
();
// Call willPop which will show an Alert.
await
tester
.
tap
(
find
.
text
(
'NO'
));
await
tester
.
pump
();
// Start the dismiss animation.
await
tester
.
pump
();
// Resolve the willPop callback.
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
// Wait until it has finished.
expect
(
find
.
text
(
'Sample Form'
),
findsOneWidget
);
// Do it again. Note that each time the Alert is shown and dismissed
// the FormState's dependenciesChanged() method runs. We're making sure
// that the dependenciesChanged() method doesn't add an extra willPop
// callback.
await
tester
.
tap
(
find
.
byTooltip
(
'Back'
));
await
tester
.
pump
();
// Start the pop "back" operation.
await
tester
.
pump
();
// Call willPop which will show an Alert.
await
tester
.
tap
(
find
.
text
(
'NO'
));
await
tester
.
pump
();
// Start the dismiss animation.
await
tester
.
pump
();
// Resolve the willPop callback.
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
// Wait until it has finished.
expect
(
find
.
text
(
'Sample Form'
),
findsOneWidget
);
// This time really dismiss the SampleForm by pressing the Alert's
// YES button.
await
tester
.
tap
(
find
.
byTooltip
(
'Back'
));
await
tester
.
pump
();
// Start the pop "back" operation.
await
tester
.
pump
();
// Call willPop which will show an Alert.
await
tester
.
tap
(
find
.
text
(
'YES'
));
await
tester
.
pump
();
// Start the dismiss animation.
await
tester
.
pump
();
// Resolve the willPop callback.
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
// Wait until it has finished.
expect
(
find
.
text
(
'Sample Form'
),
findsNothing
);
});
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment