-
Notifications
You must be signed in to change notification settings - Fork 29.8k
Description
Steps to reproduce
When a CupertinoSheet updates (didUpdateWidget) it tries to dispose and re-create the animation. Since its using a SingleTickerProvider it fails to recreate the controller. The dispose method of an animation controller does not reset the TickerProvider.
| _disposeCurve(); |
Unsure which behaviour would be intended.
I would intuitively expect that I can re-use a ticker provider if I dispose the ticker correctly. So I would even go as far as saying the sheet implementation is sound, the ticker provider createTicker method could use a check to see if the previously created ticker was disposed.
Expected results
The animation is disposed correctly and recreated.
Actual results
Error occurs.
Code sample
I frankensteined this sample code to verify disposing an animation controller does not reset the provider mixin.
Code sample
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Counter Application',
debugShowCheckedModeBanner: false,
theme: ThemeData(colorSchemeSeed: Colors.blue),
home: const CounterScreen(title: 'Counter Screen'),
);
}
}
class CounterScreen extends StatefulWidget {
final String title;
const CounterScreen({super.key, required this.title});
@override
State<CounterScreen> createState() => _CounterScreenState();
}
class _CounterScreenState extends State<CounterScreen> with SingleTickerProviderStateMixin {
int _counter = 0;
late AnimationController _animationController;
@override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 500),
);
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
/// Disposes the current [AnimationController] and creates a new one,
/// vsynced to this [_CounterScreenState].
void _disposeAndRecreateAnimationController() {
_animationController.dispose(); // Dispose the existing controller
_animationController = AnimationController(
vsync: this, // Re-initialize with vsync to this
duration: const Duration(milliseconds: 500),
);
setState(() {});
}
@override
Widget build(BuildContext context) {
// A simple scale animation for the icon to demonstrate the controller is working
final Animation<double> scaleAnimation = Tween<double>(begin: 1.0, end: 1.2).animate(
CurvedAnimation(
parent: _animationController,
curve: Curves.easeOut,
),
);
return Scaffold(
appBar: AppBar(title: Text(widget.title)),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('You have pushed the button this many times:'),
Text(
'$_counter',
style: Theme.of(context).textTheme.headlineMedium,
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: _disposeAndRecreateAnimationController,
child: const Text('Reset Animation Controller'),
),
const SizedBox(height: 20),
// Display an icon that animates when _incrementCounter or _disposeAndRecreateAnimationController is called
AnimatedBuilder(
animation: _animationController,
builder: (context, child) {
return Transform.scale(
scale: scaleAnimation.value,
child: const Icon(Icons.fingerprint, size: 50, color: Colors.blue),
);
},
),
],
),
),
);
}
}Screenshots or Video
Screenshots / Video demonstration
[Upload media here]
Logs
Logs
════════ Exception caught by widgets library ═══════════════════════════════════
The following assertion was thrown building LayoutBuilder:
_CupertinoSheetTransitionState is a SingleTickerProviderStateMixin but multiple tickers were created.
A SingleTickerProviderStateMixin can only be used as a TickerProvider once.
If a State is used for multiple AnimationController objects, or if it is passed to other objects and those objects might use it more than one time in total, then instead of mixing in a SingleTickerProviderStateMixin, use a regular TickerProviderStateMixin.
The relevant error-causing widget was:
LayoutBuilder LayoutBuilder:file:///Users/.../lib/src/modal/modal.dart:312:12
When the exception was thrown, this was the stack:
#0 SingleTickerProviderStateMixin.createTicker.<anonymous closure> (package:flutter/src/widgets/ticker_provider.dart:201:7)
ticker_provider.dart:201
#1 SingleTickerProviderStateMixin.createTicker (package:flutter/src/widgets/ticker_provider.dart:214:6)
ticker_provider.dart:214
#2 new AnimationController (package:flutter/src/animation/animation_controller.dart:257:21)
animation_controller.dart:257
#3 _CupertinoSheetTransitionState._setupAnimation (package:flutter/src/cupertino/sheet.dart:417:30)
sheet.dart:417
#4 _CupertinoSheetTransitionState.didUpdateWidget (package:flutter/src/cupertino/sheet.dart:396:7)
Flutter Doctor output
Doctor output
[✓] Flutter (Channel stable, 3.38.1, on macOS 26.2 25C5037j darwin-arm64, locale en-US) [503ms]
• Flutter version 3.38.1 on channel stable at /Users/.../flutter
• Upstream repository https://github.com/flutter/flutter.git
• Framework revision b45fa18946 (3 weeks ago), 2025-11-12 22:09:06 -0600
• Engine revision b5990e5ccc
• Dart version 3.10.0
• DevTools version 2.51.1
• Feature flags: enable-web, enable-linux-desktop, enable-macos-desktop, enable-windows-desktop, enable-android, enable-ios, cli-animations, enable-native-assets,
omit-legacy-version-file, enable-lldb-debugging
[!] Android toolchain - develop for Android devices (Android SDK version 35.0.0) [1,840ms]
• Android SDK at /Users/.../Library/Android/Sdk
• Emulator version 36.1.9.0 (build_id 13823996) (CL:N/A)
• Platform android-36, build-tools 35.0.0
• ANDROID_SDK_ROOT = /Users/.../Library/Android/Sdk
• Java binary at: /opt/homebrew/opt/openjdk@17/bin/java
This JDK is specified in your Flutter configuration.
To change the current JDK, run: `flutter config --jdk-dir="path/to/jdk"`.
• Java version OpenJDK Runtime Environment Homebrew (build 17.0.15+0)
! Some Android licenses not accepted. To resolve this, run: flutter doctor --android-licenses
[!] Xcode - develop for iOS and macOS (Xcode 26.0) [1,258ms]
• Xcode at /Applications/Xcode-26.0.0.app/Contents/Developer
• Build 17A324
! CocoaPods 1.15.2 out of date (1.16.2 is recommended).
CocoaPods is a package manager for iOS or macOS platform code.
Without CocoaPods, plugins will not work on iOS or macOS.
For more info, see https://flutter.dev/to/platform-plugins
To update CocoaPods, see https://guides.cocoapods.org/using/getting-started.html#updating-cocoapods
[✓] Chrome - develop for the web [6ms]
• Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome