Flutter: Ticker must be disposed before calling super.dispose() - flutter

I don't know why this error is appearing in the console box
Console message:
SplashScreenState created a Ticker via its SingleTickerProviderStateMixin, but at the time dispose() was called on the mixin, that Ticker was still active. The Ticker must be disposed before calling super.dispose().
Tickers used by AnimationControllers should
be disposed by calling dispose() on
the AnimationController itself. Otherwise,
the ticker will leak.
The offending ticker was:
Ticker(created by SplashScreenState#dae31(lifecycle state: created))
HERE IS MY FULL CODE FOR SPLASHSCREEN:
import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class SplashScreen extends StatefulWidget {
#override
SplashScreenState createState() => new SplashScreenState();
}
class SplashScreenState extends State<SplashScreen>
with SingleTickerProviderStateMixin {
var _visible = true;
AnimationController animationController;
Animation<double> animation;
startTime() async {
var _duration = new Duration(seconds: 3);
return new Timer(_duration, navigationPage);
}
void navigationPage() {
Navigator.of(context).pushReplacementNamed(HOME_SCREEN);
}
#override
void initState() {
super.initState();
animationController = new AnimationController(
vsync: this,
duration: new Duration(seconds: 2),
);
animation =
new CurvedAnimation(parent: animationController, curve: Curves.easeOut);
animation.addListener(() => this.setState(() {}));
animationController.forward();
setState(() {
_visible = !_visible;
});
startTime();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
fit: StackFit.expand,
children: <Widget>[
new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Image.asset(
'assets/vegan1.png',
width: animation.value * 280,
height: animation.value * 280,
),
],
),
],
),
);
}
}
How can I solve this error. Please answer if you have any solution or idea to solve this.only added important points are added reduce the size of code. If you need more console code then please comment.

Override dispose method and dispose the AnimationController instance.
#override
dispose() {
animationController.dispose(); // you need this
super.dispose();
}

Related

Flutter GetX: Animation with GetxController

I'm trying GetX for my new project and tried to use AnimationController which is inspired this comment. Everything works fine with default value for Tween -> blur & spread & AnimationController -> duration.
What I'm doing:
1: Created an widget with corresponding controller (controller is binded through initialBinding of GetMaterialApp).
GetMaterialApp(
...
initialBinding: AnimationBinding()
...
);
class AnimationBinding extends Bindings {
#override
void dependencies() {
Get.lazyPut<AnimationController>(
() => AnimationController(),
);
}
}
class CustomAnimatedWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return GetBuilder<LogoAnimationController>(
builder: (_) {
return Container(
width: 150,
height: 150,
child: Text('for demo only, originally its not text widget'),
...
remaining code that uses `_.animation.value`
...
),
);
},
);
}
}
class AnimationController extends GetxController with SingleGetTickerProviderMixin {
Animation animation;
AnimationController _animationController;
#override
void onInit() {
super.onInit();
_animationController = AnimationController(
vsync: this, duration: Duration(seconds: 2));
_animationController.repeat(reverse: true);
animation = Tween(begin: 2.0, end: 15.0)
.animate(_animationController)
..addListener(() => update());
}
#override
void onReady() {
super.onReady();
}
#override
void onClose() {
super.onClose();
_animationController.dispose();
}
}
2: Used this widget inside view.
child: CustomAnimatedWidget();
Till now, everything is working fine. But I want to update blur, spread & duration, with:
updating CustomAnimatedWidget:
final double blur;
final double spread;
final int duration;
CustomAnimatedWidget({this.blur, this.spread, this.duration});
...
builder: (_) => ...
...
initState: (s) {
_.blur.value = blur;
_.spread.value = spread;
_.duration.value = duration;
},
...
updating AnimationController:
Rx<double> blur = 2.0.obs;
Rx<double> spread = 15.0.obs;
Rx<int> duration = 2.obs;
and using/calling the CustomAnimationWidget using:
child: CustomAnimatedWidget(duration: 4, blur: 2, spread: 10);
but don't know how to use it with _animationController & _animation because they are called in onInit.
Please share your solutions, thanks.
I Extended GetxController with GetTickerProviderStateMixin and then make a AnimationController and CurvedAnimation by code below:
late final AnimationController _controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
)..repeat(reverse: false);
late final Animation<double> animation = CurvedAnimation(
parent: _controller,
curve: Curves.ease,
);
After that i just created object of my CustomGetxController like this final _customController = Get.put(CustomGetxController()); then i called it like this:
FadeTransition(
opacity: driverController.animation,
child:const Text('My Flashing Text')
),

Flutter Splash Screen Video Player

I am trying to create a background video splash screen for my app.
Currently, I am achieving a blank screen by running this code.
void main() => runApp(WalkThrough());
class WalkThrough extends StatefulWidget {
#override
_WalkThroughState createState() => _WalkThroughState();
}
class _WalkThroughState extends State<WalkThrough> {
VideoPlayerController _controller;
#override
void initState() {
super.initState();
// Pointing the video controller to our local asset.
_controller = VideoPlayerController.asset('assets/video.mp4')
..initialize().then((_) {
// Once the video has been loaded we play the video and set looping to true.
_controller.play();
_controller.setLooping(true);
_controller.setVolume(0.0);
_controller.play();
// Ensure the first frame is shown after the video is initialized.
setState(() {});
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: <Widget>[
I suspect the problem may be here and have based my research off this Full screen video background in Flutter on Login as I am trying to achieve a similar result.
SizedBox.expand(
child: FittedBox(
// If your background video doesn't look right, try changing the BoxFit property.
// BoxFit.fill created the look I was going for.
fit: BoxFit.fill,
child: SizedBox(
width: _controller.value.size?.width ?? 0,
height: _controller.value.size?.height ?? 0,
child: VideoPlayer(_controller),
),
),
),
I think the video player package has an issue to show video in the Ios simulator. I had the same issue and search for it and find out this issue in Github. until now this issue is open. I tested the video player on a real device and there were no problems.
Use this video player video_player plugin
& Save your mp4 file in assets/videos location
Try the below code
import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:video_player/video_player.dart';
import 'HomePage.dart';
class SplashPage extends StatefulWidget {
SplashPage({Key key}) : super(key: key);
#override
State<StatefulWidget> createState() => _SplashPageState();
}
class _SplashPageState extends State<SplashPage> {
VideoPlayerController _controller;
bool _visible = false;
#override
void initState() {
super.initState();
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
]);
_controller = VideoPlayerController.asset("assets/video/splash_video.mp4");
_controller.initialize().then((_) {
_controller.setLooping(true);
Timer(Duration(milliseconds: 100), () {
setState(() {
_controller.play();
_visible = true;
});
});
});
Future.delayed(Duration(seconds: 4), () {
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(
builder: (context) => HomePage(param_homepage: 'Welcome Home')),
(e) => false);
});
}
#override
void dispose() {
super.dispose();
if (_controller != null) {
_controller.dispose();
_controller = null;
}
}
_getVideoBackground() {
return AnimatedOpacity(
opacity: _visible ? 1.0 : 0.0,
duration: Duration(milliseconds: 1000),
child: VideoPlayer(_controller),
);
}
_getBackgroundColor() {
return Container(color: Colors.transparent //.withAlpha(120),
);
}
_getContent() {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.start,
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Stack(
children: <Widget>[
_getVideoBackground(),
],
),
),
);
}
}

Flutter: Perform an action when a countdown timer reaches 0

I've created a screen in Flutter that displays a countdown timer. I'm able to play, pause and restart the timer, but I am trying to figure out how to perform an action when the timer reaches 0 (for example, restart itself).
As the dart file is fairly lengthy, I'm just copying below what I believe to be the relevant portions here. But I can add more if needed.
First I create a widget/class for the countdown timer:
class Countdown extends AnimatedWidget {
Countdown({ Key key, this.animation }) : super(key: key, listenable: animation);
Animation<int> animation;
#override
build(BuildContext context){
return Text(
animation.value.toString(),
style: TextStyle(
fontSize: 120,
color: Colors.deepOrange
),
);
}
}
I then have a stateful widget which creates the controller and also imports some data (gameData.countdownClock is the countdown timer's start time, it comes from user input at an earlier screen):
class _GameScreenState extends State<GameScreen> with TickerProviderStateMixin {
AnimationController _controller;
_GameScreenState(this.gameData);
GameData gameData;
#override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: Duration(seconds: gameData.countdownClock),
);
}
And then the container that displays the clock:
Container(
child: Countdown(
animation: StepTween(
begin: gameData.countdownClock,
end: 0,
).animate(_controller),
),
),
Do I have to add a listener in that last container? Or somewhere else? (Or something else entirely!)
Any help is appreciated. Thank you
I found the answer on this page:
After complete the widget animation run a function in Flutter
I needed to add .addStatusListener to the animation controller in the initState().
So the new initState() code looks like this:
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: Duration(seconds: gameData.countdownClock),
);
_controller.addStatusListener((status){
if(status == AnimationStatus.completed){
_controller.reset();
}
}
);
}
Possible value of Animation controller is between 0 to 1.
So I think you have to add listener on gamedata.countDownClock
Please check the below code you might get some idea from it.
import 'dart:async';
import 'package:flutter/material.dart';
class GameScreen extends StatefulWidget {
#override
_GameScreenState createState() => _GameScreenState();
}
class _GameScreenState extends State<GameScreen> with SingleTickerProviderStateMixin {
int countdownClock = 10;
#override
void initState() {
super.initState();
// Your Game Data Counter Change every one Second
const oneSec = const Duration(seconds:1);
new Timer.periodic(oneSec, (Timer t) {
// Restart The Counter Logic
if (countdownClock == 0)
countdownClock = 11;
setState(() { countdownClock = countdownClock - 1; });
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("")),
body: Center(
child: Text('$countdownClock')
),
);
}
}

Triggering widget animation from outside widget

I have a custom widget that has normal / animated state. Sometimes I want to be it animated, and sometimes static.
I have made a simple test project to demonstrate my problem: test page contains my custom widget (ScoreBoard) and 2 buttons to start / stop animating scoreboard. My problem, that ScoreBoard is not animated, even if I start animation.
Here is my code:
TestPage:
class TestPage extends StatefulWidget {
#override
_TestPageState createState() => _TestPageState();
}
class _TestPageState extends State<TestPage> {
bool _animate;
#override
void initState() {
_animate = false;
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
ScoreBoard(
text: "Hello, world!",
animate: _animate,
),
FlatButton(
child: Text("START animation"),
onPressed: () {
setState(() {
_animate = true;
});
},
),
FlatButton(
child: Text("STOP animation"),
onPressed: () {
setState(() {
_animate = false;
});
},
),
],
),
);
}
}
ScoreBoard widget:
class ScoreBoard extends StatefulWidget {
final String text;
final bool animate;
const ScoreBoard({Key key, this.text, this.animate}) : super(key: key);
#override
_ScoreBoardState createState() => _ScoreBoardState();
}
class _ScoreBoardState extends State<ScoreBoard>
with SingleTickerProviderStateMixin {
AnimationController _controller;
#override
void initState() {
super.initState();
_controller = new AnimationController(
duration: const Duration(seconds: 1),
vsync: this,
)..forward();
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return widget.animate
? ScaleTransition(
child:
Text(widget.text, style: Theme.of(context).textTheme.display1),
scale: new CurvedAnimation(
parent: _controller,
curve: Curves.easeIn,
),
)
: Container(
child:
Text(widget.text, style: Theme.of(context).textTheme.display1),
);
}
}
Would you be so kind to help me? Thanks in advance!
Answer
If you initialize an AnimationController widget and do not specify arguments for lowerBound and upperBound (which is the case here), your animation is going to start by default with lowerBound 0.0.
AnimationController({double value, Duration duration, String debugLabel, double lowerBound: 0.0, double upperBound: 1.0, AnimationBehavior animationBehavior: AnimationBehavior.normal, #required TickerProvider vsync })
Creates an animation controller. [...]
https://docs.flutter.io/flutter/animation/AnimationController-class.html
If you initialize the state of your widget ScoreBoard, the method forward gets called only once during the whole lifetime of your app. The method forward makes that your animation increases from the lowerBound (0.0) to the upperBound (1.0) within 1 second.
Starts running this animation forwards (towards the end).
https://docs.flutter.io/flutter/animation/AnimationController/forward.html
In our case, there is no way back once the method forward got called. We are only able to stop the animation.
Press Ctrl + F5 for a full restart of the app to see the animation.
To make it even clearer, change the duration of your animation to 10 seconds.
Btw. since Dart 2 you do not need to use the new keyword.
#override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 10),
vsync: this,
)..forward();
}
Example
To see what happens you could add this to your build method:
#override
Widget build(BuildContext context) {
// Execute 'reverse' if the animation is completed
if (_controller.isCompleted)
_controller.reverse();
else if (_controller.isDismissed)
_controller.forward();
// ...
... and do not call the method forward in the initState method:
#override
void initState() {
super.initState();
_controller = new AnimationController(
duration: const Duration(seconds: 10),
vsync: this,
);
}

How do i access an inner widget's animation controller (or state) from outer widget?

So I’ve been experimenting with Flutter & Dart for the past few days.
I’m stuck on this one for over a day now, so I’m here.
So I have the AlarmScreen, and we have 2 objects inside it. One is the DraggableMoonWidget, and the other is the RisingSunWidget.
Currently, the RisingSunWidget animates onto the screen from the bottom, while the DraggableMoonWidget is draggable by touch.
What I want to achieve, is that when the RisingSunWidget’s animation would stop and change when the DraggableMoonWidget is being dragged. So I have the MoonDragListener in place and working, but I still can’t figure it out. (currently calls the listener back to the AlarmScreen, but then what?)
I have tried a whole bunch of methods to do that, all deleted since then, as not a single one worked.
TLDR
How do I control the RisingSunWidget’s animation controller when the user touches the DraggableMoonWidget, for example, I want to stop the controller and animate it to a different point.
What is the best approach in dart/flutter?
AlarmScreen
import 'package:flutter/widgets.dart';
import 'package:moonworshiper_app/backgrounds.dart';
import 'package:moonworshiper_app/ui/alarm/moon_draggable.dart';
import 'package:moonworshiper_app/ui/alarm/rising_sun.dart';
class AlarmScreen extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return new _AlarmScreenState();
}
}
class _AlarmScreenState extends State<AlarmScreen> {
bool _moonWasTouched = false;
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return new Stack(
children: <Widget>[
new DraggableMoonWidget(new MoonDragListener(this)),
new LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
return new RisingSunWidget(constraints.heightConstraints().maxHeight, _moonWasTouched);
})
],
);
}
void _refreshSun() {
setState(() {
_moonWasTouched = true;
});
}
}
class MoonDragListener {
_AlarmScreenState state;
MoonDragListener(this.state);
void onMoonDragStarted() {
state._refreshSun();
}
}
DraggableMoonWidget
import 'package:flutter/widgets.dart';
import 'package:moonworshiper_app/ui/alarm/alarm_screen.dart';
class DraggableMoonWidget extends StatefulWidget {
final MoonDragListener moonStartListener;
DraggableMoonWidget(this.moonStartListener);
State<StatefulWidget> createState() => new _DraggableMoonState();
}
class _DraggableMoonState extends State<DraggableMoonWidget>
with TickerProviderStateMixin {
final moonDragTween = new Tween<Offset>(
begin: new Offset(0.0, -0.5),
end: new Offset(0.0, 0.5),
);
var moonAnimListener;
AnimationController _animationController;
Animation<Offset> _dragAnimation;
AnimationController _dragAnimationController;
bool isFirstDraw = true;
#override
initState() {
super.initState();
_animationController = new AnimationController(
vsync: this,
duration: const Duration(milliseconds: 3000),
);
_dragAnimationController = new AnimationController(vsync: this);
moonAnimListener = (AnimationStatus status) {
if (status == AnimationStatus.dismissed) {
_animationController.forward();
} else if (status == AnimationStatus.completed) {
_animationController.reverse();
} else if (status == AnimationStatus.forward) {}
};
_dragAnimation = moonDragTween.animate(new CurvedAnimation(
parent: _dragAnimationController,
curve: Curves.easeInOut,
reverseCurve: Curves.easeInOut));
_dragAnimationController.animateTo(0.5, duration: new Duration());
_animationController.addStatusListener(moonAnimListener);
_animationController.forward();
}
#override
Widget build(BuildContext context) {
return new Container(
child: new Center(
child: new SlideTransition(
position: _dragAnimation,
child: new GestureDetector(
child: new Image.asset(
"assets/moon.png",
width: 280.0,
height: 280.0,
),
onVerticalDragStart: (DragStartDetails details) {
print("start:" + details.globalPosition.toString());
_animationController.removeStatusListener(moonAnimListener);
_animationController.stop();
_dragStartDetails = details;
_dragAnimationController.animateTo(0.5,
duration: new Duration(milliseconds: 50));
if (isFirstDraw) {
isFirstDraw = false;
widget.moonStartListener.onMoonDragStarted();
}
},
),
),
),
// margin: new EdgeInsets.only(top: 48.0),
);
}
#override
void dispose() {
_animationController.dispose();
super.dispose();
}
}
RisingSunWidget
import 'package:flutter/widgets.dart';
class RisingSunWidget extends StatefulWidget {
// needed to calculate the offset map
final double screenHeight;
// that's how we know if the use touched the moon
final bool moonWasTouched;
RisingSunWidget(this.screenHeight, this.moonWasTouched);
#override
State<StatefulWidget> createState() {
return new RisingSunState();
}
}
class RisingSunState extends State<RisingSunWidget> with TickerProviderStateMixin {
AnimationController _animationController;
Animation<Offset> _sunAnimation;
final double sunSize = 320.0;
#override
initState() {
super.initState();
_animationController = new AnimationController(
vsync: this,
duration: const Duration(milliseconds: 6000),
);
// how many suns fit in the height of our screen
assert(widget.screenHeight > sunSize);
double sunsInHeight = widget.screenHeight / sunSize;
print(sunsInHeight.toString() + " suns could fit on the user's screen");
var sunsPlusMargins = sunsInHeight + 1; // required margins
final moonTween = new Tween<Offset>(
begin: new Offset(0.0, -0.5 * sunsPlusMargins),
end: new Offset(0.0, 0.5 * sunsPlusMargins), //move by 8% of height max
);
_sunAnimation = moonTween.animate(new CurvedAnimation(
parent: _animationController,
curve: Curves.easeInOut,
reverseCurve: Curves.easeInOut,
));
if (widget.moonWasTouched) {
_animationController.stop();
_animationController.animateTo(0.68,
duration: new Duration(milliseconds: 2000));
} else {
_animationController.animateTo(0.88,
duration: new Duration(milliseconds: 0));
_animationController.animateTo(0.75,
duration: new Duration(milliseconds: 15000));
}
}
#override
Widget build(BuildContext context) {
return new Center(
child: new SlideTransition(
position: _sunAnimation,
child: new Image.asset(
"assets/sun.png",
width: sunSize,
height: sunSize,
),
),
);
}
#override
void dispose() {
_animationController.dispose();
super.dispose();
}
}
2 possibilities :
Use the BuildContext context that is provided in your build method. BuildContext has a few methods get the closest parent of a specific type.
Pass a key attribute to the desired widget.
GlobalKey to be exact.
GlobalKey allows you directly access to a Widget or it's state.