Periodic Timer can not dispose - flutter

Code content is not important. Just one problem timer can not dispose when I want to leave this page. When I leave from this page, sendMessage("message"); function continue to run. Is there any option to dispose this timer?
Timer timer;
#override
void initState() {
super.initState();
timer = Timer.periodic(new Duration(seconds: 5), (timer) async {
setState(() {
unicode++;
unicodeString = unicode.toString();
if (unicodeString.length < 6) {
int different = 6 - unicodeString.length;
for (var i = 0; i < different; i++) {
unicodeString = "0" + unicodeString;
}
}
sendMessage("meesage");
showSnackBarWithKey("Message Sended !");
});
});
}
#override
void dispose() {
timer.cancel();
super.dispose();
}
The error is below.
EXCEPTION CAUGHT BY WIDGETS LIBRARY
The following assertion was thrown while finalizing the widget tree:
'package:flutter/src/widgets/framework.dart': Failed assertion: line 4182 pos 12:
'_debugLifecycleState != _ElementLifecycle.defunct': is not true.
Either the assertion indicates an error in the framework itself, or we should provide substantially
more information in this error message to help you determine and fix the underlying cause.
In either case, please report this assertion by filing a bug on GitHub:
I use dispose timer, but it can not dispose timer. I could not solve this problem. Help, please.

i findout issue after run your code,
the main problem is,
dispose is called on a widget when it is completely removed from the parent tree.
so when you route new page,
Using push navigation, a new screen is added on top of current
screen. hence the tree (of old screen) is not completely destroyed
hence dispose is not called.
using pop. the screen is removed so is the tree. hence dispose is
called.
using push replacement. new screen replaces old screen deleting the
widget tree. so dispose is called.
and for code,
try this.
(main part is pushReplacement i am using this for navigation)
Navigator.pushReplacement(
context, MaterialPageRoute(builder: (context) => SplashScreen()));
final code is,
class TimerButton extends StatefulWidget {
#override
_TimerButtonState createState() => _TimerButtonState();
}
class _TimerButtonState extends State<TimerButton> {
Timer _timer;
#override
void initState() {
super.initState();
_timer = Timer.periodic(new Duration(seconds: 5), (timer) async{
setState(() {
/* unicode++;
unicodeString = unicode.toString();
if (unicodeString.length < 6) {
int different = 6 - unicodeString.length;
for (var i = 0; i < different; i++) {
unicodeString = "0" + unicodeString;
}
}*/
sendMessage("meesage");
showSnackBarWithKey("Message Sended !");
});
});
}
#override
void dispose() {
_timer.cancel();
super.dispose();
}
#override
Widget build(BuildContext context) {
// TODO: implement build
return RaisedButton(
onPressed: (){
Navigator.pushReplacement(
context, MaterialPageRoute(builder: (context) => SplashScreen()));
},
child: Text("data"),
);
}
}

I hope the above solution works fine for you but if not, then you can also try the below code because in my case the above solution does not works fine.
static Timer timerObjVar;
static Timer timerObj;
timerObj = Timer.periodic(Duration(seconds: 10), (Timer timer) async {
timerObjVar = timer;
_initData();
});
// when you want to cancel the timer call this function
cancelTimer() {
if (timerObjVar != null) {
timerObjVar.cancel();
timerObjVar = null;
}
if (timerObj != null) {
timerObj.cancel();
timerObj = null;
}
}

Instead of dispose() try putting it in a deactivate().

Related

Flutter How can I use setState and only change one area and leave the other as it is?

I want a page that has a timer and also displays math problems. and whenever the correct answer has been entered, a new task should appear. But the problem is that whenever a new task appears, the timer is reset. How can I prevent this ?
late Timer timer;
double value = 45;
void startTimer() {
timer = Timer.periodic(const Duration(seconds: 1), (timer) {
if (value > 0) {
setState(() {
value--;
});
} else {
setState(() {
timer.cancel();
});
}
});
}
#override
void initState() {
// TODO: implement initState
super.initState();
startTimer();
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
void userTextFieldInput() {
controller.addListener(
() {
String rightResult = (firstIntValue + secondIntValue).toString();
String userResult = controller.text;
if (rightResult == userResult) {
setState(() {
DatabaseHelper(
firstUserValue: firstIntValue,
secondUserValue: secondIntValue,
finalUserResult: int.parse(controller.text),
).setDB();
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => const CalculatePage(),
),
);
});
} else if (controller.text.length >= 2) {
controller.clear();
}
},
);
}
You should create two different state, one for timer and another one for the problems.
You can use the package flutter bloc to manage these state easily.

Flutter: Unhandled Exception: 'package:provider/src/provider.dart': Failed assertion: line 240 pos 12: 'context != null': is not true

I get the exception above when I navigate from this page (shoe_box_page.dart) to another page and the content from this page (shoe_box_page.dart) is not completely loaded yet.
The error message I get
class ShoeBoxPage extends StatefulWidget {
#override
_ShoeBoxPageState createState() => _ShoeBoxPageState();
}
class _ShoeBoxPageState extends State<ShoeBoxPage> {
final _scrollController = ScrollController();
#override
void initState() {
super.initState();
Future.delayed(Duration(seconds: 1)).then((_) {
_checkRequestLoad();
});
}
bool get _canScroll {
if (!_scrollController.hasClients) return false;
final x = _scrollController.position.maxScrollExtent;
final deviceHeight = MediaQuery.of(context).size.height;
return x - _progressIndicatorHeight > deviceHeight;
}
void _checkRequestLoad() {
final bloc = context.bloc<ShoeBoxBloc>();
if (bloc.state.billsAvailable && !_canScroll) {
context
.bloc<ShoeBoxBloc>()
.add(ShoeBoxEvent.scrollingOverUnloadedScope());
Future.delayed(Duration(seconds: 1)).then((_) {
_checkRequestLoad();
});
}
}
#override
Widget build(BuildContext context) {
return BlocBuilder<ShoeBoxBloc, ShoeBoxState>(
...
I hope someone of you can help me :)
Best,
Alex
I think the problem is: context inside the function is null.
So you either need to define those functions inside the build method where you can get the context, or pass the context as the function argument while calling those functions.
When the future in _checkRequestLoad completes, the state might not be used anymore. So before doing something after an asynchronous gap, you should check to see if the element (that the state belongs to) is still mounted:
void _checkRequestLoad() {
final bloc = context.bloc<ShoeBoxBloc>();
if (bloc.state.billsAvailable && !_canScroll) {
context
.bloc<ShoeBoxBloc>()
.add(ShoeBoxEvent.scrollingOverUnloadedScope());
Future.delayed(Duration(seconds: 1)).then((_) {
if (mounted) // <---
_checkRequestLoad();
});
}
}
I'm not sure this is causing the exception you see, but it is a bug.

Flutter : Cancel Timer In Dispose Not Working

I have create running clock using timer with this code :
Source Code
class LiveClock extends StatefulWidget {
#override
_LiveClockState createState() => _LiveClockState();
}
class _LiveClockState extends State<LiveClock> {
String _timeString;
String _dateString;
Timer _timerClock;
String _formatTime(DateTime dateTime) => DateFormat.Hms().format(dateTime);
String _formatDate(DateTime dateTime) =>
DateFormat.yMMMMEEEEd(appConfig.indonesiaLocale).format(dateTime);
#override
void initState() {
super.initState();
_timeString = _formatTime(DateTime.now());
_dateString = _formatDate(DateTime.now());
_timerClock = Timer.periodic(Duration(seconds: 1), _getTime);
}
#override
void dispose() {
_timerClock.cancel();
super.dispose();
}
void _getTime(Timer timer) {
final DateTime now = DateTime.now();
final String formattedTime = _formatTime(now);
setState(() => _timeString = formattedTime);
}
#override
Widget build(BuildContext context) {
print('This Rebuild');
return Text(
'$_dateString $_timeString ',
textAlign: TextAlign.center,
);
}
}
Result
But the problem is , if i navigate to another screen , the timer still running although i have dispose the timer.
did I make mistake or it's behaviour the timer ?
In flutter, dispose is called on a widget when it is completely removed from the parent tree.
When using routes(navigation) in flutter.
Using push navigation, a new screen is added on top of current screen. hence the tree (of old screen) is not completely destroyed hence dispose is not called.
using pop. the screen is removed so is the tree. hence dispose is called.
using push replacement. new screen replaces old screen deleting the widget tree. so dispose is called.
hope this helps

setState() or markNeedsBuild() called during build exception prevents me from executing callback

I'm trying to execute a callback function once a timer ends in a particular widget but I keep getting this exception:
I/flutter (16413): Another exception was thrown: setState() or markNeedsBuild() called during build.
So I have this widget called countdown:
class Countdown extends StatefulWidget {
final VoidCallback onCountdownExpire;
Countdown(this.onCountdownExpire);
#override
CountdownState createState() => CountdownState();
}
class CountdownState extends State<Countdown> with TickerProviderStateMixin {
AnimationController controller;
String get timerString {
Duration duration = controller.duration * controller.value;
return '${duration.inMinutes}:${(duration.inSeconds % 60).toString()}';
}
#override
void initState() {
super.initState();
controller = AnimationController(
vsync: this,
duration: Duration(seconds: 2),
)..addStatusListener((AnimationStatus status){
if (status == AnimationStatus.completed)
widget.onCountdownExpire();
});
controller.reverse(from: 1.0);
}
...
...
... // omitted code
}
So once the animation is completed it will call the callback function:
class _QuizPageState extends State<QuizPage> {
... // omitted code
#override
void initState() {
... // omitted code
}
void onCountdownExpire() {
setState(() {
_topContentImage = AssetImage(questions[questionNum++].imagePath);
});
}
... // omitted code
}
I've tried to follow a solution but it does not work and gives me the same exception:
void onCountdownExpire() =>
setState(() {
_topContentImage = AssetImage(questions[questionNum++].imagePath);
});
I also tried this but to no avail:
#override
void initState() {
super.initState();
controller = AnimationController(
vsync: this,
duration: Duration(seconds: 2),
)..addStatusListener((AnimationStatus status) =>
(status == AnimationStatus.completed) ?
widget.onCountdownExpire():null
);
controller.reverse(from: 1.0);
}
maybe try including 'dart:async':
import 'dart:async';
then try wrapping your call of the onCountdownExpire function in a short-lived Timer():
...
Timer(
Duration(milliseconds:50),
() {
if (status == AnimationStatus.completed)
widget.onCountdownExpire();
},
);
...
this will make the setState() happen outside the build phase of the last frame of your animation.
The error occurs most likely because the Countdown() is being redrawn as part of the redraw of the QuizPage() widget. Adding the Timer() will force the update to happen outside of the build() scope, in an async fashion, and will still achieve the same results without the error.

setState() called after dispose()

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);
}
}
}