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
0cadbce4
Commit
0cadbce4
authored
May 24, 2017
by
Hans Muller
Committed by
GitHub
May 24, 2017
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Added Navigator.removeRoute() (#10298)
parent
69c25424
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
208 additions
and
13 deletions
+208
-13
dropdown.dart
packages/flutter/lib/src/material/dropdown.dart
+10
-2
navigator.dart
packages/flutter/lib/src/widgets/navigator.dart
+58
-6
dropdown_test.dart
packages/flutter/test/material/dropdown_test.dart
+13
-0
navigator_test.dart
packages/flutter/test/widgets/navigator_test.dart
+127
-5
No files found.
packages/flutter/lib/src/material/dropdown.dart
View file @
0cadbce4
...
...
@@ -328,6 +328,10 @@ class _DropdownRoute<T> extends PopupRoute<_DropdownRouteResult<T>> {
child:
menu
,
);
}
void
_dismiss
()
{
navigator
?.
removeRoute
(
this
);
}
}
/// An item in a menu created by a [DropdownButton].
...
...
@@ -486,7 +490,7 @@ class _DropdownButtonState<T> extends State<DropdownButton<T>> with WidgetsBindi
@override
void
dispose
()
{
WidgetsBinding
.
instance
.
removeObserver
(
this
);
//TODO(hansmuller) if _dropDownRoute != null Navigator.remove(context, _dropdownRoute)
_removeDropdownRoute
();
super
.
dispose
();
}
...
...
@@ -494,7 +498,11 @@ class _DropdownButtonState<T> extends State<DropdownButton<T>> with WidgetsBindi
// Defined by WidgetsBindingObserver
@override
void
didChangeMetrics
()
{
//TODO(hansmuller) if _dropDownRoute != null Navigator.remove(context, _dropdownRoute)
_removeDropdownRoute
();
}
void
_removeDropdownRoute
()
{
_dropdownRoute
?.
_dismiss
();
_dropdownRoute
=
null
;
}
...
...
packages/flutter/lib/src/widgets/navigator.dart
View file @
0cadbce4
...
...
@@ -6,6 +6,7 @@ import 'dart:async';
import
'package:flutter/foundation.dart'
;
import
'package:flutter/rendering.dart'
;
import
'package:flutter/scheduler.dart'
;
import
'basic.dart'
;
import
'binding.dart'
;
...
...
@@ -231,12 +232,15 @@ class NavigatorObserver {
NavigatorState
get
navigator
=>
_navigator
;
NavigatorState
_navigator
;
/// The [Navigator] pushed
the given route
.
/// The [Navigator] pushed
`route`
.
void
didPush
(
Route
<
dynamic
>
route
,
Route
<
dynamic
>
previousRoute
)
{
}
/// The [Navigator] popped
the given route
.
/// The [Navigator] popped
`route`
.
void
didPop
(
Route
<
dynamic
>
route
,
Route
<
dynamic
>
previousRoute
)
{
}
/// The [Navigator] removed `route`.
void
didRemove
(
Route
<
dynamic
>
route
,
Route
<
dynamic
>
previousRoute
)
{
}
/// The [Navigator] is being controlled by a user gesture.
///
/// Used for the iOS back gesture.
...
...
@@ -673,6 +677,19 @@ class Navigator extends StatefulWidget {
return
Navigator
.
of
(
context
).
pushReplacement
(
route
,
result:
result
);
}
/// Immediately remove `route` and [Route.dispose] it.
///
/// The route's animation does not run and the future returned from pushing
/// the route will not complete. Ongoing input gestures are cancelled. If
/// the [Navigator] has any [Navigator.observers], they will be notified with
/// [NavigatorObserver.didRemove].
///
/// This method is used to dismiss dropdown menus that are up when the screen's
/// orientation changes.
static
void
removeRoute
(
BuildContext
context
,
Route
<
dynamic
>
route
)
{
return
Navigator
.
of
(
context
).
removeRoute
(
route
);
}
/// The state from the closest instance of this class that encloses the given context.
///
/// Typical usage is as follows:
...
...
@@ -1100,6 +1117,36 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
return
true
;
}
/// Immediately remove `route` and [Route.dispose] it.
///
/// The route's animation does not run and the future returned from pushing
/// the route will not complete. Ongoing input gestures are cancelled. If
/// the [Navigator] has any [Navigator.observers], they will be notified with
/// [NavigatorObserver.didRemove].
///
/// This method is used to dismiss dropdown menus that are up when the screen's
/// orientation changes.
void
removeRoute
(
Route
<
dynamic
>
route
)
{
assert
(
route
!=
null
);
assert
(!
_debugLocked
);
assert
(()
{
_debugLocked
=
true
;
return
true
;
});
assert
(
route
.
_navigator
==
this
);
final
int
index
=
_history
.
indexOf
(
route
);
assert
(
index
!=
-
1
);
final
Route
<
dynamic
>
previousRoute
=
index
>
0
?
_history
[
index
-
1
]
:
null
;
final
Route
<
dynamic
>
nextRoute
=
(
index
+
1
<
_history
.
length
)
?
_history
[
index
+
1
]
:
null
;
setState
(()
{
_history
.
removeAt
(
index
);
previousRoute
?.
didChangeNext
(
nextRoute
);
nextRoute
?.
didChangePrevious
(
previousRoute
);
for
(
NavigatorObserver
observer
in
widget
.
observers
)
observer
.
didRemove
(
route
,
previousRoute
);
route
.
dispose
();
});
assert
(()
{
_debugLocked
=
false
;
return
true
;
});
_cancelActivePointers
();
}
/// Complete the lifecycle for a route that has been popped off the navigator.
///
/// When the navigator pops a route, the navigator retains a reference to the
...
...
@@ -1178,10 +1225,15 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
void
_cancelActivePointers
()
{
// TODO(abarth): This mechanism is far from perfect. See https://github.com/flutter/flutter/issues/4770
final
RenderAbsorbPointer
absorber
=
_overlayKey
.
currentContext
?.
ancestorRenderObjectOfType
(
const
TypeMatcher
<
RenderAbsorbPointer
>());
setState
(()
{
absorber
?.
absorbing
=
true
;
});
if
(
SchedulerBinding
.
instance
.
schedulerPhase
==
SchedulerPhase
.
idle
)
{
// If we're between frames (SchedulerPhase.idle) then absorb any
// subsequent pointers from this frame. The absorbing flag will be
// reset in the next frame, see build().
final
RenderAbsorbPointer
absorber
=
_overlayKey
.
currentContext
?.
ancestorRenderObjectOfType
(
const
TypeMatcher
<
RenderAbsorbPointer
>());
setState
(()
{
absorber
?.
absorbing
=
true
;
});
}
for
(
int
pointer
in
_activePointers
.
toList
())
WidgetsBinding
.
instance
.
cancelPointer
(
pointer
);
}
...
...
packages/flutter/test/material/dropdown_test.dart
View file @
0cadbce4
...
...
@@ -3,6 +3,7 @@
// found in the LICENSE file.
import
'dart:math'
as
math
;
import
'dart:ui'
show
window
;
import
'package:flutter_test/flutter_test.dart'
;
import
'package:flutter/material.dart'
;
...
...
@@ -463,4 +464,16 @@ void main() {
expect
(
menuRect
.
bottomLeft
,
new
Offset
(
800.0
-
menuRect
.
width
,
600.0
));
expect
(
menuRect
.
bottomRight
,
const
Offset
(
800.0
,
600.0
));
});
testWidgets
(
'Dropdown menus are dismissed on screen orientation changes'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
buildFrame
());
await
tester
.
tap
(
find
.
byType
(
dropdownButtonType
));
await
tester
.
pumpAndSettle
();
expect
(
find
.
byType
(
ListView
),
findsOneWidget
);
window
.
onMetricsChanged
();
await
tester
.
pump
();
expect
(
find
.
byType
(
ListView
,
skipOffstage:
false
),
findsNothing
);
});
}
packages/flutter/test/widgets/navigator_test.dart
View file @
0cadbce4
...
...
@@ -8,7 +8,7 @@ import 'package:flutter/material.dart';
class
FirstWidget
extends
StatelessWidget
{
@override
Widget
build
(
BuildContext
context
)
{
return
new
GestureDetector
(
return
new
GestureDetector
(
onTap:
()
{
Navigator
.
pushNamed
(
context
,
'/second'
);
},
...
...
@@ -85,12 +85,12 @@ class OnTapPage extends StatelessWidget {
}
}
typedef
void
OnPushed
(
Route
<
dynamic
>
route
,
Route
<
dynamic
>
previousRoute
);
typedef
void
OnPopped
(
Route
<
dynamic
>
route
,
Route
<
dynamic
>
previousRoute
);
typedef
void
OnObservation
(
Route
<
dynamic
>
route
,
Route
<
dynamic
>
previousRoute
);
class
TestObserver
extends
NavigatorObserver
{
OnPushed
onPushed
;
OnPopped
onPopped
;
OnObservation
onPushed
;
OnObservation
onPopped
;
OnObservation
onRemoved
;
@override
void
didPush
(
Route
<
dynamic
>
route
,
Route
<
dynamic
>
previousRoute
)
{
...
...
@@ -105,6 +105,12 @@ class TestObserver extends NavigatorObserver {
onPopped
(
route
,
previousRoute
);
}
}
@override
void
didRemove
(
Route
<
dynamic
>
route
,
Route
<
dynamic
>
previousRoute
)
{
if
(
onRemoved
!=
null
)
onRemoved
(
route
,
previousRoute
);
}
}
void
main
(
)
{
...
...
@@ -445,4 +451,120 @@ void main() {
final
String
replaceNamedValue
=
await
value
;
// replaceNamed result was 'B'
expect
(
replaceNamedValue
,
'B'
);
});
testWidgets
(
'removeRoute'
,
(
WidgetTester
tester
)
async
{
final
Map
<
String
,
WidgetBuilder
>
pageBuilders
=
<
String
,
WidgetBuilder
>{
'/'
:
(
BuildContext
context
)
=>
new
OnTapPage
(
id:
'/'
,
onTap:
()
{
Navigator
.
pushNamed
(
context
,
'/A'
);
}),
'/A'
:
(
BuildContext
context
)
=>
new
OnTapPage
(
id:
'A'
,
onTap:
()
{
Navigator
.
pushNamed
(
context
,
'/B'
);
}),
'/B'
:
(
BuildContext
context
)
=>
const
OnTapPage
(
id:
'B'
),
};
final
Map
<
String
,
Route
<
String
>>
routes
=
<
String
,
Route
<
String
>>{};
Route
<
String
>
removedRoute
;
Route
<
String
>
previousRoute
;
final
TestObserver
observer
=
new
TestObserver
()
..
onRemoved
=
(
Route
<
dynamic
>
route
,
Route
<
dynamic
>
previous
)
{
removedRoute
=
route
;
previousRoute
=
previous
;
};
await
tester
.
pumpWidget
(
new
MaterialApp
(
navigatorObservers:
<
NavigatorObserver
>[
observer
],
onGenerateRoute:
(
RouteSettings
settings
)
{
routes
[
settings
.
name
]
=
new
PageRouteBuilder
<
String
>(
settings:
settings
,
pageBuilder:
(
BuildContext
context
,
Animation
<
double
>
_
,
Animation
<
double
>
__
)
{
return
pageBuilders
[
settings
.
name
](
context
);
},
);
return
routes
[
settings
.
name
];
}
));
expect
(
find
.
text
(
'/'
),
findsOneWidget
);
expect
(
find
.
text
(
'A'
),
findsNothing
);
expect
(
find
.
text
(
'B'
),
findsNothing
);
await
tester
.
tap
(
find
.
text
(
'/'
));
// pushNamed('/A'), stack becomes /, /A
await
tester
.
pumpAndSettle
();
expect
(
find
.
text
(
'/'
),
findsNothing
);
expect
(
find
.
text
(
'A'
),
findsOneWidget
);
expect
(
find
.
text
(
'B'
),
findsNothing
);
await
tester
.
tap
(
find
.
text
(
'A'
));
// pushNamed('/B'), stack becomes /, /A, /B
await
tester
.
pumpAndSettle
();
expect
(
find
.
text
(
'/'
),
findsNothing
);
expect
(
find
.
text
(
'A'
),
findsNothing
);
expect
(
find
.
text
(
'B'
),
findsOneWidget
);
// Verify that the navigator's stack is ordered as expected.
expect
(
routes
[
'/'
].
isActive
,
true
);
expect
(
routes
[
'/A'
].
isActive
,
true
);
expect
(
routes
[
'/B'
].
isActive
,
true
);
expect
(
routes
[
'/'
].
isFirst
,
true
);
expect
(
routes
[
'/B'
].
isCurrent
,
true
);
final
NavigatorState
navigator
=
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
));
navigator
.
removeRoute
(
routes
[
'/B'
]);
// stack becomes /, /A
await
tester
.
pump
();
expect
(
find
.
text
(
'/'
),
findsNothing
);
expect
(
find
.
text
(
'A'
),
findsOneWidget
);
expect
(
find
.
text
(
'B'
),
findsNothing
);
// Verify that the navigator's stack no longer includes /B
expect
(
routes
[
'/'
].
isActive
,
true
);
expect
(
routes
[
'/A'
].
isActive
,
true
);
expect
(
routes
[
'/B'
].
isActive
,
false
);
expect
(
routes
[
'/'
].
isFirst
,
true
);
expect
(
routes
[
'/A'
].
isCurrent
,
true
);
expect
(
removedRoute
,
routes
[
'/B'
]);
expect
(
previousRoute
,
routes
[
'/A'
]);
navigator
.
removeRoute
(
routes
[
'/A'
]);
// stack becomes just /
await
tester
.
pump
();
expect
(
find
.
text
(
'/'
),
findsOneWidget
);
expect
(
find
.
text
(
'A'
),
findsNothing
);
expect
(
find
.
text
(
'B'
),
findsNothing
);
// Verify that the navigator's stack no longer includes /A
expect
(
routes
[
'/'
].
isActive
,
true
);
expect
(
routes
[
'/A'
].
isActive
,
false
);
expect
(
routes
[
'/B'
].
isActive
,
false
);
expect
(
routes
[
'/'
].
isFirst
,
true
);
expect
(
routes
[
'/'
].
isCurrent
,
true
);
expect
(
removedRoute
,
routes
[
'/A'
]);
expect
(
previousRoute
,
routes
[
'/'
]);
});
testWidgets
(
'remove a route whose value is awaited'
,
(
WidgetTester
tester
)
async
{
Future
<
String
>
pageValue
;
final
Map
<
String
,
WidgetBuilder
>
pageBuilders
=
<
String
,
WidgetBuilder
>{
'/'
:
(
BuildContext
context
)
=>
new
OnTapPage
(
id:
'/'
,
onTap:
()
{
pageValue
=
Navigator
.
pushNamed
(
context
,
'/A'
);
}),
'/A'
:
(
BuildContext
context
)
=>
new
OnTapPage
(
id:
'A'
,
onTap:
()
{
Navigator
.
pop
(
context
,
'A'
);
}),
};
final
Map
<
String
,
Route
<
String
>>
routes
=
<
String
,
Route
<
String
>>{};
await
tester
.
pumpWidget
(
new
MaterialApp
(
onGenerateRoute:
(
RouteSettings
settings
)
{
routes
[
settings
.
name
]
=
new
PageRouteBuilder
<
String
>(
settings:
settings
,
pageBuilder:
(
BuildContext
context
,
Animation
<
double
>
_
,
Animation
<
double
>
__
)
{
return
pageBuilders
[
settings
.
name
](
context
);
},
);
return
routes
[
settings
.
name
];
}
));
await
tester
.
tap
(
find
.
text
(
'/'
));
// pushNamed('/A'), stack becomes /, /A
await
tester
.
pumpAndSettle
();
pageValue
.
then
((
String
value
)
{
assert
(
false
);
});
final
NavigatorState
navigator
=
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
));
navigator
.
removeRoute
(
routes
[
'/A'
]);
// stack becomes /, pageValue will not complete
});
}
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