So a have a stateful parent with a timer value. This timer can be reset by the user. I also have a child stateful widget with an animation controller. The parent passes the timer value to this child to reset the animation. Here is some code:
class _ParentState extends State<Parent> {
int timer = 20;
void _userInput() {
setState(() {
timer = 20;
}
}
#override
Widget build(BuildContext context) {
return Child(
time: timer
);
}
}
Note: this is striped down for simplicity
So when my user triggers the _userInput method, the timer value gets changed but the child doesn't. Here is my full child widget:
class TimerProgress extends StatefulWidget {
TimerProgress({
Key key,
#required this.time,
#required this.onCompleted
}) : super(key: key);
final int time;
final Function onCompleted;
#override
_TimerProgressState createState() => _TimerProgressState();
}
class _TimerProgressState extends State<TimerProgress> with SingleTickerProviderStateMixin {
AnimationController _controller;
Animation<double> _widthAnimation;
#override
void initState() {
_controller = AnimationController(
duration: Duration(seconds: widget.time),
vsync: this
);
_widthAnimation = Tween<double>(
begin: 200,
end: 0
).animate(CurvedAnimation(
parent: _controller,
curve: Curves.linear
));
_controller.addListener(() {
if (_controller.value == 1) {
widget.onCompleted();
}
});
_controller.forward();
super.initState();
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return ...
}
}
So this all works, but ideally I want the animation to be reset, which doesn't work...
initState runs only once per state instance.
Implement resetting the time inside didUpdateWidget: https://api.flutter.dev/flutter/widgets/RawScrollbarState/didUpdateWidget.html
You can't (cause it will cause recursive loop, setState will trigger didUpdateWidget again) and shouldn't (after this method is invoked, build is being called) have to call setState inside this method.
Runnable example: https://www.dartpad.dev/848976603f866f4e3aa6030aba8addf7?null_safety=true
I think hot reloading is equivalent to calling setState but the following code contradicts this.
My State subclass:
class _FooPageState extends State<FooPage> {
#override
void initState() {
super.initState();
Timer.periodic(Duration(seconds: 1), (_) {
// Calling setState every 1 second.
setState(() {});
});
}
#override
Widget build(BuildContext context) => CustomPaint(painter: FooPainter());
}
My custom painter:
class FooPainter extends CustomPainter {
#override
void paint(Canvas canvas, Size size) => print('paint');
#override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
As you can see after 1s I'm calling setState, however, the print statement in paint method isn't invoked every second. But as soon as I do hot reload using IDE shortcut, it does print the statement.
Note: I know I can return true from shouldRepaint to make the print statement work but that's not the question.
After upgrading to Flutter 0.7.3 channel beta (Dart 2.1.0-dev.1.0.flutter-ccb16f7282) the WidgetsBindingObserver is not working.
It worked before and after login if AppLifecycleState paused, inactive or suspended it returns to main page. But seems it doesnt work with new update. My question is where I can get information to see how to make the WidgetsBindingObserver works again.
// Statefull HomePage
class PersonalLoginPage extends StatefulWidget {
const PersonalLoginPage({ Key key }) : super(key: key);
#override
_PersonalLoginPageState createState() => new _PersonalLoginPageState();
}
class _PersonalLoginPageState extends State<PersonalLoginPage> with WidgetsBindingObserver {
AppLifecycleState _appLifecycleState;
bool _appStatePause;
// TODO: initState function
#override
void initState() {
print("initState Starting Now .......................");
super.initState();
authenticateUser();
WidgetsBinding.instance.addObserver(this);
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
Timer _timer;
setState(() {
_appLifecycleState = state;
if (_appLifecycleState == AppLifecycleState.paused ||
_appLifecycleState == AppLifecycleState.inactive ||
_appLifecycleState == AppLifecycleState.suspending) {
_appStatePause = true;
print("New Timer Starting Now .......................");
_timer = Timer.periodic(Duration(seconds: 60), _callback);
} else {
_appStatePause = false;
}
});
}
void _callback(_timer) {
if (_appStatePause == true) {
print("Timer Finished without cancel...................");
setState(() {
Navigator.push(
context,
SlideRightRoute(widget: MyApp()),
);
});
} else {
_timer.cancel();
print("Timer cancel now................................");
}
}
// TODO: authenticateUser function
Future authenticateUser() async {
……
……
……
}
#override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
// TODO: main build Widget
#override
Widget build(BuildContext context) {
return Scaffold(
body: new Center(
child: new Text(“Hello World”)
)
);
}
How to hot-reload fields of a State subclass in Flutter?
I know that the modifying the initial value of fields isn't taken into account during hot-reload and that I can use hot-restart for them. But this is painfully slow.
Is there any way to ease the process?
A typical use-case would be animations, especially AnimationController. As it is stored inside a State field, but we usually want to iterate over its duration. Example:
class MyAnim extends StatefulWidget {
#override
_MyAnimState createState() => _MyAnimState();
}
class _MyAnimState extends State<MyAnim> with SingleTickerProviderStateMixin {
AnimationController animationController;
#override
void initState() {
animationController =
AnimationController(vsync: this, duration: const Duration(seconds: 1));
super.initState();
}
#override
void dispose() {
animationController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Container();
}
}
State provides a custom lifecycle hook for hot-reloads: reassemble
You can freely override that method to have custom hot-reload behaviors. Don't worry, this method will never be called in production.
With a small tweak you'd get the following:
class _MyAnimState extends State<MyAnim> with SingleTickerProviderStateMixin {
AnimationController animationController;
#override
void initState() {
animationController = AnimationController(vsync: this);
_initializeFields();
super.initState();
}
void _initializeFields() {
animationController.duration = const Duration(seconds: 1);
}
#override
void reassemble() {
_initializeFields();
super.reassemble();
}
#override
void dispose() {
animationController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Container();
}
}
Now, whenever you modify your State class, it will correctly update AnimationController's duration.
When I click the raised button, the timepicker is showing up. Now, if I wait 5 seconds, for example, and then confirm the time, this error will occur:
setState() called after dispose()
I literally see in the console how flutter is updating the parent widgets, but why? I don't do anything - I just wait 5 seconds?!
The example below will work in a normal project, however in my project which is quite more complex it won't work because Flutter is updating the states while I am waiting... What am I doing wrong? Does anyone have a guess at what it could be that Flutter is updating randomly in my more complex project and not in a simple project?
[UPDATE]
I took a second look at it and found out it is updating from the level on where my TabBar and TabBarView are.
Could it have to do something with the "with TickerProviderStateMixin" which I need for the TabBarView? Could it be that it causes the app to refresh regularly and randomly?
class DateTimeButton extends State<DateTimeButtonWidget> {
DateTime selectedDate = new DateTime.now();
Future initTimePicker() async {
final TimeOfDay picked = await showTimePicker(
context: context,
initialTime: new TimeOfDay(hour: selectedDate.hour, minute: selectedDate.minute),
);
if (picked != null) {
setState(() {
selectedDate = new DateTime(selectedDate.year, selectedDate.month, selectedDate.day, picked.hour, picked.minute);
});
}
}
#override
Widget build(BuildContext context) {
return new RaisedButton(
child: new Text("${selectedDate.hour} ${selectedDate.minute}"),
onPressed: () {
initTimePicker();
}
);
}
}
Just check boolean property mounted of the state class of your widget before calling setState().
if (this.mounted) {
setState(() {
// Your state change code goes here
});
}
Or even more clean approach
Override setState method in your StatelfulWidget class.
class DateTimeButton extends StatefulWidget {
#override
void setState(fn) {
if(mounted) {
super.setState(fn);
}
}
}
If it is an expected behavior that the Future completes when the widget already got disposed you can use
if (mounted) {
setState(() {
selectedDate = new DateTime(selectedDate.year, selectedDate.month, selectedDate.day, picked.hour, picked.minute);
});
}
Just write one line before setState()
if (!mounted) return;
and then
setState(() {
//Your code
});
I had the same problem and i solved changing the super constructor call order on initState():
Wrong code:
#override
void initState() {
foo_bar(); // call setState();
super.initState(); // then foo_bar()
}
Right code:
#override
void initState() {
super.initState();
foo_bar(); // first call super constructor then foo_bar that contains setState() call
}
To prevent the error from occurring, one can make use of the mounted property of the State class to ensure that a widget is mounted before settings its state:
// First Update data
if (!mounted) {
return;
}
setState(() { }
Try this
Widget build(BuildContext context) {
return new RaisedButton(
child: new Text("${selectedDate.hour} ${selectedDate.minute}"),
onPressed: () async {
await initTimePicker();
}
);
}
class MountedState<T extends StatefulWidget> extends State<T> {
#override
Widget build(BuildContext context) {
return null;
}
#override
void setState(VoidCallback fn) {
if (mounted) {
super.setState(fn);
}
}
}
Example
To prevent the error,Instead of using State use MountedState
class ExampleStatefulWidget extends StatefulWidget {
const ExampleStatefulWidget({Key key}) : super(key: key);
#override
_ExampleStatefulWidgetState createState() => _ExampleStatefulWidgetState();
}
class _ExampleStatefulWidgetState extends MountedState<ExampleStatefulWidget> {
....
}
I had this error when I mistakenly called super.initState before the variable. Check this:
#override
void initState() {
super.initState();
bloc = MainBloc();
}
Should be fixed as
#override
void initState() {
bloc = MainBloc();
super.initState();
}
The problem could occur when you have long asynchronous operation in stateful widget that could be closed/disposed before the operation finished.
Futures in Dart are not preemptive, so the only way is to check if a widget mounted before calling setState.
If you have a lot of widgets with asynchrony, adding ton of if (mounted) checks is tedious and an extension method might be useful
extension FlutterStateExt<T extends StatefulWidget> on State<T> {
void setStateIfMounted(VoidCallback fn) {
if (mounted) {
// ignore: invalid_use_of_protected_member
setState(fn);
}
}
}