diff --git a/packages/flutter/lib/src/widgets/ticker_provider.dart b/packages/flutter/lib/src/widgets/ticker_provider.dart index 2898320077..9f9b3cdd33 100644 --- a/packages/flutter/lib/src/widgets/ticker_provider.dart +++ b/packages/flutter/lib/src/widgets/ticker_provider.dart @@ -126,7 +126,7 @@ mixin SingleTickerProviderStateMixin on State imple ), ]); }()); - _ticker = Ticker(onTick, debugLabel: kDebugMode ? 'created by $this' : null); + _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 @@ -199,7 +199,7 @@ mixin TickerProviderStateMixin on State implements @override Ticker createTicker(TickerCallback onTick) { _tickers ??= <_WidgetTicker>{}; - final _WidgetTicker result = _WidgetTicker(onTick, this, debugLabel: 'created by $this'); + final _WidgetTicker result = _WidgetTicker(onTick, this, debugLabel: kDebugMode ? 'created by ${describeIdentity(this)}' : null); _tickers!.add(result); return result; } diff --git a/packages/flutter/test/widgets/ticker_provider_test.dart b/packages/flutter/test/widgets/ticker_provider_test.dart index bd1fda25b3..d6fbeceea7 100644 --- a/packages/flutter/test/widgets/ticker_provider_test.dart +++ b/packages/flutter/test/widgets/ticker_provider_test.dart @@ -117,7 +117,7 @@ void main() { 'the ticker will leak.\n' ); expect(error.diagnostics[3], isA>()); - expect(error.toStringDeep().split('\n').take(14).join('\n'), equalsIgnoringHashCodes( + expect(error.toStringDeep().split('\n').take(13).join('\n'), equalsIgnoringHashCodes( 'FlutterError\n' ' _SingleTickerTestState#00000(ticker active) was disposed with an\n' ' active Ticker.\n' @@ -129,8 +129,7 @@ void main() { ' calling dispose() on the AnimationController itself. Otherwise,\n' ' the ticker will leak.\n' ' The offending ticker was:\n' - ' Ticker(created by _SingleTickerTestState#00000(lifecycle state:\n' - ' created))\n' + ' Ticker(created by _SingleTickerTestState#00000)\n' ' The stack trace when the Ticker was actually created was:' )); key.currentState!.controller.stop(); @@ -158,7 +157,7 @@ void main() { 'the ticker will leak.\n' ); expect(error.diagnostics[3], isA>()); - expect(error.toStringDeep().split('\n').take(14).join('\n'), equalsIgnoringHashCodes( + expect(error.toStringDeep().split('\n').take(13).join('\n'), equalsIgnoringHashCodes( 'FlutterError\n' ' _SingleTickerTestState#00000(ticker active) was disposed with an\n' ' active Ticker.\n' @@ -170,15 +169,14 @@ void main() { ' calling dispose() on the AnimationController itself. Otherwise,\n' ' the ticker will leak.\n' ' The offending ticker was:\n' - ' Ticker(created by _SingleTickerTestState#00000(lifecycle state:\n' - ' created))\n' + ' Ticker(created by _SingleTickerTestState#00000)\n' ' The stack trace when the Ticker was actually created was:' )); key.currentState!.controller.stop(); } }); - testWidgets('ProviderStateMixin dispose while any ticker is active', (WidgetTester tester) async { + testWidgets('TickerProviderStateMixin dispose while any ticker is active', (WidgetTester tester) async { final GlobalKey<_MultipleTickerTestState> key = GlobalKey<_MultipleTickerTestState>(); final Widget widget = _MultipleTickerTest(key: key); await tester.pumpWidget(widget); @@ -199,7 +197,7 @@ void main() { 'the ticker will leak.\n' ); expect(error.diagnostics[3], isA>()); - expect(error.toStringDeep().split('\n').take(14).join('\n'), equalsIgnoringHashCodes( + expect(error.toStringDeep().split('\n').take(12).join('\n'), equalsIgnoringHashCodes( 'FlutterError\n' ' _MultipleTickerTestState#00000(tickers: tracking 2 tickers) was\n' ' disposed with an active Ticker.\n' @@ -211,14 +209,22 @@ void main() { ' calling dispose() on the AnimationController itself. Otherwise,\n' ' the ticker will leak.\n' ' The offending ticker was:\n' - ' _WidgetTicker(created by\n' - ' _MultipleTickerTestState#00000(lifecycle state: created,\n' - ' tickers: tracking 0 tickers))' + ' _WidgetTicker(created by _MultipleTickerTestState#00000)' )); key.currentState!.controllers.first.stop(); } }); }); + + testWidgets('SingleTickerProviderStateMixin does not call State.toString', (WidgetTester tester) async { + await tester.pumpWidget(const _SingleTickerTest()); + expect(tester.state<_SingleTickerTestState>(find.byType(_SingleTickerTest)).toStringCount, 0); + }); + + testWidgets('TickerProviderStateMixin does not call State.toString', (WidgetTester tester) async { + await tester.pumpWidget(const _MultipleTickerTest()); + expect(tester.state<_MultipleTickerTestState>(find.byType(_MultipleTickerTest)).toStringCount, 0); + }); } class BoringTickerTest extends StatefulWidget { @@ -255,6 +261,14 @@ class _SingleTickerTestState extends State<_SingleTickerTest> with SingleTickerP Widget build(BuildContext context) { return Container(); } + + int toStringCount = 0; + + @override + String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { + toStringCount += 1; + return super.toString(minLevel: minLevel); + } } class _MultipleTickerTest extends StatefulWidget { @@ -279,6 +293,14 @@ class _MultipleTickerTestState extends State<_MultipleTickerTest> with TickerPro Widget build(BuildContext context) { return Container(); } + + int toStringCount = 0; + + @override + String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { + toStringCount += 1; + return super.toString(minLevel: minLevel); + } } class _SingleTickerCreateMultipleTicker extends StatefulWidget {