building a countdown clock inside a provider package change notifier - flutter

I have built a working countdown clock that displays the time remaining in the format, hours:minutes:senconds. inside a stateful widget. that uses a fullscreen inkwell to start and stop it.
What I want to do is transfer this code to a change notifier. (I already a change notifier setup ready to go called CountdownTimers) so i can see this countdown running from multiple pages on my app.
Here is the code for the working countdown clock in the stateful widget:
class ChessClock2 extends StatefulWidget {
const ChessClock2({Key? key}) : super(key: key);
#override
State<ChessClock2> createState() => _ChessClock2State();
}
class _ChessClock2State extends State<ChessClock2> {
static const countdownDuration = Duration(hours: 5, minutes: 10, seconds: 10);
Duration duration = Duration();
Timer? timer;
bool beenPressed = false;
#override
void initState() {
super.initState();
Reset();
}
void Reset() {
setState(() => duration = countdownDuration);
}
void AddTime() {
final minusSeconds = -1;
setState(() {
final seconds = duration.inSeconds + minusSeconds;
if (seconds < 0) {
timer?.cancel();
} else {
duration = Duration(seconds: seconds);
}
});
}
void StartTimer() {
timer = Timer.periodic(
Duration(seconds: 1),
(_) => AddTime(),
);
}
void StopTimer() {
setState(() => timer?.cancel());
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: InkWell(
onTap: () {
setState(() {
beenPressed = !beenPressed;
});
beenPressed ? StartTimer() : StopTimer();
},
child: Container(
width: double.infinity,
height: double.infinity,
decoration: BoxDecoration(
color: beenPressed ? kOrange : kBlueGrey900,
borderRadius: BorderRadius.circular(30),
),
child: Center(
child: TimeDisplay(),
),
),
),
),
);
}
Widget TimeDisplay() {
String twoDigits(int n) => n.toString().padLeft(2, '0');
final hours = twoDigits(
duration.inHours.remainder(60),
);
final minutes = twoDigits(
duration.inMinutes.remainder(60),
);
final seconds = twoDigits(
duration.inSeconds.remainder(60),
);
return Text(
'$hours:$minutes:$seconds',
style: TextStyle(fontSize: 50),
);
}
}
When I transfer the code over, I'm running into trouble because I can't use setState in the change notifier and I'm unsure how to translate the code to get it to work.
so far, by moving the individual variables over as well as the widget TimDisplay, I'm able to get the timer to display correctly from the change notifier but am not sure how to get it to work from the change notifier.
here is where I am now:
type hereclass ChessClock3 extends StatefulWidget {
const ChessClock3({Key? key}) : super(key: key);
#override
State<ChessClock3> createState() => _ChessClock3State();
}
class _ChessClock3State extends State<ChessClock3> {
#override
void initState() {
super.initState();
Reset();
}
void Reset() {
setState(() => duration = countdownDuration);
}
void AddTime() {
final minusSeconds = -1;
setState(() {
final seconds = duration.inSeconds + minusSeconds;
if (seconds < 0) {
timer?.cancel();
} else {
duration = Duration(seconds: seconds);
}
});
}
void StartTimer() {
timer = Timer.periodic(
Duration(seconds: 1),
(_) => AddTime(),
);
}
void StopTimer() {
setState(() => timer?.cancel());
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: InkWell(
onTap: () {
setState(() {
context.read<CountdownTimers>().BeenPressed();
});
context.read<CountdownTimers>().beenPressed
? StartTimer()
: StopTimer();
},
child: Container(
width: double.infinity,
height: double.infinity,
decoration: BoxDecoration(
color: context.watch<CountdownTimers>().beenPressed
? kKillTeamOrange
: kBlueGrey900,
borderRadius: BorderRadius.circular(30),
),
child: Center(
child: context.read<CountdownTimers>().TimeDisplay(),
),
),
),
),
);
}
}
class CountdownTimers with ChangeNotifier {
Duration _countdownDuration = Duration(hours: 5, minutes: 10, seconds: 10);
Duration _duration = Duration();
Timer? timer;
bool _beenPressed = false;
Duration get countdownDuration => _countdownDuration;
Duration get duration => _duration;
bool get beenPressed => _beenPressed;
void BeenPressed() {
_beenPressed = !_beenPressed;
}
Widget TimeDisplay() {
String twoDigits(int n) => n.toString().padLeft(2, '0');
final hours = twoDigits(
_duration.inHours.remainder(60),
);
final minutes = twoDigits(
_duration.inMinutes.remainder(60),
);
final seconds = twoDigits(
_duration.inSeconds.remainder(60),
);
return Text(
'$hours:$minutes:$seconds',
style: TextStyle(fontSize: 50),
);
}
}
If anyone can show me how to translate the code over It would be very much appreciated.
thanks so much!

I would use Flutter Riverpod instead like the code below. In Riverpod it's not recommended to use Change Notifier. Even in complexe application. To change the state of Change Notifier you must call notifyListeners.
import 'dart:async';
import 'package:flutter_riverpod/flutter_riverpod.dart';
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) {
startTimer();
}
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();
}
}
});
}
void stopTimer() {
timer?.cancel();
}
void resetTimer({required Duration initialDuration}) {
stopTimer();
state = initialDuration;
startTimer();
}
void addTime({required Duration duration}) {
state = state + duration;
}
void subtractTime({required Duration duration}) {
state = state - duration;
}
#override
void dispose() {
timer?.cancel();
super.dispose();
}
}
Then in the widget
Consumer(builder: (context, ref, _) {
return Text(
ref
.watch(countDownControllerProvider(
const Duration(
days: 25,
hours: 15,
minutes: 36,
seconds: 45)))
.formatDuration(),
style: context.theme.textTheme.bodyText1!
.copyWith(color: Colors.white),
);
})
And finally, don't hesitate to put your conversion logic Duration to String, into a extension
extension DurationExtensions on Duration {
String formatDuration() {
int seconds = inSeconds;
final days = seconds~/Duration.secondsPerDay;
seconds -= days*Duration.secondsPerDay;
final hours = seconds~/Duration.secondsPerHour;
seconds -= hours*Duration.secondsPerHour;
final minutes = seconds~/Duration.secondsPerMinute;
seconds -= minutes*Duration.secondsPerMinute;
final List<String> tokens = [];
if (days != 0) {
tokens.add('${days}d');
}
if (tokens.isNotEmpty || hours != 0){
tokens.add('${hours}h');
}
if (tokens.isNotEmpty || minutes != 0) {
tokens.add('${minutes}min');
}
if(tokens.isNotEmpty || seconds != 0) {
tokens.add('${seconds}s');
}
return tokens.join('');
}
}

The trick you are looking for is the function notifyListeners() of a ChangeNotifier. If you used context.watch() inside your widget to watch the ChangeNotifier, you will be updated and the widget is rebuild if necessary.
A short example might look like this
ConsumingWidget.dart
#override
Widget build(BuildContext context) {
var timeProvider = context.watch<TimeProvider>();
int counter = timeProvider.counter;
return Text("${counter} seconds have passed");
}
In this case, you would need to provide an instance of TimeProvider above your main widget in the widget tree via ChangeNotifierProvider.
You can then let the widget rebuild (if .counter changed) via notifyListeners()
ParentWidget.dart
#override
Widget build(BuildContext context) {
ChangeNotifierProvider(
create: (_) => TimeProvider(),
child: ConsumingWidget()
);
}
Provider.dart
class TimeProvider extends ChangeNotifier {
Timer? timer;
final Duration initialDuration;
int counter = 0;
CountdownController(this.initialDuration) : super(initialDuration) {
startTimer();
}
void startTimer() {
timer = Timer.periodic(const Duration(seconds: 1), (timer) {
counter += 1; // Change some data based on the timer. Just an example here
notifyListeners(); // Then update listeners!
});
}
#override
void dispose() {
timer?.cancel();
super.dispose();
}
This is the built-in solution of the Provider package. Starting from this basic pattern, you can then look into more sophisticated state management solutions like Riverpod, Bloc etc.

Related

Flutter - Provider - Flutter_Countdown_Timer - CountdownTimerController - FlutterError (setState() or markNeedsBuild() called during build

I am developing an app using Flutter, managing state with provider, and I keep running into the error:
"FlutterError (setState() or markNeedsBuild() called during build. This _InheritedProviderScope<CountdownTimerController?> widget cannot
be marked as needing to build because the framework is already in the
process of building widgets. A widget can be marked as needing to be
built during the build phase only if one of its ancestors is currently
building. This exception is allowed because the framework builds
parent widgets before children, which means a dirty descendant will
always be built. Otherwise, the framework might not visit this widget
during this build phase."
I'm using the package flutter countdown timer as a widget, but I am trying to place the associated CountdownTimerController as a provider higher in the widget tree so I can access its state from other widgets. Whenever I attempt to define a countdown timer widget using provider.watch, I receive the above error at compile time. Oddly, the widget will still build, but the error throws every time I try to add a new instance of the widget into a listview with an action button.
Here is my provider definition within a listview builder:
body: ListView.separated(
padding: const EdgeInsets.fromLTRB(10, 5, 10, 5),
itemCount: challenges.length,
itemBuilder: (BuildContext context, int index) {
Challenge challenge = challenges[index];
return MultiProvider(providers: [
ChangeNotifierProvider(
create: (context) => CountdownTimerController(
endTime: challenge.nextCheckIn.millisecondsSinceEpoch))
], child: ChallengeCard(challenge));
},
Here is my custom ChallengeCard widget, where I am attempting to access the controller with context.watch:
class ChallengeCard extends StatefulWidget {
final Challenge challenge;
const ChallengeCard(this.challenge, {Key? key}) : super(key: key);
#override
State<StatefulWidget> createState() => _ChallengeCardState();
}
class _ChallengeCardState extends State<ChallengeCard> {
#override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.fromLTRB(10, 5, 10, 5),
child: Column(
children: [
Row(
children: [
Expanded(
flex: 3,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.challenge.passage.fullRef,
style: TextStyles.title,
),
Row(
children: [
const Text(
"Next Check In: ",
style: TextStyles.subtitle,
),
CountdownTimer(
textStyle: TextStyles.subtitle,
endWidget: const Text(
"Now!",
style: TextStyles.subtitle,
),
controller:
context.watch<CountdownTimerController>(),
onEnd: () {
print("Timer Finished!");
},
),
],
),
],
),
),
Expanded(child: DetailsButton(widget.challenge)),
Expanded(child: CheckInButton(widget.challenge)),
],
),
CounterProgressIndicator(widget.challenge)
],
),
),
);
}
}
Here is the full flutter_countdown_provider class:
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_countdown_timer/index.dart';
typedef CountdownTimerWidgetBuilder = Widget Function(
BuildContext context, CurrentRemainingTime? time);
/// A Countdown.
class CountdownTimer extends StatefulWidget {
///Widget displayed after the countdown
final Widget endWidget;
///Used to customize the countdown style widget
final CountdownTimerWidgetBuilder? widgetBuilder;
///Countdown controller, can end the countdown event early
final CountdownTimerController? controller;
///Countdown text style
final TextStyle? textStyle;
///Event called after the countdown ends
final VoidCallback? onEnd;
///The end time of the countdown.
final int? endTime;
CountdownTimer({
Key? key,
this.endWidget = const Center(
child: Text('The current time has expired'),
),
this.widgetBuilder,
this.controller,
this.textStyle,
this.endTime,
this.onEnd,
}) : assert(endTime != null || controller != null),
super(key: key);
#override
_CountDownState createState() => _CountDownState();
}
class _CountDownState extends State<CountdownTimer> {
late CountdownTimerController controller;
CurrentRemainingTime? get currentRemainingTime =>
controller.currentRemainingTime;
Widget get endWidget => widget.endWidget;
CountdownTimerWidgetBuilder get widgetBuilder =>
widget.widgetBuilder ?? builderCountdownTimer;
TextStyle? get textStyle => widget.textStyle;
#override
void initState() {
super.initState();
initController();
}
///Generate countdown controller.
initController() {
controller = widget.controller ??
CountdownTimerController(endTime: widget.endTime!, onEnd: widget.onEnd);
if (controller.isRunning == false) {
controller.start();
}
controller.addListener(() {
if (mounted) {
setState(() {});
}
});
}
#override
void didUpdateWidget(CountdownTimer oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.endTime != widget.endTime || widget.controller != oldWidget.controller) {
controller.dispose();
initController();
}
}
#override
Widget build(BuildContext context) {
return widgetBuilder(context, currentRemainingTime);
}
Widget builderCountdownTimer(
BuildContext context, CurrentRemainingTime? time) {
if (time == null) {
return endWidget;
}
String value = '';
if (time.days != null) {
var days = time.days!;
value = '$value$days days ';
}
var hours = _getNumberAddZero(time.hours ?? 0);
value = '$value$hours : ';
var min = _getNumberAddZero(time.min ?? 0);
value = '$value$min : ';
var sec = _getNumberAddZero(time.sec ?? 0);
value = '$value$sec';
return Text(
value,
style: textStyle,
);
}
/// 1 -> 01
String _getNumberAddZero(int number) {
if (number < 10) {
return "0" + number.toString();
}
return number.toString();
}
}
Here is the full CountdownTimerController class:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_countdown_timer/index.dart';
///Countdown timer controller.
class CountdownTimerController extends ChangeNotifier {
CountdownTimerController(
{required int endTime, this.onEnd, TickerProvider? vsync})
: this._endTime = endTime {
if (vsync != null) {
this._animationController =
AnimationController(vsync: vsync, duration: Duration(seconds: 1));
}
}
///Event called after the countdown ends
final VoidCallback? onEnd;
///The end time of the countdown.
int _endTime;
///Is the countdown running.
bool _isRunning = false;
///Countdown remaining time.
CurrentRemainingTime? _currentRemainingTime;
///Countdown timer.
Timer? _countdownTimer;
///Intervals.
Duration intervals = const Duration(seconds: 1);
///Seconds in a day
int _daySecond = 60 * 60 * 24;
///Seconds in an hour
int _hourSecond = 60 * 60;
///Seconds in a minute
int _minuteSecond = 60;
bool get isRunning => _isRunning;
set endTime(int endTime) => _endTime = endTime;
///Get the current remaining time
CurrentRemainingTime? get currentRemainingTime => _currentRemainingTime;
AnimationController? _animationController;
///Start countdown
start() {
disposeTimer();
_isRunning = true;
_countdownPeriodicEvent();
if (_isRunning) {
_countdownTimer = Timer.periodic(intervals, (timer) {
_countdownPeriodicEvent();
});
}
}
///Check if the countdown is over and issue a notification.
_countdownPeriodicEvent() {
_currentRemainingTime = _calculateCurrentRemainingTime();
_animationController?.reverse(from: 1);
notifyListeners();
if (_currentRemainingTime == null) {
onEnd?.call();
disposeTimer();
}
}
///Calculate current remaining time.
CurrentRemainingTime? _calculateCurrentRemainingTime() {
int remainingTimeStamp =
((_endTime - DateTime.now().millisecondsSinceEpoch) / 1000).floor();
if (remainingTimeStamp <= 0) {
return null;
}
int? days, hours, min, sec;
///Calculate the number of days remaining.
if (remainingTimeStamp >= _daySecond) {
days = (remainingTimeStamp / _daySecond).floor();
remainingTimeStamp -= days * _daySecond;
}
///Calculate remaining hours.
if (remainingTimeStamp >= _hourSecond) {
hours = (remainingTimeStamp / _hourSecond).floor();
remainingTimeStamp -= hours * _hourSecond;
} else if (days != null) {
hours = 0;
}
///Calculate remaining minutes.
if (remainingTimeStamp >= _minuteSecond) {
min = (remainingTimeStamp / _minuteSecond).floor();
remainingTimeStamp -= min * _minuteSecond;
} else if (hours != null) {
min = 0;
}
///Calculate remaining second.
sec = remainingTimeStamp.toInt();
return CurrentRemainingTime(
days: days,
hours: hours,
min: min,
sec: sec,
milliseconds: _animationController?.view);
}
disposeTimer() {
_isRunning = false;
_countdownTimer?.cancel();
_countdownTimer = null;
}
#override
void dispose() {
disposeTimer();
_animationController?.dispose();
super.dispose();
}
}

how to delay data view 3 second in flutter

this my code
Container(
child: Column(
children: jobProvider.jobs
.map(
(job) => JobTile(job),
)
.toList(),
),
)
how to load this data delay for 3 seconds ? here I display the listview data, before displaying it I want to display, the
return Container(
child: ProfileShimmer(),
);
Should be as easy as this:
import 'package:flutter/material.dart';
class PageWithDelayedView extends StatefulWidget {
const PageWithDelayedView({Key? key}) : super(key: key);
#override
_PageWithDelayedViewState createState() => _PageWithDelayedViewState();
}
class _PageWithDelayedViewState extends State<PageWithDelayedView> {
bool _initialized = false;
#override
void initState() {
super.initState();
// Schedule function call after the widget is ready to display
WidgetsBinding.instance?.addPostFrameCallback((_) {
_initialize();
});
}
void _initialize() {
Future<void>.delayed(const Duration(seconds: 3), () {
if (mounted) { // Check that the widget is still mounted
setState(() {
_initialized = true;
});
}
});
}
#override
Widget build(BuildContext context) {
if (!_initialized) {
return Text('Hold on a bit');
}
return Text('Yay, I\'m ready!');
}
}

How to change color with certain second on timer?

Objective : I want to have different timing duration for each color
Blue = 2 seconds
Red = 5 seconds
Green = 1 seconds
Yellow = 8 seconds
I got the code from here How to render child without parent when using setState?,
import 'dart:async';
import 'package:flutter/material.dart';
import 'dart:math';
void main() {
runApp(MaterialApp(debugShowCheckedModeBanner: false, home: Sample()));
}
class Sample extends StatelessWidget {
#override
Widget build(BuildContext context) {
print("Parent Widget rebuild");
return SafeArea(
child: Column(
children: [
Container(
height: 50,
width: 50,
color: Colors.red,
),
SizedBox(height: 20),
ChangingColor(),
],
),
);
}
}
class ChangingColor extends StatefulWidget {
#override
_ChangingColorState createState() => _ChangingColorState();
}
class _ChangingColorState extends State<ChangingColor> {
Timer _timer;
Color _color;
List<Color> arrColors = [Colors.blue, Colors.red, Colors.green, Colors.yellow] ;
int _pos =0;
List<int> arrSeconds = [2,5,1,8]; //here I set array for seconds
#override
void initState() {
for (var e in arrSeconds) {
_timer = Timer.periodic(Duration(seconds: e), (Timer t) { //I want the array looping using different seconds
print("seconds: $e");
setState(() {
_pos = (_pos + 1) % arrColors.length;
});
});}
super.initState();
}
#override
Widget build(BuildContext context) {
print("Child Widget rebuild");
return Container(
height: 50,
width: 50,
color: arrColors[_pos],
);
}
#override
void dispose() {
_timer.cancel();
_timer = null;
super.dispose();
}
}
The problem is seems like the timer did not finish yet but the color keep changing. Should I use async?
Please try this:
import 'dart:async';
import 'package:flutter/material.dart';
import 'dart:math';
void main() {
runApp(MaterialApp(debugShowCheckedModeBanner: false, home: Sample()));
}
class Sample extends StatelessWidget {
#override
Widget build(BuildContext context) {
print("Parent Widget rebuild");
return SafeArea(
child: Column(
children: [
Container(
height: 50,
width: 50,
color: Colors.red,
),
SizedBox(height: 20),
ChangingColor(),
],
),
);
}
}
class ChangingColor extends StatefulWidget {
#override
_ChangingColorState createState() => _ChangingColorState();
}
class _ChangingColorState extends State<ChangingColor> {
Timer _timer;
Color _color;
List<Color> arrColors = [Colors.blue, Colors.red, Colors.green, Colors.yellow] ;
int _pos =0;
List<int> arrSeconds = [2,5,1,8]; //here I set array for seconds
#override
void initState() {
super.initState();
doSomething();
}
#override
Widget build(BuildContext context) {
print("Child Widget rebuild");
return Container(
height: 50,
width: 50,
color: arrColors[_pos],
);
}
#override
void dispose() {
_timer.cancel();
_timer = null;
super.dispose();
}
Future<void> doSomething() async {
var second=0;
var colorCount=1;
var count=0;
var totalList=[];
for(var e in arrSeconds){
count=count+e;
totalList.add(count);
}
print(totalList);
_timer = Timer.periodic(Duration(seconds: 1), (Timer t) {
if(totalList[totalList.length-1]==second){
_timer.cancel();
setState(() {
_pos=0;
});
doSomething();
}else{
if(totalList.contains(second)){
setState(() {
_pos =colorCount;
colorCount++;
print(second);
});
}
}
second++;
});
}
}

Flutter - Restart CountdownTimer

I have a countdown timer (5 mins). I need something to restart this countdown and start from the beginning.
I try to change state of countdown variable but doesn´t works, stops and restarting from the last number of counter.
My code:
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:quiver/async.dart';
class ProgressIndicatorDemo extends StatefulWidget {
#override
_ProgressIndicatorDemoState createState() =>
new _ProgressIndicatorDemoState();
}
class _ProgressIndicatorDemoState extends State<ProgressIndicatorDemo>
with SingleTickerProviderStateMixin {
AnimationController controller;
Animation<double> animation;
var countdown;
int actual;
#override
void initState() {
super.initState();
animationStart();
startTimer();
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
void animationStart() {
controller =
AnimationController(duration: Duration(minutes: 5), vsync: this);
animation = Tween(begin: 0.0, end: 1.0).animate(controller)
..addListener(() {
setState(() {});
});
controller.forward();
}
void startTimer() {
CountdownTimer countDownTimer = new CountdownTimer(
new Duration(minutes: 5),
new Duration(seconds: 1),
);
countdown = countDownTimer.listen(null);
countdown.onData((duration) {
setState(() {
actual = 300 - duration.elapsed.inSeconds;
});
});
countdown.onDone(() {
countdown.cancel();
});
}
#override
Widget build(BuildContext context) {
return new Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
animation.value != 1.0
? CircularProgressIndicator(
semanticsLabel:
(animation.value * 100.0).toStringAsFixed(1).toString() +
'%',
semanticsValue:
(animation.value * 100.0).toStringAsFixed(1).toString() +
'%',
backgroundColor: Colors.black.withOpacity(0.25),
valueColor:
new AlwaysStoppedAnimation<Color>(Colors.green[700]),
value: animation.value)
: Icon(Icons.info_outline),
actual != null
? Text(
"Tiempo:" +
DateFormat.ms()
.format(DateTime.fromMillisecondsSinceEpoch(
actual * 1000))
.toString(),
textAlign: TextAlign.center,
)
: Container(),
FlatButton(
child: Text('Reset ' + actual.toString()), onPressed: () {}),
],
),
);
}
}
So the question is: How can i restart from the beggining the countdown?
You could use a cleaner solution by using the RestartableTimer
import 'package:async/async.dart';
RestartableTimer _timer = new RestartableTimer(_timerDuration, _startNewPage);
Then you can restart your countdown by simply calling _timer.reset();
Hope it helps.
Try cancel the timer first and start the timer again :
void restartTimer() {
countDownTimer.cancel();
startTimer();
}
try to declare your Timer at the top and whenever you want to restart it call this method.
void restartTimer() {
countDownTimer = new CountdownTimer(
new Duration(minutes: 5),
new Duration(seconds: 1),
);
don't forget to declare the variable

Animate a color periodically in flutter

I have a container widget that I try to animate from Colors.blue to Colors.blueGrey and back periodically every 2 seconds.
How can I most easily tackle this in Flutter?
You can use an infinite while loop, don't think this is the best way of doing this, but it gets the job done.
I have a Color Changing class
class ColorChanger extends StatefulWidget {
#override
_ColorChangerState createState() => _ColorChangerState();
}
class _ColorChangerState extends State<ColorChanger>
with SingleTickerProviderStateMixin {
AnimationController _animationController;
Animation _colorTween;
#override
void initState() {
_animationController = AnimationController(
vsync: this, duration: Duration(milliseconds: 1999));
_colorTween = ColorTween(begin: Colors.blue, end: Colors.blueGrey)
.animate(_animationController);
changeColors();
super.initState();
}
Future changeColors() async {
while (true) {
await new Future.delayed(const Duration(seconds: 2), () {
if (_animationController.status == AnimationStatus.completed) {
_animationController.reverse();
} else {
_animationController.forward();
}
});
}
}
#override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _colorTween,
builder: (context, child) => Container(
color: _colorTween.value,
),
);
}
}
This is a rough example, but should lead you in the right direction.
Please see ColorTween Class
I would suggest using the AnimatedContainer. This widget allows you to build it with a particular atribute like color and when you rebuild it with a different value it performs linear interpolation between those values.
#override
Widget build(BuildContext context) {
return AnimatedContainer(
width: 100,
height: 100,
duration: _animationDuration,
color: _color,
);
}
Then you just have to rebuild the widget with a different color:
void _changeColor() {
final newColor = _color == Colors.blue ? Colors.blueGrey : Colors.blue;
setState(() {
_color = newColor;
});
}
The make it periodically I would use a timer class:
_timer = Timer.periodic(_animationDuration, (timer) => _changeColor());
The whole code:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
class AnimationTest extends StatefulWidget {
#override
_AnimationTestState createState() => _AnimationTestState();
}
class _AnimationTestState extends State<AnimationTest> {
final _animationDuration = Duration(seconds: 2);
Timer _timer;
Color _color;
#override
void initState() {
super.initState();
_timer = Timer.periodic(_animationDuration, (timer) => _changeColor());
_color = Colors.blue;
}
void _changeColor() {
final newColor = _color == Colors.blue ? Colors.blueGrey : Colors.blue;
setState(() {
_color = newColor;
});
}
#override
Widget build(BuildContext context) {
return AnimatedContainer(
width: 100,
height: 100,
duration: _animationDuration,
color: _color,
);
}
#override
void dispose() {
super.dispose();
_timer.cancel();
}
}