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
fe8e882a
Unverified
Commit
fe8e882a
authored
Nov 09, 2021
by
Michael Goderbauer
Committed by
GitHub
Nov 09, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Do not rebuild when TickerMode changes (#93166)
parent
f129fb0a
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
333 additions
and
56 deletions
+333
-56
editable_text.dart
packages/flutter/lib/src/widgets/editable_text.dart
+29
-25
ticker_provider.dart
packages/flutter/lib/src/widgets/ticker_provider.dart
+139
-25
ticker_mode_test.dart
packages/flutter/test/widgets/ticker_mode_test.dart
+165
-6
No files found.
packages/flutter/lib/src/widgets/editable_text.dart
View file @
fe8e882a
...
...
@@ -1524,10 +1524,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
ScrollController
?
_internalScrollController
;
ScrollController
get
_scrollController
=>
widget
.
scrollController
??
(
_internalScrollController
??=
ScrollController
());
late
final
AnimationController
_cursorBlinkOpacityController
=
AnimationController
(
vsync:
this
,
duration:
_fadeDuration
,
)..
addListener
(
_onCursorColorTick
);
AnimationController
?
_cursorBlinkOpacityController
;
final
LayerLink
_toolbarLayerLink
=
LayerLink
();
final
LayerLink
_startHandleLayerLink
=
LayerLink
();
...
...
@@ -1564,14 +1561,12 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
// cursor position after the user has finished placing it.
static
const
Duration
_floatingCursorResetTime
=
Duration
(
milliseconds:
125
);
late
final
AnimationController
_floatingCursorResetController
=
AnimationController
(
vsync:
this
,
)..
addListener
(
_onFloatingCursorResetTick
);
AnimationController
?
_floatingCursorResetController
;
@override
bool
get
wantKeepAlive
=>
widget
.
focusNode
.
hasFocus
;
Color
get
_cursorColor
=>
widget
.
cursorColor
.
withOpacity
(
_cursorBlinkOpacityController
.
value
);
Color
get
_cursorColor
=>
widget
.
cursorColor
.
withOpacity
(
_cursorBlinkOpacityController
!
.
value
);
@override
bool
get
cutEnabled
=>
widget
.
toolbarOptions
.
cut
&&
!
widget
.
readOnly
;
...
...
@@ -1698,6 +1693,10 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
@override
void
initState
()
{
super
.
initState
();
_cursorBlinkOpacityController
=
AnimationController
(
vsync:
this
,
duration:
_fadeDuration
,
)..
addListener
(
_onCursorColorTick
);
_clipboardStatus
?.
addListener
(
_onChangedClipboardStatus
);
widget
.
controller
.
addListener
(
_didChangeTextEditingValue
);
widget
.
focusNode
.
addListener
(
_handleFocusChanged
);
...
...
@@ -1808,12 +1807,14 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
_internalScrollController
?.
dispose
();
_currentAutofillScope
?.
unregister
(
autofillId
);
widget
.
controller
.
removeListener
(
_didChangeTextEditingValue
);
_floatingCursorResetController
.
dispose
();
_floatingCursorResetController
?.
dispose
();
_floatingCursorResetController
=
null
;
_closeInputConnectionIfNeeded
();
assert
(!
_hasInputConnection
);
_cursorTimer
?.
cancel
();
_cursorTimer
=
null
;
_cursorBlinkOpacityController
.
dispose
();
_cursorBlinkOpacityController
?.
dispose
();
_cursorBlinkOpacityController
=
null
;
_selectionOverlay
?.
dispose
();
_selectionOverlay
=
null
;
widget
.
focusNode
.
removeListener
(
_handleFocusChanged
);
...
...
@@ -1952,10 +1953,13 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
@override
void
updateFloatingCursor
(
RawFloatingCursorPoint
point
)
{
_floatingCursorResetController
??=
AnimationController
(
vsync:
this
,
)..
addListener
(
_onFloatingCursorResetTick
);
switch
(
point
.
state
)
{
case
FloatingCursorDragState
.
Start
:
if
(
_floatingCursorResetController
.
isAnimating
)
{
_floatingCursorResetController
.
stop
();
if
(
_floatingCursorResetController
!
.
isAnimating
)
{
_floatingCursorResetController
!
.
stop
();
_onFloatingCursorResetTick
();
}
// We want to send in points that are centered around a (0,0) origin, so
...
...
@@ -1980,8 +1984,8 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
case
FloatingCursorDragState
.
End
:
// We skip animation if no update has happened.
if
(
_lastTextPosition
!=
null
&&
_lastBoundedOffset
!=
null
)
{
_floatingCursorResetController
.
value
=
0.0
;
_floatingCursorResetController
.
animateTo
(
1.0
,
duration:
_floatingCursorResetTime
,
curve:
Curves
.
decelerate
);
_floatingCursorResetController
!
.
value
=
0.0
;
_floatingCursorResetController
!
.
animateTo
(
1.0
,
duration:
_floatingCursorResetTime
,
curve:
Curves
.
decelerate
);
}
break
;
}
...
...
@@ -1989,7 +1993,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
void
_onFloatingCursorResetTick
()
{
final
Offset
finalPosition
=
renderEditable
.
getLocalRectForCaret
(
_lastTextPosition
!).
centerLeft
-
_floatingCursorOffset
;
if
(
_floatingCursorResetController
.
isCompleted
)
{
if
(
_floatingCursorResetController
!
.
isCompleted
)
{
renderEditable
.
setFloatingCursor
(
FloatingCursorDragState
.
End
,
finalPosition
,
_lastTextPosition
!);
if
(
_lastTextPosition
!.
offset
!=
renderEditable
.
selection
!.
baseOffset
)
// The cause is technically the force cursor, but the cause is listed as tap as the desired functionality is the same.
...
...
@@ -1999,7 +2003,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
_pointOffsetOrigin
=
null
;
_lastBoundedOffset
=
null
;
}
else
{
final
double
lerpValue
=
_floatingCursorResetController
.
value
;
final
double
lerpValue
=
_floatingCursorResetController
!
.
value
;
final
double
lerpX
=
ui
.
lerpDouble
(
_lastBoundedOffset
!.
dx
,
finalPosition
.
dx
,
lerpValue
)!;
final
double
lerpY
=
ui
.
lerpDouble
(
_lastBoundedOffset
!.
dy
,
finalPosition
.
dy
,
lerpValue
)!;
...
...
@@ -2528,14 +2532,14 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
}
void
_onCursorColorTick
()
{
renderEditable
.
cursorColor
=
widget
.
cursorColor
.
withOpacity
(
_cursorBlinkOpacityController
.
value
);
_cursorVisibilityNotifier
.
value
=
widget
.
showCursor
&&
_cursorBlinkOpacityController
.
value
>
0
;
renderEditable
.
cursorColor
=
widget
.
cursorColor
.
withOpacity
(
_cursorBlinkOpacityController
!
.
value
);
_cursorVisibilityNotifier
.
value
=
widget
.
showCursor
&&
_cursorBlinkOpacityController
!
.
value
>
0
;
}
/// Whether the blinking cursor is actually visible at this precise moment
/// (it's hidden half the time, since it blinks).
@visibleForTesting
bool
get
cursorCurrentlyVisible
=>
_cursorBlinkOpacityController
.
value
>
0
;
bool
get
cursorCurrentlyVisible
=>
_cursorBlinkOpacityController
!
.
value
>
0
;
/// The cursor blink interval (the amount of time the cursor is in the "on"
/// state or the "off" state). A complete cursor blink period is twice this
...
...
@@ -2561,9 +2565,9 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
//
// These values and curves have been obtained through eyeballing, so are
// likely not exactly the same as the values for native iOS.
_cursorBlinkOpacityController
.
animateTo
(
targetOpacity
,
curve:
Curves
.
easeOut
);
_cursorBlinkOpacityController
!
.
animateTo
(
targetOpacity
,
curve:
Curves
.
easeOut
);
}
else
{
_cursorBlinkOpacityController
.
value
=
targetOpacity
;
_cursorBlinkOpacityController
!
.
value
=
targetOpacity
;
}
if
(
_obscureShowCharTicksPending
>
0
)
{
...
...
@@ -2591,7 +2595,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
return
;
}
_targetCursorVisibility
=
true
;
_cursorBlinkOpacityController
.
value
=
1.0
;
_cursorBlinkOpacityController
!
.
value
=
1.0
;
if
(
EditableText
.
debugDeterministicCursor
)
return
;
if
(
widget
.
cursorOpacityAnimates
)
{
...
...
@@ -2606,14 +2610,14 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
_cursorTimer
?.
cancel
();
_cursorTimer
=
null
;
_targetCursorVisibility
=
false
;
_cursorBlinkOpacityController
.
value
=
0.0
;
_cursorBlinkOpacityController
!
.
value
=
0.0
;
if
(
EditableText
.
debugDeterministicCursor
)
return
;
if
(
resetCharTicks
)
_obscureShowCharTicksPending
=
0
;
if
(
widget
.
cursorOpacityAnimates
)
{
_cursorBlinkOpacityController
.
stop
();
_cursorBlinkOpacityController
.
value
=
0.0
;
_cursorBlinkOpacityController
!
.
stop
();
_cursorBlinkOpacityController
!
.
value
=
0.0
;
}
}
...
...
packages/flutter/lib/src/widgets/ticker_provider.dart
View file @
fe8e882a
...
...
@@ -15,7 +15,7 @@ export 'package:flutter/scheduler.dart' show TickerProvider;
/// This only works if [AnimationController] objects are created using
/// widget-aware ticker providers. For example, using a
/// [TickerProviderStateMixin] or a [SingleTickerProviderStateMixin].
class
TickerMode
extends
State
less
Widget
{
class
TickerMode
extends
State
ful
Widget
{
/// Creates a widget that enables or disables tickers.
///
/// The [enabled] argument must not be null.
...
...
@@ -62,18 +62,85 @@ class TickerMode extends StatelessWidget {
return
widget
?.
enabled
??
true
;
}
/// Obtains a [ValueNotifier] from the [TickerMode] surrounding the `context`,
/// which indicates whether tickers are enabled in the given subtree.
///
/// When that [TickerMode] enabled or disabled tickers, the notifier notifies
/// its listeners.
///
/// While the [ValueNotifier] is stable for the lifetime of the surrounding
/// [TickerMode], calling this method does not establish a dependency between
/// the `context` and the [TickerMode] and the widget owning the `context`
/// does not rebuild when the ticker mode changes from true to false or vice
/// versa. This is preferable when the ticker mode does not impact what is
/// currently rendered on screen, e.g. because it is ony used to mute/unmute a
/// [Ticker]. Since no dependency is established, the widget owning the
/// `context` is also not informed when it is moved to a new location in the
/// tree where it may have a different [TickerMode] ancestor. When this
/// happens, the widget must manually unsubscribe from the old notifier,
/// obtain a new one from the new ancestor [TickerMode] by calling this method
/// again, and re-subscribe to it. [StatefulWidget]s can, for example, do this
/// in [State.activate], which is called after the widget has been moved to
/// a new location.
///
/// Alternatively, [of] can be used instead of this method to create a
/// dependency between the provided `context` and the ancestor [TickerMode].
/// In this case, the widget automatically rebuilds when the ticker mode
/// changes or when it is moved to a new [TickerMode] ancestor, which
/// simplifies the management cost in the widget at the expensive of some
/// potential unnecessary rebuilds.
///
/// In the absence of a [TickerMode] widget, this function returns a
/// [ValueNotifier], whose [ValueNotifier.value] is always true.
static
ValueNotifier
<
bool
>
getNotifier
(
BuildContext
context
)
{
final
_EffectiveTickerMode
?
widget
=
context
.
getElementForInheritedWidgetOfExactType
<
_EffectiveTickerMode
>()?.
widget
as
_EffectiveTickerMode
?;
return
widget
?.
notifier
??
ValueNotifier
<
bool
>(
true
);
}
@override
State
<
TickerMode
>
createState
()
=>
_TickerModeState
();
}
class
_TickerModeState
extends
State
<
TickerMode
>
{
bool
_ancestorTicketMode
=
true
;
final
ValueNotifier
<
bool
>
_effectiveMode
=
ValueNotifier
<
bool
>(
true
);
@override
void
didChangeDependencies
()
{
super
.
didChangeDependencies
();
_ancestorTicketMode
=
TickerMode
.
of
(
context
);
_updateEffectiveMode
();
}
@override
void
didUpdateWidget
(
TickerMode
oldWidget
)
{
super
.
didUpdateWidget
(
oldWidget
);
_updateEffectiveMode
();
}
@override
void
dispose
()
{
_effectiveMode
.
dispose
();
super
.
dispose
();
}
void
_updateEffectiveMode
()
{
_effectiveMode
.
value
=
_ancestorTicketMode
&&
widget
.
enabled
;
}
@override
Widget
build
(
BuildContext
context
)
{
return
_EffectiveTickerMode
(
enabled:
enabled
&&
TickerMode
.
of
(
context
),
child:
child
,
enabled:
_effectiveMode
.
value
,
notifier:
_effectiveMode
,
child:
widget
.
child
,
);
}
@override
void
debugFillProperties
(
DiagnosticPropertiesBuilder
properties
)
{
super
.
debugFillProperties
(
properties
);
properties
.
add
(
FlagProperty
(
'requested mode'
,
value:
enabled
,
ifTrue:
'enabled'
,
ifFalse:
'disabled'
,
showName:
true
));
properties
.
add
(
FlagProperty
(
'requested mode'
,
value:
widget
.
enabled
,
ifTrue:
'enabled'
,
ifFalse:
'disabled'
,
showName:
true
));
}
}
...
...
@@ -81,11 +148,13 @@ class _EffectiveTickerMode extends InheritedWidget {
const
_EffectiveTickerMode
({
Key
?
key
,
required
this
.
enabled
,
required
this
.
notifier
,
required
Widget
child
,
})
:
assert
(
enabled
!=
null
),
super
(
key:
key
,
child:
child
);
super
(
key:
key
,
child:
child
);
final
bool
enabled
;
final
ValueNotifier
<
bool
>
notifier
;
@override
bool
updateShouldNotify
(
_EffectiveTickerMode
oldWidget
)
=>
enabled
!=
oldWidget
.
enabled
;
...
...
@@ -127,10 +196,8 @@ mixin SingleTickerProviderStateMixin<T extends StatefulWidget> on State<T> imple
]);
}());
_ticker
=
Ticker
(
onTick
,
debugLabel:
kDebugMode
?
'created by
${describeIdentity(this)}
'
:
null
);
// We assume that this is called from initState, build, or some sort of
// event handler, and that thus TickerMode.of(context) would return true. We
// can't actually check that here because if we're in initState then we're
// not allowed to do inheritance checks yet.
_updateTickerModeNotifier
();
_updateTicker
();
// Sets _ticker.mute correctly.
return
_ticker
!;
}
...
...
@@ -154,14 +221,35 @@ mixin SingleTickerProviderStateMixin<T extends StatefulWidget> on State<T> imple
_ticker
!.
describeForError
(
'The offending ticker was'
),
]);
}());
_tickerModeNotifier
?.
removeListener
(
_updateTicker
);
_tickerModeNotifier
=
null
;
super
.
dispose
();
}
ValueNotifier
<
bool
>?
_tickerModeNotifier
;
@override
void
didChangeDependencies
()
{
if
(
_ticker
!=
null
)
_ticker
!.
muted
=
!
TickerMode
.
of
(
context
);
super
.
didChangeDependencies
();
void
activate
()
{
super
.
activate
();
// We may have a new TickerMode ancestor.
_updateTickerModeNotifier
();
_updateTicker
();
}
void
_updateTicker
()
{
if
(
_ticker
!=
null
)
{
_ticker
!.
muted
=
!
_tickerModeNotifier
!.
value
;
}
}
void
_updateTickerModeNotifier
()
{
final
ValueNotifier
<
bool
>
newNotifier
=
TickerMode
.
getNotifier
(
context
);
if
(
newNotifier
==
_tickerModeNotifier
)
{
return
;
}
_tickerModeNotifier
?.
removeListener
(
_updateTicker
);
newNotifier
.
addListener
(
_updateTicker
);
_tickerModeNotifier
=
newNotifier
;
}
@override
...
...
@@ -198,8 +286,14 @@ mixin TickerProviderStateMixin<T extends StatefulWidget> on State<T> implements
@override
Ticker
createTicker
(
TickerCallback
onTick
)
{
if
(
_tickerModeNotifier
==
null
)
{
// Setup TickerMode notifier before we vend the first ticker.
_updateTickerModeNotifier
();
}
assert
(
_tickerModeNotifier
!=
null
);
_tickers
??=
<
_WidgetTicker
>{};
final
_WidgetTicker
result
=
_WidgetTicker
(
onTick
,
this
,
debugLabel:
kDebugMode
?
'created by
${describeIdentity(this)}
'
:
null
);
final
_WidgetTicker
result
=
_WidgetTicker
(
onTick
,
this
,
debugLabel:
kDebugMode
?
'created by
${describeIdentity(this)}
'
:
null
)
..
muted
=
!
_tickerModeNotifier
!.
value
;
_tickers
!.
add
(
result
);
return
result
;
}
...
...
@@ -210,6 +304,35 @@ mixin TickerProviderStateMixin<T extends StatefulWidget> on State<T> implements
_tickers
!.
remove
(
ticker
);
}
ValueNotifier
<
bool
>?
_tickerModeNotifier
;
@override
void
activate
()
{
super
.
activate
();
// We may have a new TickerMode ancestor, get its Notifier.
_updateTickerModeNotifier
();
_updateTickers
();
}
void
_updateTickers
()
{
if
(
_tickers
!=
null
)
{
final
bool
muted
=
!
_tickerModeNotifier
!.
value
;
for
(
final
Ticker
ticker
in
_tickers
!)
{
ticker
.
muted
=
muted
;
}
}
}
void
_updateTickerModeNotifier
()
{
final
ValueNotifier
<
bool
>
newNotifier
=
TickerMode
.
getNotifier
(
context
);
if
(
newNotifier
==
_tickerModeNotifier
)
{
return
;
}
_tickerModeNotifier
?.
removeListener
(
_updateTickers
);
newNotifier
.
addListener
(
_updateTickers
);
_tickerModeNotifier
=
newNotifier
;
}
@override
void
dispose
()
{
assert
(()
{
...
...
@@ -235,20 +358,11 @@ mixin TickerProviderStateMixin<T extends StatefulWidget> on State<T> implements
}
return
true
;
}());
_tickerModeNotifier
?.
removeListener
(
_updateTickers
);
_tickerModeNotifier
=
null
;
super
.
dispose
();
}
@override
void
didChangeDependencies
()
{
final
bool
muted
=
!
TickerMode
.
of
(
context
);
if
(
_tickers
!=
null
)
{
for
(
final
Ticker
ticker
in
_tickers
!)
{
ticker
.
muted
=
muted
;
}
}
super
.
didChangeDependencies
();
}
@override
void
debugFillProperties
(
DiagnosticPropertiesBuilder
properties
)
{
super
.
debugFillProperties
(
properties
);
...
...
packages/flutter/test/widgets/ticker_mode_test.dart
View file @
fe8e882a
...
...
@@ -2,6 +2,7 @@
// 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/scheduler.dart'
;
import
'package:flutter/widgets.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
...
...
@@ -98,36 +99,194 @@ void main() {
expect
(
outerTickCount
,
0
);
expect
(
innerTickCount
,
0
);
});
testWidgets
(
'Changing TickerMode does not rebuild widgets with SingleTickerProviderStateMixin'
,
(
WidgetTester
tester
)
async
{
Widget
widgetUnderTest
({
required
bool
tickerEnabled
})
{
return
TickerMode
(
enabled:
tickerEnabled
,
child:
const
_TickingWidget
(),
);
}
_TickingWidgetState
state
()
=>
tester
.
state
<
_TickingWidgetState
>(
find
.
byType
(
_TickingWidget
));
await
tester
.
pumpWidget
(
widgetUnderTest
(
tickerEnabled:
true
));
expect
(
state
().
ticker
.
isTicking
,
isTrue
);
expect
(
state
().
buildCount
,
1
);
await
tester
.
pumpWidget
(
widgetUnderTest
(
tickerEnabled:
false
));
expect
(
state
().
ticker
.
isTicking
,
isFalse
);
expect
(
state
().
buildCount
,
1
);
await
tester
.
pumpWidget
(
widgetUnderTest
(
tickerEnabled:
true
));
expect
(
state
().
ticker
.
isTicking
,
isTrue
);
expect
(
state
().
buildCount
,
1
);
});
testWidgets
(
'Changing TickerMode does not rebuild widgets with TickerProviderStateMixin'
,
(
WidgetTester
tester
)
async
{
Widget
widgetUnderTest
({
required
bool
tickerEnabled
})
{
return
TickerMode
(
enabled:
tickerEnabled
,
child:
const
_MultiTickingWidget
(),
);
}
_MultiTickingWidgetState
state
()
=>
tester
.
state
<
_MultiTickingWidgetState
>(
find
.
byType
(
_MultiTickingWidget
));
await
tester
.
pumpWidget
(
widgetUnderTest
(
tickerEnabled:
true
));
expect
(
state
().
ticker
.
isTicking
,
isTrue
);
expect
(
state
().
buildCount
,
1
);
await
tester
.
pumpWidget
(
widgetUnderTest
(
tickerEnabled:
false
));
expect
(
state
().
ticker
.
isTicking
,
isFalse
);
expect
(
state
().
buildCount
,
1
);
await
tester
.
pumpWidget
(
widgetUnderTest
(
tickerEnabled:
true
));
expect
(
state
().
ticker
.
isTicking
,
isTrue
);
expect
(
state
().
buildCount
,
1
);
});
testWidgets
(
'Moving widgets with SingleTickerProviderStateMixin to a new TickerMode ancestor works'
,
(
WidgetTester
tester
)
async
{
final
GlobalKey
tickingWidgetKey
=
GlobalKey
();
Widget
widgetUnderTest
({
required
LocalKey
tickerModeKey
,
required
bool
tickerEnabled
})
{
return
TickerMode
(
key:
tickerModeKey
,
enabled:
tickerEnabled
,
child:
_TickingWidget
(
key:
tickingWidgetKey
),
);
}
// Using different local keys to simulate changing TickerMode ancestors.
await
tester
.
pumpWidget
(
widgetUnderTest
(
tickerEnabled:
true
,
tickerModeKey:
UniqueKey
()));
final
State
tickerModeState
=
tester
.
state
(
find
.
byType
(
TickerMode
));
final
_TickingWidgetState
tickingState
=
tester
.
state
<
_TickingWidgetState
>(
find
.
byType
(
_TickingWidget
));
expect
(
tickingState
.
ticker
.
isTicking
,
isTrue
);
await
tester
.
pumpWidget
(
widgetUnderTest
(
tickerEnabled:
false
,
tickerModeKey:
UniqueKey
()));
expect
(
tester
.
state
(
find
.
byType
(
TickerMode
)),
isNot
(
same
(
tickerModeState
)));
expect
(
tickingState
,
same
(
tester
.
state
<
_TickingWidgetState
>(
find
.
byType
(
_TickingWidget
))));
expect
(
tickingState
.
ticker
.
isTicking
,
isFalse
);
});
testWidgets
(
'Moving widgets with TickerProviderStateMixin to a new TickerMode ancestor works'
,
(
WidgetTester
tester
)
async
{
final
GlobalKey
tickingWidgetKey
=
GlobalKey
();
Widget
widgetUnderTest
({
required
LocalKey
tickerModeKey
,
required
bool
tickerEnabled
})
{
return
TickerMode
(
key:
tickerModeKey
,
enabled:
tickerEnabled
,
child:
_MultiTickingWidget
(
key:
tickingWidgetKey
),
);
}
// Using different local keys to simulate changing TickerMode ancestors.
await
tester
.
pumpWidget
(
widgetUnderTest
(
tickerEnabled:
true
,
tickerModeKey:
UniqueKey
()));
final
State
tickerModeState
=
tester
.
state
(
find
.
byType
(
TickerMode
));
final
_MultiTickingWidgetState
tickingState
=
tester
.
state
<
_MultiTickingWidgetState
>(
find
.
byType
(
_MultiTickingWidget
));
expect
(
tickingState
.
ticker
.
isTicking
,
isTrue
);
await
tester
.
pumpWidget
(
widgetUnderTest
(
tickerEnabled:
false
,
tickerModeKey:
UniqueKey
()));
expect
(
tester
.
state
(
find
.
byType
(
TickerMode
)),
isNot
(
same
(
tickerModeState
)));
expect
(
tickingState
,
same
(
tester
.
state
<
_MultiTickingWidgetState
>(
find
.
byType
(
_MultiTickingWidget
))));
expect
(
tickingState
.
ticker
.
isTicking
,
isFalse
);
});
testWidgets
(
'Ticking widgets in old route do not rebuild when new route is pushed'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
MaterialApp
(
routes:
<
String
,
WidgetBuilder
>{
'/foo'
:
(
BuildContext
context
)
=>
const
Text
(
'New route'
),
},
home:
Row
(
children:
const
<
Widget
>[
_TickingWidget
(),
_MultiTickingWidget
(),
Text
(
'Old route'
),
],
),
));
_MultiTickingWidgetState
multiTickingState
()
=>
tester
.
state
<
_MultiTickingWidgetState
>(
find
.
byType
(
_MultiTickingWidget
,
skipOffstage:
false
));
_TickingWidgetState
tickingState
()
=>
tester
.
state
<
_TickingWidgetState
>(
find
.
byType
(
_TickingWidget
,
skipOffstage:
false
));
expect
(
find
.
text
(
'Old route'
),
findsOneWidget
);
expect
(
find
.
text
(
'New route'
),
findsNothing
);
expect
(
multiTickingState
().
ticker
.
isTicking
,
isTrue
);
expect
(
multiTickingState
().
buildCount
,
1
);
expect
(
tickingState
().
ticker
.
isTicking
,
isTrue
);
expect
(
tickingState
().
buildCount
,
1
);
tester
.
state
<
NavigatorState
>(
find
.
byType
(
Navigator
)).
pushNamed
(
'/foo'
);
await
tester
.
pumpAndSettle
();
expect
(
find
.
text
(
'Old route'
),
findsNothing
);
expect
(
find
.
text
(
'New route'
),
findsOneWidget
);
expect
(
multiTickingState
().
ticker
.
isTicking
,
isFalse
);
expect
(
multiTickingState
().
buildCount
,
1
);
expect
(
tickingState
().
ticker
.
isTicking
,
isFalse
);
expect
(
tickingState
().
buildCount
,
1
);
});
}
class
_TickingWidget
extends
StatefulWidget
{
const
_TickingWidget
({
required
this
.
onTick
}
);
const
_TickingWidget
({
Key
?
key
,
this
.
onTick
})
:
super
(
key:
key
);
final
VoidCallback
onTick
;
final
VoidCallback
?
onTick
;
@override
State
<
_TickingWidget
>
createState
()
=>
_TickingWidgetState
();
}
class
_TickingWidgetState
extends
State
<
_TickingWidget
>
with
SingleTickerProviderStateMixin
{
late
Ticker
_ticker
;
late
Ticker
ticker
;
int
buildCount
=
0
;
@override
void
initState
()
{
super
.
initState
();
ticker
=
createTicker
((
Duration
_
)
{
widget
.
onTick
?.
call
();
})..
start
();
}
@override
Widget
build
(
BuildContext
context
)
{
buildCount
+=
1
;
return
Container
();
}
@override
void
dispose
()
{
ticker
.
dispose
();
super
.
dispose
();
}
}
class
_MultiTickingWidget
extends
StatefulWidget
{
const
_MultiTickingWidget
({
Key
?
key
,
this
.
onTick
})
:
super
(
key:
key
);
final
VoidCallback
?
onTick
;
@override
State
<
_MultiTickingWidget
>
createState
()
=>
_MultiTickingWidgetState
();
}
class
_MultiTickingWidgetState
extends
State
<
_MultiTickingWidget
>
with
TickerProviderStateMixin
{
late
Ticker
ticker
;
int
buildCount
=
0
;
@override
void
initState
()
{
super
.
initState
();
_
ticker
=
createTicker
((
Duration
_
)
{
widget
.
onTick
();
ticker
=
createTicker
((
Duration
_
)
{
widget
.
onTick
?.
call
();
})..
start
();
}
@override
Widget
build
(
BuildContext
context
)
{
buildCount
+=
1
;
return
Container
();
}
@override
void
dispose
()
{
_
ticker
.
dispose
();
ticker
.
dispose
();
super
.
dispose
();
}
}
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