I have a stopwatch widget which is called from the Detailed Activity page on one of my screens that starts when the page loads. How am I able to keep it continuously running in the background so that when I navigate from Detailed Activity to the Home page, I can display that same stopwatch's time?
At the moment, the Home page displays the last saved time (into the variable). Instead I would like this to continue incrementing each second and vice versa switching between the two screens. I've tried adding the startWatch() and initState() method in my Home page, but don't know how to pass the stopwatch value while navigating to the Detailed Activity page
To clarify this more, my thinking was on the Detailed Activity page it is at e.g. 00:15:00 -> pass it into Home page and call initState() to automatically start the timer again. Would this work?
Timer.dart
class NewStopWatch extends StatefulWidget {
#override
_NewStopWatchState createState() => new _NewStopWatchState();
}
class _NewStopWatchState extends State<NewStopWatch> {
static _NewStopWatchState stopwatch;
Stopwatch watch = new Stopwatch();
Timer timer;
bool startStop = true;
static String elapsedTime = '';
String duration;
updateTime(Timer timer) {
if (watch.isRunning) {
setState(() {
elapsedTime = transformMilliSeconds(watch.elapsedMilliseconds);
User.getCurrentUser().getCurrentActivity().setElapsedTime(elapsedTime);
});
}
}
#override
Widget build(BuildContext context) {
return new Container(
padding: EdgeInsets.all(20.0),
child: new Column(
children: <Widget>[
new Text(elapsedTime, style: new TextStyle(fontSize: 25.0)),
SizedBox(height: 20.0),
new Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
FloatingActionButton(
heroTag: "btn1",
backgroundColor: Colors.red,
onPressed: () => startOrStop(),
child: Icon(Icons.pause)),
SizedBox(width: 20.0),
FloatingActionButton(
heroTag: "btn2",
backgroundColor: Colors.green,
onPressed: () => completeActivity(),
child: Icon(Icons.check)),
],
)
],
));
}
void initState() {
super.initState();
WidgetsBinding.instance
.addPostFrameCallback((_) => startWatch());
}
startOrStop() {
if(startStop) {
startWatch();
} else {
stopWatch();
}
}
startWatch() {
setState(() {
startStop = false;
watch.start();
timer = Timer.periodic(Duration(milliseconds: 100), updateTime);
});
}
stopWatch() {
setState(() {
startStop = true;
watch.stop();
setTime();
});
}
resetWatch() {
watch.reset();
setTime();
}
setTime() {
var timeSoFar = watch.elapsedMilliseconds;
setState(() {
elapsedTime = transformMilliSeconds(timeSoFar);
});
}
completeActivity() { //do I call activity.stopActivity()?
return showDialog(
context: context,
builder: (context) => new AlertDialog(
title: new Text('Complete Activity?',
style: new TextStyle(color: Colors.black, fontSize: 20.0)),
actions: <Widget>[
new FlatButton(
onPressed: () {
duration = elapsedTime;
print("Current activitiy: ${User.getCurrentUser().getCurrentActivity()}");
// User.getCurrentUser().completeActivity();
User.getCurrentUser().addPastActivity(User.getCurrentUser().getCurrentActivity());
User.getCurrentUser().getCurrentActivity().setStatus(ActivityStatus.completed);
User.getCurrentUser().setCurrentActivity(null);
Navigator.push(context, MaterialPageRoute(builder: (context) => FrontPage()));
// Navigator.popUntil(context, ModalRoute.withName("/"),);
},
child:
new Text('Yes', style: new TextStyle(fontSize: 18.0)),
),
new FlatButton(
onPressed: () => Navigator.pop(context), // this line dismisses the dialog
child: new Text('No', style: new TextStyle(fontSize: 18.0)),
)
],
),
) ??
false;
}
transformMilliSeconds(int milliseconds) {
int hundreds = (milliseconds / 10).truncate();
int seconds = (hundreds / 100).truncate();
int minutes = (seconds / 60).truncate();
int hours = (minutes / 60).truncate();
String hoursStr = (hours % 60).toString().padLeft(2, '0');
String minutesStr = (minutes % 60).toString().padLeft(2, '0');
String secondsStr = (seconds % 60).toString().padLeft(2, '0');
return "$hoursStr:$minutesStr:$secondsStr";
}
static String getElapsedTime() {
return elapsedTime;
}
}
It is not 100% clear for me if this solution will work since you posted a lot of code but it is missing the part where you actually display the NewStopWatch but I hope this solution helps:
Pass the Stopwatch and the timer as arguments in the init method of your NewStopWath class and have them passed from your main page - the page where you want them displayed as well... i suppose it is the FrontPage.
Like so:
class FrontPage extends StatelessWidget {
Stopwatch watch = Stopwatch();
Timer timer = Timer.periodic(Duration(milliseconds: 100), updateTime);
//... code
//.. somewhere a NewStopWatch is created
return NewStopWatch(watch, timer);
}
//... code
class NewStopWatch extends StatefulWidget {
Stopwatch watch;
Timer timer;
NewStopWatch(this.watch, this.timer);
#override
_NewStopWatchState createState() => new _NewStopWatchState();
}
class _NewStopWatchState extends State<NewStopWatch> {
static _NewStopWatchState stopwatch;
bool startStop = true;
static String elapsedTime = '';
String duration;
Stopwatch get watch => widget.watch;
Timer get timer => widget.timer;
void initState() {
super.initState();
WidgetsBinding.instance
.addPostFrameCallback((_) => startWatch());
}
Be sure to check if you should remove timer = Timer.periodic(Duration(milliseconds: 100), updateTime); from startWatch()
maybe you don't need to have it passed as an argument
Related
I'm new to flutter and very new to riverpod. I've just been helped with some code to use a countdown clock that can then be viewed on multiple pages using Riverpod.
here is the Riverpod State Notifier.
final countDownControllerProvider = StateNotifierProvider.family
.autoDispose<CountdownController, Duration, Duration>(
(ref, initialDuration) {
return CountdownController(initialDuration);
});
class CountdownController extends StateNotifier<Duration> {
Timer? timer;
final Duration initialDuration;
CountdownController(this.initialDuration) : super(initialDuration) {
stopTimer();
}
void startTimer() {
timer = Timer.periodic(const Duration(seconds: 1), (timer) {
if (state == Duration.zero) {
timer.cancel();
} else {
if (mounted) {
state = state - const Duration(seconds: 1);
} else {
timer.cancel();
}
}
});
}
}
Currently, the input for the time to display on the countdown clock is inputted when you call CountdownController. (the class with startTimer function inside it).
the problem I'm having is if I want to call startTimer(), I need to reinput the time to display which is a problem if I'm stopping and starting the clock.
how would I move the time input from a parameter of the CountdownController class, into a function inside the class that I can then call on when needed so I don't have to set it when starting/stopping the clock?
and what would that code look like?
thanks so much
I didn't test it. If you need to save duration to state, consider making the state a data class.
EDIT: tested.
import 'dart:math';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter/material.dart';
import 'dart:async';
void main() {
runApp(const ProviderScope(child: App()));
}
final countDownControllerProvider =
StateNotifierProvider.autoDispose<CountdownController, Timer?>((ref) {
return CountdownController(ref);
});
final counterProvider = StateProvider((_) => 0);
final intervalProvider = StateProvider((_) => Duration(seconds: 1));
class CountdownController extends StateNotifier<Timer?> {
CountdownController(this.ref) : super(null);
final Ref ref;
void startTimer() {
state?.cancel();
state = Timer.periodic(ref.read(intervalProvider), (timer) {
ref.read(counterProvider.notifier).state++;
});
}
void stopTimer() {
state?.cancel();
}
void accelerate(double multiplier) {
final duration = ref.read(intervalProvider);
ref.read(intervalProvider.notifier).state = Duration(
milliseconds: (duration.inMilliseconds * (1 / multiplier)).floor(),
);
startTimer();
}
void speedUp() {
accelerate(sqrt2);
}
void speedDown() {
accelerate(1 / sqrt2);
}
}
class App extends ConsumerWidget {
const App();
#override
Widget build(BuildContext context, WidgetRef ref) {
final counter = ref.watch(counterProvider);
final controller = ref.watch(countDownControllerProvider.notifier);
final timer = ref.watch(countDownControllerProvider);
return MaterialApp(
home: Scaffold(
body: SafeArea(
child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
Text("$counter", style: Theme.of(context).textTheme.headlineLarge),
SizedBox(height: 24),
Row(children: [
SizedBox(width: 24),
Expanded(
child: ElevatedButton(
onPressed: controller.startTimer,
child: Text(timer == null ? "start" : "stop"),
),
),
SizedBox(width: 24),
Expanded(
child: ElevatedButton(
onPressed: controller.speedUp,
child: Text("+"),
),
),
SizedBox(width: 24),
Expanded(
child: ElevatedButton(
onPressed: controller.speedDown,
child: Text("-"),
),
),
SizedBox(width: 24),
]),
]),
),
),
);
}
}
Thanks for reading my question.
I wonder why
Unhandled Exception: setState() called after
dispose():_PomodoroState#42b6e(lifecycle state: defunct, not mounted)
is occured when I leave that page without pausing my timer function!
and also I want to know if it's fine to leave it like this.
But I think it's not good idea so I want some advices from you guys
This is my timer code.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:cat_app/provider/counts.dart';
class Pomodoro extends StatefulWidget {
#override
_PomodoroState createState() => _PomodoroState();
}
class _PomodoroState extends State<Pomodoro> {
double coinCount = 0;
Stopwatch watch = Stopwatch();
late Timer timer;
bool startStop = true;
String elapsedTime = '';
updateTime(Timer timer) {
if (watch.isRunning) {
setState(() {
print("startstop Inside=$startStop");
elapsedTime = transformMilliSeconds(watch.elapsedMilliseconds);
});
if (coinCount == 360000) {
context.read<Counts>().add(1);
coinCount = 0;
} else {
coinCount = coinCount + 10;
}
context.read<Times>().timeAdd(100);
}
}
restartTimer() {
updateTime(timer).cancel();
setState(() {
startStop = true;
watch.stop();
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
elevation: 0,
backgroundColor: Colors.white,
leading: IconButton(
icon: Icon(Icons.arrow_back, color: Colors.black),
onPressed: () => Navigator.of(context).pop(),
),
),
body: Container(
padding: EdgeInsets.all(20.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(elapsedTime,
style: TextStyle(fontFamily: 'Kitto', fontSize: 25.0)),
SizedBox(height: 20.0),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
IconButton(
splashColor: Colors.transparent,
highlightColor: Colors.transparent,
onPressed: () => startOrStop(),
icon: Image.asset('assets/button.png'),
iconSize: 50,
)
],
)
],
),
),
);
}
startOrStop() {
if (startStop) {
startWatch();
} else {
stopWatch();
}
}
startWatch() {
setState(() {
startStop = false;
watch.start();
timer = Timer.periodic(Duration(milliseconds: 100), updateTime);
});
}
stopWatch() {
setState(() {
startStop = true;
watch.stop();
setTime();
});
}
setTime() {
var timeSoFar = watch.elapsedMilliseconds;
setState(() {
elapsedTime = transformMilliSeconds(timeSoFar);
});
}
transformMilliSeconds(int milliseconds) {
int hundreds = (milliseconds / 10).truncate();
int seconds = (hundreds / 100).truncate();
int minutes = (seconds / 60).truncate();
int hours = (minutes / 60).truncate();
String hoursStr = (hours % 60).toString().padLeft(2, '0');
String minutesStr = (minutes % 60).toString().padLeft(2, '0');
String secondsStr = (seconds % 60).toString().padLeft(2, '0');
return "$hoursStr:$minutesStr:$secondsStr";
}
}
Can you tell me what is wrong?? I want to handle it!
dispose() is used to execute code when the screen is disposed. Equal to onDestroy() of Android.
Example:
#override
void dispose() {
anyController?.dispose();
timer.cancel();
super.dispose();
}
Add this in your Code
Thanks for reading my questions
I wonder again why the error shown below is printed when I navigate to timer page.
I want to know how can I fix it.
The following LateError was thrown while finalizing the widget tree:
LateInitializationError: Field 'timer' has not been initialized.
This is my timer code!
class Pomodoro extends StatefulWidget {
#override
_PomodoroState createState() => _PomodoroState();
}
class _PomodoroState extends State<Pomodoro> {
double coinCount = 0;
Stopwatch watch = Stopwatch();
late Timer timer;
bool startStop = true;
String elapsedTime = '';
#override
void dispose() {
timer.cancel();
super.dispose();
}
updateTime(Timer timer) {
if (watch.isRunning) {
setState(() {
elapsedTime = transformMilliSeconds(watch.elapsedMilliseconds);
});
if (coinCount == 360000) {
context.read<Counts>().add(1);
coinCount = 0;
} else {
coinCount = coinCount + 10;
}
context.read<Times>().timeAdd(100);
}
}
restartTimer() {
updateTime(timer).cancel();
setState(() {
startStop = true;
watch.stop();
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
elevation: 0,
backgroundColor: Colors.white,
leading: IconButton(
icon: Icon(Icons.arrow_back, color: Colors.black),
onPressed: () => Navigator.of(context).pop(),
),
),
body: Container(
padding: EdgeInsets.all(20.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(elapsedTime,
style: TextStyle(fontFamily: 'Kitto', fontSize: 25.0)),
SizedBox(height: 20.0),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
IconButton(
splashColor: Colors.transparent,
highlightColor: Colors.transparent,
onPressed: () => startOrStop(),
icon: Image.asset('assets/button.png'),
iconSize: 50,
)
],
)
],
),
),
);
}
startOrStop() {
if (startStop) {
startWatch();
} else {
stopWatch();
}
}
startWatch() {
setState(() {
startStop = false;
watch.start();
timer = Timer.periodic(Duration(milliseconds: 100), updateTime);
});
}
stopWatch() {
setState(() {
startStop = true;
watch.stop();
setTime();
});
}
setTime() {
var timeSoFar = watch.elapsedMilliseconds;
setState(() {
elapsedTime = transformMilliSeconds(timeSoFar);
});
}
transformMilliSeconds(int milliseconds) {
int hundreds = (milliseconds / 10).truncate();
int seconds = (hundreds / 100).truncate();
int minutes = (seconds / 60).truncate();
int hours = (minutes / 60).truncate();
String hoursStr = (hours % 60).toString().padLeft(2, '0');
String minutesStr = (minutes % 60).toString().padLeft(2, '0');
String secondsStr = (seconds % 60).toString().padLeft(2, '0');
return "$hoursStr:$minutesStr:$secondsStr";
}
}
If you know how to fix it, I hope you answered to my question.
I would be grateful for your answer.
And Have a nice day!
I don't think you should use late in that case. Adding late to field means that the field will be initialized when you use it for the first time.
use late when you strongly convinced that first time you use late field it will be initialized. And always remember that using late makes you code less safe and adds possibility of runtime errors.
You don't want a late variable, you want a nullable one. If you need to check if something is initialized, you should be using a nullable variable instead and your code is already set up to check for null.
Just change
late Timer timer;
to
Timer? timer;
I'm new with dart and flutter and currently I'm studying so please don't judge me for this (maybe) symple question.
I am trying to build a timer but all my code started from a stopwatch so I guess that's the first problem.
In few words, I'm trying to create a timer from 3 minutes that I can stop and start anytime I want (it suppose to be a referee tool) but my starting string is '03:00' and I can see that, so that's fine, I can't find any answer though on how the time can run from 03:00 minutes to 00:00.
Also, as you can see in my code I created a button to reset the time but it always goes back to 00:00 and not to 03:00.
Anyone who can help? I'm definitely missing something.
import 'dart:async';
import 'package:flutter/material.dart';
class NewStopWatch extends StatefulWidget {
#override
_NewStopWatchState createState() => _NewStopWatchState();
}
class _NewStopWatchState extends State<NewStopWatch> {
Stopwatch watch = Stopwatch();
Timer timer;
bool startStop = true;
IconData btnPlayStatus = Icons.play_arrow;
String elapsedTime = '03:00';
updateTime(Timer timer) {
if (watch.isRunning) {
setState(() {
elapsedTime = transformMilliSeconds(watch.elapsedMilliseconds);
});
}
}
#override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(20.0),
child: Column(
children: <Widget>[
Text(elapsedTime, style: TextStyle(fontSize: 60.0)),
SizedBox(height: 20.0),
Row(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
width: 100,
height: 50,
child: FloatingActionButton(
shape: ContinuousRectangleBorder(),
heroTag: "btn1",
backgroundColor: Colors.blueGrey,
onPressed: () => startOrStop(),
child: Icon(btnPlayStatus)),
),
SizedBox(width: 20.0),
Container(
width: 30,
height: 50,
child: FloatingActionButton(
shape: ContinuousRectangleBorder(),
heroTag: "btn2",
backgroundColor: Colors.blueGrey,
onPressed: () => resetWatch(), //resetWatch,
child: Icon(Icons.subdirectory_arrow_left)),
),
],
)
],
),
);
}
resetWatch() {
setState(() {
watch.reset();
setTime();
});
}
startOrStop() {
if(startStop) {
setState(() {
btnPlayStatus = Icons.pause;
});
startWatch();
} else {
setState(() {
btnPlayStatus = Icons.play_arrow;
});
stopWatch();
}
}
startWatch() {
setState(() {
startStop = false;
watch.start();
timer = Timer.periodic(Duration(milliseconds: 100), updateTime);
});
}
stopWatch() {
setState(() {
startStop = true;
watch.stop();
setTime();
});
}
setTime() {
var timeSoFar = watch.elapsedMilliseconds;
setState(() {
elapsedTime = transformMilliSeconds(timeSoFar);
});
}
transformMilliSeconds(int milliseconds) {
int hundreds = (milliseconds / 10).truncate();
int seconds = (hundreds / 100).truncate();
int minutes = (seconds / 60).truncate();
String minutesStr = (minutes % 60).toString().padLeft(2, '0');
String secondsStr = (seconds % 60).toString().padLeft(2, '0');
return "$minutesStr:$secondsStr";
}
}
First of all you need to think if you always want to start from 3 minutes; if so create a static field as follows:
static duration = new Duration(minutes:3);
Edit: I've refactored your code and made it working.
updateTimer(Timer t) {
if (watch.isRunning) {
setState(() {
Duration newDuration = _NewStopWatchState.duration -
new Duration(milliseconds: watch.elapsedMilliseconds);
elapsedTime = durationToMinutesAndSeconds(newDuration);
});
}
}
This is the updateTimer function.
Next, you didn't update your stopWatch() function to take care of the new changes so I changed it for you.
stopWatch() {
setState(() {
startStop = true;
watch.stop();
Duration newDuration = _NewStopWatchState.duration -
new Duration(milliseconds: watch.elapsedMilliseconds);
elapsedTime = durationToMinutesAndSeconds(newDuration);
});
}
I've also updated the resetWatch() function
resetWatch() {
setState(() {
watch.reset();
elapsedTime = durationToMinutesAndSeconds(_NewStopWatchState.duration);
});
}
I've also created an utility function to convert a duration to minutes and seconds.
String durationToMinutesAndSeconds(Duration d) {
return "${d.inMinutes.toString().padLeft(2, '0')}" +
":${d.inSeconds.remainder(60).toString().padLeft(2, '0')}";
}
I've tried it on my machine and the code is working, hope this time is working even on your side.
That's the modified code:
import 'dart:async';
import 'package:flutter/material.dart';
class NewStopWatch extends StatefulWidget {
#override
_NewStopWatchState createState() => _NewStopWatchState();
}
class _NewStopWatchState extends State<NewStopWatch> {
Stopwatch watch = Stopwatch();
Timer timer;
bool startStop = true;
static Duration duration = Duration(minutes:3);
IconData btnPlayStatus = Icons.play_arrow;
String elapsedTime = '';
updateTimer() {
if (watch.isRunning) {
setState(() {
elapsedTime = transformMilliSeconds(_NewStopWatchState.duration.inMilliseconds - watch.elapsedMilliseconds);
});
}
}
#override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(20.0),
child: Column(
children: <Widget>[
Text(elapsedTime),
SizedBox(height: 20.0),
Row(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
width: 100,
height: 50,
child: FloatingActionButton(
shape: ContinuousRectangleBorder(),
heroTag: "btn1",
backgroundColor: Colors.blueGrey,
onPressed: () => startOrStop(),
child: Icon(btnPlayStatus)),
),
SizedBox(width: 20.0),
Container(
width: 30,
height: 50,
child: FloatingActionButton(
shape: ContinuousRectangleBorder(),
heroTag: "btn2",
backgroundColor: Colors.blueGrey,
onPressed: () => resetWatch(), //resetWatch,
child: Icon(Icons.subdirectory_arrow_left)),
),
],
),
],
),
);
}
resetWatch() {
setState(() {
watch.reset();
setTime();
elapsedTime="${_NewStopWatchState.duration
.inMinutes}:${_NewStopWatchState.duration.inSeconds.remainder(60)}";
});
}
startOrStop() {
if(startStop) {
setState(() {
btnPlayStatus = Icons.pause;
});
startWatch();
} else {
setState(() {
btnPlayStatus = Icons.play_arrow;
});
stopWatch();
}
}
startWatch() {
setState(() {
startStop = false;
watch.start();
timer = Timer.periodic(Duration(milliseconds: 100), updateTimer());
});
}
stopWatch() {
setState(() {
startStop = true;
watch.stop();
setTime();
});
}
setTime() {
var timeSoFar = watch.elapsedMilliseconds;
setState(() {
elapsedTime = transformMilliSeconds(timeSoFar);
});
}
transformMilliSeconds(int milliseconds) {
int hundreds = (milliseconds / 10).truncate();
int seconds = (hundreds / 100).truncate();
int minutes = (seconds / 60).truncate();
String minutesStr = (minutes % 60).toString().padLeft(2, '0');
String secondsStr = (seconds % 60).toString().padLeft(2, '0');
return "$minutesStr:$secondsStr";
}
}
If you want a stop watch also with timer you can use my easy github project.
I hope this will will help some one. Thank you.
Here you can get my github repo
So from past few day I have been trying to get around this logical loop, and have tried everything I could and then finally decided to post it here (hence made my debut here). I am stuck, all the help is appreciated. Thanks !
Logic: In the app there are 2 container which displays White's and black's time, which is selected from a given five options -> 5,10,15,30,60, which then update's the time to display in those containers, used a provider package for this and everything else.
Now I have also added a Raised button named 'switch', which when pressed was supposed to:
Start's the white's countdown timer
Then if pressed again should stops white's timer and then start Black's countdown timer
Then if pressed again should stop the black's timer and then starts white's timer.
And repeat till the timer runs out and one out of the two is declared a winner.
Problem:
So with what I coded so far, when 'switch' is pressed it starts white's timer and if pressed again stops white's timer but it doesn't begins black's timer. I know its because of the way I have framed the if() conditions and also because I don't know how to stop the timer from outside. What I have done is to use a - bool checkTimerW and checkTimerB for each white and black, which I check in the if() condition to cancel the timer is based on it.
Code:
Provider -
import 'dart:async';
import 'package:flutter/foundation.dart';
class SettingsProvider extends ChangeNotifier {
int valueW = 0;
int valueB = 0;
// * with these booleans we will stop the timer.
bool checkTimerW = true;
bool checkTimerB = true;
String timeToDisplayW = ""; // for white
String timeToDisplayB = ""; // for black
bool switchT = false;
// this is called in the settings Modal Bottom Sheet
void changeValue(int valW, int valB) {
//? Changing the value in seconds
valueW = valW * 60;
valueB = valB * 60;
print(valueW);
timeToDisplayW = valueW.toString();
timeToDisplayB = valueB.toString();
notifyListeners();
}
void reset() {
started = true;
stopped = true;
checkTimerW = false;
checkTimerB = false;
notifyListeners();
}
void toggleSwitch() {
if (switchT == false) {
switchT = true;
print('true');
} else if (switchT == true) {
switchT = false;
print('false');
}
}
void switchTimer() {
if (switchT == false) {
// Starts white's timer
Timer.periodic(
Duration(seconds: 1),
(Timer t) {
if (valueW <= 1 || checkTimerW == false) {
t.cancel();
checkTimerW = true;
// TODO : Black Won
notifyListeners();
} else {
valueW = valueW - 1;
notifyListeners();
}
timeToDisplayW = valueW.toString();
notifyListeners();
},
);
// stops black's timer
checkTimerB = false;
toggleSwitch();
notifyListeners();
} else {
// Starts black's timer
Timer.periodic(
Duration(seconds: 1),
(Timer t) {
if (valueB <= 1 || checkTimerB == false) {
t.cancel();
checkTimerB = true;
// TODO : White won
notifyListeners();
} else {
valueB = valueB - 1;
notifyListeners();
}
timeToDisplayB = valueB.toString();
notifyListeners();
},
);
// stops white's timer
checkTimerW = false;
toggleSwitch();
notifyListeners();
}
}
}
Main.dart -
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import 'controller/countdown_controller.dart';
import 'widgets/blackButton.dart';
import 'widgets/bottom_sheet_design.dart';
import 'widgets/whiteButton.dart';
import 'providers/settings.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
]);
return ChangeNotifierProvider(
create: (ctx) => SettingsProvider(),
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.amber,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(),
),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
void settings(BuildContext ctx) {
showModalBottomSheet(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(10),
topRight: Radius.circular(10),
),
),
context: ctx,
builder: (_) => BottomSheetDesign(),
);
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
backgroundColor: Colors.grey[350],
body: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Expanded(
flex: 1,
child: Transform.rotate(
angle: pi / 1,
child: GestureDetector(
onTap: () {
Provider.of<SettingsProvider>(context, listen: false)
.switchTimer();
},
child: Container(
width: 80.0,
height: 500,
child: Center(
child: Transform.rotate(
angle: pi / 2,
child: Text('Switch',
style: Theme.of(context).textTheme.bodyText2),
),
),
decoration: BoxDecoration(
color: Colors.blueGrey,
),
),
),
),
),
VerticalDivider(),
Expanded(
flex: 4,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Row(
children: <Widget>[
// container that displays black's timer
BlackButton(),
Expanded(
child: Transform.rotate(
angle: pi / 2,
child: RaisedButton(
onPressed: () {
settings(context);
},
color: Colors.blue[300],
child: Text('Settings'),
),
),
),
],
),
SizedBox(
height: 20,
),
Row(
children: <Widget>[
// container that displays white's timer
WhiteButton(),
Expanded(
child: Transform.rotate(
angle: pi / 2,
child: RaisedButton(
onPressed: () {
Provider.of<SettingsProvider>(context, listen: false).reset();
},
color: Colors.red[600],
child: Text('Reset'),
),
),
),
],
),
],
),
),
],
),
),
);
}
}
I have coded this in the past. It might help you.
class DoubleTimer extends StatefulWidget {
#override
_DoubleTimerState createState() => _DoubleTimerState();
}
class _DoubleTimerState extends State<DoubleTimer> {
int timeToGoA = 50000;
int timeToGoB = 50000;
int state = 0; //0: waiting, 1: counting A, 2: counting B
DateTime timeStamp;
_DoubleTimerState() {
print("init");
}
#override
Widget build(BuildContext context) {
print(
"${DateTime.now().compareTo(DateTime.now().add(Duration(seconds: 1)))}");
return Row(
children: <Widget>[
if (state == 1)
ToTime(timeStamp.add(Duration(milliseconds: timeToGoA))),
FlatButton(
onPressed: () {
setState(() {
switch (state) {
case 0:
state = 1;
timeStamp = DateTime.now();
print("Running A");
break;
case 1:
state = -1;
timeToGoA -=
DateTime.now().difference(timeStamp).inMilliseconds;
timeStamp = DateTime.now();
print("A: $timeToGoA\nRunning B");
break;
case -1:
state = 1;
timeToGoB -=
DateTime.now().difference(timeStamp).inMilliseconds;
timeStamp = DateTime.now();
print("B: $timeToGoB\nRunning A");
break;
}
});
},
child: Text("switch"),
),
if (state == -1)
ToTime(timeStamp.add(Duration(milliseconds: timeToGoB))),
],
);
}
}
class ToTime extends StatelessWidget {
final DateTime timeStamp;
const ToTime(this.timeStamp, {Key key}) : super(key: key);
static final Map<String, int> _times = <String, int>{
'y': -const Duration(days: 365).inMilliseconds,
'm': -const Duration(days: 30).inMilliseconds,
'w': -const Duration(days: 7).inMilliseconds,
'd': -const Duration(days: 1).inMilliseconds,
'h': -const Duration(hours: 1).inMilliseconds,
'\'': -const Duration(minutes: 1).inMilliseconds,
'"': -const Duration(seconds: 1).inMilliseconds,
"ms": -1,
};
Stream<String> get relativeStream async* {
while (true) {
int duration = DateTime.now().difference(timeStamp).inMilliseconds;
String res = '';
int level = 0;
int levelSize;
for (MapEntry<String, int> time in _times.entries) {
int timeDelta = (duration / time.value).floor();
if (timeDelta > 0) {
levelSize = time.value;
res += '$timeDelta${time.key} ';
duration -= time.value * timeDelta;
level++;
}
if (level == 2) {
break;
}
}
levelSize ??= _times.values.reduce(min);
if (level > 0 && level < 2) {
List<int> _tempList =
_times.values.where((element) => (element < levelSize)).toList();
if (_tempList.isNotEmpty) levelSize = _tempList.reduce(max);
}
if (res.isEmpty) {
yield 'now';
} else {
res.substring(0, res.length - 2);
yield res;
}
// print('levelsize $levelSize sleep ${levelSize - duration}ms');
await Future.delayed(Duration(milliseconds: levelSize - duration));
}
}
#override
Widget build(BuildContext context) {
return StreamBuilder<String>(
stream: relativeStream,
builder: (context, snapshot) {
return Text(snapshot.data ?? '??');
});
}
}
I coded it withoud provider (Using only ValueNotifier) just to show you the logic
enum Player{White, Black}
class MyTimer extends ValueNotifier<int>{
Player _turn; //White starts
int _minutes;
int _whiteTime;
int _blackTime;
MyTimer(int time) :
_minutes = time * 60,
_whiteTime = time * 60,
_blackTime = time * 60,
_turn = Player.White, //White starts
super(time * 60 * 2);
bool get _isWhiteTurn => Player.White == _turn;
String get timeLeft{
if(value != 0){
//int time = _isWhiteTurn ? _whiteTime : _blackTime; //use this instead of playerTime if you want to display the time in seconds
Duration left = Duration(seconds: _isWhiteTurn ? _whiteTime : _blackTime);
String playerTime = left.toString();
playerTime = playerTime.substring(0, playerTime.lastIndexOf('.'));
return '${describeEnum(_turn)} turn time left : $playerTime';
}
else{
return '${describeEnum(_turn)} wins!'; //We have a winner
}
}
void switchPlayer() => _turn = _isWhiteTurn ? Player.Black : Player.White;
void reset([int time]){
if(time != null) _minutes = time * 60; //if you want to start with a different value
_turn = Player.White; //White starts
_whiteTime = _minutes; //reset time
_blackTime = _minutes; //reset time
value = 2*_minutes; //reset time
//twice as long because it counts the whole time of the match (the time of the 2 players)
}
void start(){
_initilizeTimer();
}
void _initilizeTimer(){
Timer.periodic(
Duration(seconds: 1),
(Timer t) {
if(_whiteTime == 0 || _blackTime == 0){
t.cancel();
switchPlayer(); //the time of one player ends, so it switch to the winner player
value = 0; //end the game
}
else{
_isWhiteTurn ? --_whiteTime : --_blackTime;
--value;
}
},
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final MyTimer clock = MyTimer(1);
#override
void initState(){
super.initState();
clock.start();
}
#override
void dispose(){
clock.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
backgroundColor: Colors.grey[350],
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ValueListenableBuilder<int>(
valueListenable: clock,
builder: (context, unit, _) =>
Text(clock.timeLeft ,style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500))
),
RaisedButton(
child: Text('Switch'),
onPressed: () => clock.switchPlayer(),
)
],
),
)
),
);
}
}
The idea is the same but what I want to show you is that you can use only one timer to do the whole logic and with some enum value (White and Black) change between both minutes.
The button change the turn of the player (switchPlayer method) and inside the timer you see that depending the turn of the player it reduces its time _isWhiteTurn ? --_whiteTime : --_blackTime;. As a ValueNotifier it updates only when value changes, but you can use your Provider with ChangeNotifier and update when you want (and it's better, because when changing player in my example I still have to wait for the second to end so the timer updates the text).
You can try change something like this with an enum to simplify the timer logic
bool get _isWhiteTurn => Player.White == _turn;
void startMatch() {
Timer.periodic(
Duration(seconds: 1),
(Timer t) {
if (valueW == 0 || valueB == 0) {
t.cancel();
if(valueW == 0) checkTimerB = true;
else checkTimerW = true
//it won the one whose time didn't end
} else {
_isWhiteTurn ? --valueW : --valueB;
}
timeToDisplayW = valueW.toString();
timeToDisplayB = valueB.toString();
//only one of them will change
notifyListeners();
},
);
}
void switchTimer(){
_turn = _isWhiteTurn ? Player.Black : Player.White;
notifyListeners();
}
That way you have only one timer the whole match that will cancel when one of the timer gets to 0 (or if someone loese, but thats other logic in some other Provider I guess)
UPDATE
You can change the timeLeft getter to something like this
String get timeLeft{
if(value != 0){
//int time = _isWhiteTurn ? _whiteTime : _blackTime; //use this instead of playerTime if you want to display the time in seconds
Duration white = Duration(seconds: _whiteTime);
Duration black = Duration(seconds: _blackTime);
String whiteTime = white.toString();
String blackTime = black.toString();
whiteTime = whiteTime.substring(0, whiteTime.lastIndexOf('.'));
blackTime = blackTime.substring(0, blackTime.lastIndexOf('.'));
return '''
${describeEnum(Player.White)} time left : $whiteTime
${describeEnum(Player.Black)} time left : $blackTime
''';
}
else{
return '${describeEnum(_turn)} wins!'; //We have a winner
}
}
That way it will return a String with both times and only the timer of the player in turn will change each second. But as I saig try this logic with ChangeNotifierProvider and it should work too, and you can consume it in differents parts of your widget tree