Managing multiple setState() methods in single class? - flutter

I am creating a timer when it will come to 0. It will navigate to the next Page.(Not created navigation yet). Till the timer is on user can answer the questions. The problem is I want to start the timer as the StartGame() file opens. In main.dart I have created a navigation for this on click on button. But when this page loads it automatically calls both ( in void getNum() and startTime() ) setState methods() one by one. Therefore answers (options) that I created as buttons automatically changes without onPressed by user. But I only want startTime() method to be called once as widget builds / inits. then after the getNum() method on every click of user.
How do i make these two setState() methods to be called individually without affecting each other.
class StartGame extends StatefulWidget {
StartGameState createState() => StartGameState();
}
class StartGameState extends State<StartGame> {
int no1, no2, no3, no4, inp1, inp2;
int pos;
int res;
List<int> answers;
GenerateQuestion g = new GenerateQuestion();
void getNum() {
answers = g.generateNum();
pos = g.answerPosition();
inp1 = g.generateValue();
inp2 = g.generateValue();
res = inp1 + inp2;
answers.add(res);
answers.shuffle();
setState(() {
no1 = answers[0];
no2 = answers[1];
no3 = answers[2];
no4 = answers[3];
print("set state 1 called");
});
} // void get Num ends here
//counter timer starts from herer
int counter = 10;
Timer timer;
void startTime() {
counter = 10;
timer = new Timer.periodic(Duration(seconds: 1), (timer) {
if (counter > 0) {
setState(() {
counter--;
print("set state 2 called");
});
} else {
timer.cancel();
}
});
}

You can achieve this using the initState() method, it gets only called once when the widget is built.
class _StartGameState extends State<StartGame> {
#override
void initState() {
startTime(); //this function only gets called once
super.initState();
}
//your Code...

Related

Multiple audioplayers keep playing on Flutter

I have an app on Flutter with different tabs and on each tab basically I have a different audioplayer. By the way I use the "audioplayers.dart" package.
When the user changes tab, I want the audioplayer to stop. So I put a stop() function in the dispose method.
However, sometimes this isn't working because of states issues.
I wonder if there is an easiest way by forbidding maybe an audioplayer to be play when the user is on another page ?
late AudioPlayer audioplayer;
#override
void initState() {
super.initState();
audioplayer = AudioPlayer(playerId: 'liked_musics');
audioplayer.onDurationChanged.listen((Duration d) {
setState(() => duree = d);
});
audioplayer.onAudioPositionChanged.listen((Duration d) {
setState(() => position = d);
});
audioplayer.onPlayerCompletion.listen((event) {
setState(() {
position = duree;
statut = PlayerState.STOPPED;
});
});
}
#override
dispose() {
audioplayer.stop();
}
Thank you for your help
Joffrey
You can use event bus package
Sample pseudo code:
class AudioStopEvent {
int clickedTabPosition;
AudioStopEvent(this.clickedTabPosition);
}
class BusHelper {
static EventBus eventBus = new EventBus();
}
class YourTabPage{
#override
void initState() {
super.initState();
BusHelper.eventBus.on<AudioStopEvent>().listen((event) {
if(event.clickedTabPosition != currentPagePosition)
audio.stop();
});
}
}
class YourTabManagementClass{
whenChangeTabPage(){
BusHelper.eventBus.fire(new AudioStopEvent(clickedPagePosition)); }
}

setState() or markNeedsBuild() called during build. Trying to make a simple timer in flutter

I am trying to make a simple timer which run till a given time. This is how I have tried to call the timer function. It gives the error as mentioned in the title. I believe the error is there because I am calling set state method in the init state, but I really need to make this functionality that, when this widget enters the screen, a timer begins and do something when the timer ends. Any help is greatly appreciated.
late double timeRemaining;
late Timer _timer;
void startTimer(double timeRemaing) {}
#override
void initState() {
timeRemaining =
widget.startDate.difference(widget.endDate).inSeconds / 1000 - 80;
const Duration seconds = Duration(seconds: 1);
_timer = Timer.periodic(seconds, (timer) {
setState(() {
timeRemaining--;
if (timeRemaining <= 0) {
// done = true;
done = true;
timer.cancel();
}
});
});
super.initState();
}
as the title says, you're building widget during another build (when you call setState in the timer).So the solution is to wait for the widget to finish building, then start your timer, this can be done by using addPostFrameCallback, like the following:
#override
void initState() {
timeRemaining =
widget.startDate.difference(widget.endDate).inSeconds / 1000 - 80;
const Duration seconds = Duration(seconds: 1);
// this will schedule a callback for the end of this frame.
WidgetsBinding.instance.addPostFrameCallback((_) {
_timer = Timer.periodic(seconds, (timer) {
setState(() {
timeRemaining--;
if (timeRemaining <= 0) {
// done = true;
done = true;
timer.cancel();
}
});
});
});
super.initState();
}
try it and tell me if this works

How to add timer in tasks after being triggered in flutter

I am working on a slider in flutter which is focused on rating. I want to add a timer to manage a rating time (i.e if the user rated then he/she can't rate again for an hour. Which means once the function is triggered it won't trigger again for a specific time).
So how is it possible to do that. I have a little knowledge about the Timer class and I don't how exactly it should be done. By doing this we are bounding the user not to rate as many times as user want
A simple implementation using a Timer for the functionality you require is to store a bool and start the timer and toggle the bool value when the user performs the function and on timeout toggle the `bool to false and start the timer again.
Below is an example that uses setState.
import 'dart:async';
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(home:TimerExample()));
}
class TimerExample extends StatefulWidget{
#override
State<StatefulWidget> createState() {
return _TimerExampleState();
}
}
class _TimerExampleState extends State<TimerExample> {
// Using a short timer for testing, set a duration
// you can set a any duration you like, For Example:
// Duration(hours: 1) | Duration(minutes: 30)
final _timeoutDuration = Duration(seconds:3);
late Timer _timer;
bool _canVote = true;
#override
void dispose() {
super.dispose();
_timer.cancel();
}
void onTimeout() {
setState(() => _canVote = true);
}
void startTimer() {
setState(() {
_canVote = false;
_timer = Timer(_timeoutDuration, onTimeout);
});
}
#override
Widget build(BuildContext context) {
return TextButton(
onPressed: _canVote ? startTimer : null,
child: Text('Vote')
);
}
}

Periodic Timer can not dispose

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().

Listening to a variable change in flutter

I'm trying to listen to a variable change to execute some code. So the variable is a bool named reset. I want to execute something (say reset the animation controller) once the animation ends OR a button (from another widget) is pressed. Executing something when the animation ends works as once it ends AnimationStatus.dismissed will be its state and the listener will be called. This then allows me to use a callback function onCountdownexpire in order to set the variable reset accordingly and based on what it is set, execute some code in the if(widget.reset) block. So there is no need to listen for this case.
Problem:
However, lets say a button is pressed (in another widget) and I set the variable reset to true. I want the animation to stop and reset. I cannot now depend on the AnimationStatus listener as it only executes when there is a state change. I want it to reset during its state. So I have to somehow listen to the variable widget.reset.
I have done some research on this and found out that ValueNotifier might be a way to go but there are not a lot of examples on how to go about using it. Like how do I go about listening to it ?
Code:
class Countdown extends StatefulWidget {
final VoidCallback onCountdownExpire;
bool reset;
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.dismissed) {
debugPrint("Animation.dismissed");
widget.onCountdownExpire();
if (widget.reset) {
widget.reset = false;
controller.reverse(from: 1.0);
}
}
});
controller.reverse(from: 1.0);
}
... // omitted code
}
What I have tried but it does not seem to be working as expected:
class Countdown extends StatefulWidget {
final VoidCallback onCountdownExpire;
Countdown(this.onCountdownExpire);
ValueNotifier reset = ValueNotifier(false);
#override
CountdownState createState() => CountdownState();
}
class CountdownState extends State<Countdown> with TickerProviderStateMixin {
AnimationController controller;
#override
void initState() {
widget.reset.addListener(() {
debugPrint("value notifier is true");
controller.reverse(from: 1.0);
});
super.initState();
controller = AnimationController(
vsync: this,
duration: Duration(seconds: 2),
)..addStatusListener((AnimationStatus status){
if (status == AnimationStatus.dismissed) {
debugPrint("Animation.dismissed");
widget.onCountdownExpire();
}
});
controller.reverse(from: 1.0);
}
... // omitted code
}
Update: The solution (above) was actually working, I just had to use something like this to notify the listener:
countdown.reset.notifyListeners();
// OR
countdown.reset.value = true;
OP already got an answer in the comments. I'm just typing it here so the question is correctly marked as answered.
Just notify the listener:
countdown.reset.notifyListeners();
// OR
countdown.reset.value = true;
Credit to #pskink
Use of overwriting getter/setters
You can make use of the getter and setter functions.
In the example below we are printing happy birthday when the person's age changes:
class Person {
int _age;
Person(this._age);
int get age => _age;
set age(int newValue) {
print("Happy birthday! You have a new Age.");
//do some awsome things here when the age changes
_age = newValue;
}
}
The usage is exactly as you are used to:
final newPerson = Person(20);
print(newPerson.age); //20
newPerson.age = 21; //prints happy birthday
print(newPerson.age); //21