I have been playing with Dart Timer Class that I got it to work in its very basic form, however I am stuck trying to add a pause function to it. I have looked into their documentations, but their is not much about their Timer class...
Is there any way I can pause and resume the timer/countdown on click? This is what I have achieved so far:
import 'package:flutter/material.dart';
import 'dart:async';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Timer _timer;
int _start = 10;
void startTimer() {
const oneSec = const Duration(seconds: 1);
_timer = new Timer.periodic(
oneSec,
(Timer timer) => setState(() {
if (_start < 1) {
timer.cancel();
} else {
_start = _start - 1;
}
}));
}
Widget build(BuildContext context) {
return new Scaffold(
appBar: AppBar(title: Text("Timer test")),
body: Column(
children: <Widget>[
RaisedButton(
onPressed: () {
startTimer();
},
child: Text("start"),
),
Text("$_start")
],
));
}
}
I just uploaded this package to implement exactly this: https://pub.dev/packages/pausable_timer
// It starts paused
final timer = PausableTimer(Duration(seconds: 1), () => print('Fired!'));
timer.start();
timer.pause();
timer.start();
There is also an issue requesting the feature in Dart itself, but it doesn't look like it will be added soon.
There is no built-in pause function, since the Timer class is mainly intended for scheduling blocks of code for later.
What is your use case? The Stopwatch class has pause and resume funtionality.
Working flutter example with pause and vibrations is below (source)
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:vibration/vibration.dart';
void main() => runApp(MaterialApp(home: CountdownCard()));
class CountdownCard extends StatefulWidget {
// This widget is the root of your application.
#override
_CountdownCardState createState() => _CountdownCardState();
}
class _CountdownCardState extends State<CountdownCard> {
Timer _timer;
int _start = 0;
bool _vibrationActive = false;
void startTimer(int timerDuration) {
if (_timer != null) {
_timer.cancel();
cancelVibrate();
}
setState(() {
_start = timerDuration;
});
const oneSec = const Duration(seconds: 1);
print('test');
_timer = new Timer.periodic(
oneSec,
(Timer timer) => setState(
() {
if (_start < 1) {
timer.cancel();
print('alarm');
vibrate();
} else {
_start = _start - 1;
}
},
),
);
}
void cancelVibrate() {
_vibrationActive = false;
Vibration.cancel();
}
void vibrate() async {
_vibrationActive = true;
if (await Vibration.hasVibrator()) {
while (_vibrationActive) {
Vibration.vibrate(duration: 1000);
await Future.delayed(Duration(seconds: 2));
}
}
}
void pauseTimer() {
if (_timer != null) _timer.cancel();
}
void unpauseTimer() => startTimer(_start);
#override
void dispose() {
_timer.cancel();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Countdown'),
),
body: Wrap(children: <Widget>[
Column(
children: <Widget>[
RaisedButton(
onPressed: () {
startTimer(10);
},
child: Text("start"),
),
Text("$_start"),
RaisedButton(
onPressed: () {
pauseTimer();
},
child: Text("pause"),
),
RaisedButton(
onPressed: () {
unpauseTimer();
},
child: Text("unpause"),
),
RaisedButton(
onPressed: () {
cancelVibrate();
},
child: Text("stop alarm"),
),
],
),
]));
}
}
variables we define
start timer on page opening.
timer off when page is closed.
if the screen is touched, the variable duration becomes true and the timer stops counting down
Timer? _timer;
int _start = 200;
bool duration = false;
Duration oneSec = const Duration(milliseconds: 10);
#override
void initState() {
// TODO: implement initState
super.initState();
startTimer();
}
#override
void dispose() {
_timer!.cancel();
super.dispose();
}
```
GestureDetector(
onTap: () {},
onTapDown: (e) {
setState(() {
duration = true;
});
},
onHorizontalDragEnd: (e) {
Navigator.pop(context);
},
onTapUp: (e) {
setState(() {
duration = false;
});
},
child:Text("demo page")}
void startTimer() {
_timer = new Timer.periodic(
oneSec,
(Timer timer) {
if (_start == 0) {
setState(() {
timer.cancel();
Navigator.pop(context);
});
} else {
setState(() {
duration == false ? _start-- : null;
});
}
},
);
}
}
Related
I am following this 5 minutes video to set up an audio recorder in Flutter.
When I click the ElevatedButton to start recording the audio, it does change between play and stop, and an audio file is created, but the snapshot.hasData is always false, so the Text stays 00:00 during recording. The only information I found is about setSubscriptionDuration, which I did set. I also tried flutter clean, etc. What else can it be?
I'm using Flutter 3.3.8, on macOS, flutter_sound: ^9.1.9. I'm running the app on a real iPhone XR with flutter run
I am new to flutter. I really appreciate any help you can provide!
I have
StreamBuilder
StreamBuilder<RecordingDisposition>(
stream: recorder.onProgress,
builder: (context, snapshot) {
print('snapshot.hasData :${snapshot.hasData}');
final duration =
snapshot.hasData ? snapshot.data!.duration : Duration.zero;
print('duration :$duration');
String twoDigits(int n) => n.toString().padLeft(2, '0');
final twoDigitMinutes = twoDigits(duration.inMinutes.remainder(60));
final twoDigitSeconds = twoDigits(duration.inSeconds.remainder(60));
return Text(
'$twoDigitMinutes:$twoDigitSeconds',
style: const TextStyle(
fontSize: 20,
),
);
},
),
ElevatedButton
ElevatedButton(
child: Icon(
recorder.isRecording ? Icons.stop : Icons.mic,
size: 20,
),
onPressed: () async {
if (recorder.isRecording) {
await stop();
} else {
await record();
}
setState(() {});
},
)
Initialize the recorder properly
final recorder = FlutterSoundRecorder();
Future<void> initRecorder() async {
final status = await Permission.microphone.request();
if (status != PermissionStatus.granted) {
throw 'Microphone permission not granted';
}
await recorder.openRecorder();
isRecorderReady = true;
recorder.setSubscriptionDuration(
const Duration(
microseconds: 100,
),
);
}
#override
void initState() {
super.initState();
initRecorder();
}
This is what it looks like so far:
So, I found a solution, but the StreamBuilder question is not answered. Instead of using StreamBuilder, create a stateful TimerWidget that's initialized by a ValueNotifier.
import 'dart:async';
import 'package:flutter/material.dart';
enum Time { start, pause, reset }
class TimerController extends ValueNotifier<Time> {
TimerController({Time time = Time.reset}) : super(time);
void startTimer() => value = Time.start;
void pauseTimer() => value = Time.pause;
void resetTimer() => value = Time.reset;
}
class TimerWidget extends StatefulWidget {
final TimerController controller;
const TimerWidget({
Key? key,
required this.controller,
}) : super(key: key);
#override
_TimerWidgetState createState() => _TimerWidgetState();
}
class _TimerWidgetState extends State<TimerWidget> {
Duration duration = const Duration();
Timer? timer;
#override
void initState() {
super.initState();
widget.controller.addListener(() {
switch (widget.controller.value) {
case Time.start:
startTimer();
break;
case Time.pause:
stopTimer();
break;
case Time.reset:
reset();
stopTimer();
break;
}
});
}
void reset() => setState(() => duration = const Duration());
void addTime() {
const addSeconds = 1;
setState(() {
final seconds = duration.inSeconds + addSeconds;
if (seconds < 0) {
timer?.cancel();
} else {
duration = Duration(seconds: seconds);
}
});
}
void startTimer({bool resets = true}) {
if (!mounted) return;
timer = Timer.periodic(const Duration(seconds: 1), (_) => addTime());
}
void stopTimer() {
if (!mounted) return;
setState(() => timer?.cancel());
}
#override
Widget build(BuildContext context) => Center(child: buildTime());
Widget buildTime() {
String twoDigits(int n) => n.toString().padLeft(2, "0");
final twoDigitMinutes = twoDigits(duration.inMinutes.remainder(60));
final twoDigitSeconds = twoDigits(duration.inSeconds.remainder(60));
return Text(
'$twoDigitMinutes:$twoDigitSeconds',
style: const TextStyle(
fontSize: 80,
fontWeight: FontWeight.bold,
),
);
}
}
change the microseconds: 100, to millisecond:100 in recorder.setSubscriptionDuration
recorder.setSubscriptionDuration(
const Duration(milliseconds: 100),
);
like this
Okay. So I'm going to show some code, and I honestly don't know WHY it doesn't work. I just feel like I'm out of my depth, and this is very frustrating.
Now this is NOT the program I'm actually working on, but a super-simple example program that should show the issue I'm having. Please do NOT ask me to put all of these things into or inside a single function or class, as that is NOT an option with my real program, so it wouldn't solve my actual issue.
so in my main.dart I have the following.
import 'package:flutter/cupertino.dart';
import 'dart:async';
import './page2.dart';
void main() => runApp(MyApp());
Page2 myPage = new Page2();
PageState myState = myPage.createState();
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return CupertinoApp(
title: 'Splash Test',
theme: CupertinoThemeData(
primaryColor: Color.fromARGB(255, 0, 0, 255),
),
home: MyHomePage(title: 'Splash Test Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool textBool = false;
void changeTest(dynamic function, context) async {
Timer.periodic(Duration (seconds: 2), (Timer t) {
myState.changeText();
counter++;
if (counter >= 10) {
t.cancel();
}
},);
Navigator.push(context, CupertinoPageRoute(builder: (context) => myPage));
}
#override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
child: Center(
child: CupertinoButton(
child: Text('To Splash'),
onPressed: () => changeTest(myState.changeText, context),
),
),
);
}
}
and in a second Dart file I have
import 'package:flutter/material.dart';
import 'package:flutter/semantics.dart';
import './main.dart';
class Page2 extends StatefulWidget {
#override
State<StatefulWidget> createState() => new PageState();
}
class PageState extends State<Page2> {
bool textChanger = false;
bool firstText = true;
Text myText() {
if (textChanger) {
Text text1 = new Text('Text One',
style: TextStyle(color: Color.fromARGB(255, 0, 0, 0)));
return text1;
} else {
Text text1 = new Text('Text Two',
style: TextStyle(color: Color.fromARGB(255, 0, 0, 0)));
return text1;
}
}
void changeText() {
if (!firstText) {
if (textChanger) {
print('Change One');
textChanger = false;
setState(() {
});
} else {
print('Change Two');
textChanger = true;
setState(() {
});
}
} else {
firstText = false;
}
}
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new Container(
child: Center(
child: myText()
)
),);
}
}
Now what this program does is switch to the second page, and then stalls, and nothing happens. The timer IS getting called (I can see this through the print-screen function) And I can see that the text SHOULD be changing, as the bools are being altered properly to do so.
Expected functionality: I should be able to call the instance of the second page, and the functions on it, from my main app, and make changes to the text on that second page.
In my real app (Far more complicated, I couldn't possibly parse it down into something that would fit here) I have the same issue. (If I use the hot reload in Flutter the text DOES change in my actual app.)
So as you can see, I'm trying to communicate cross-classes and cross-functions, but either A) I'm not communicating correctly, or B) The communication is with an incorrect instance of the secondary page, and so the setState() call isn't being done on the variant that's being shown? Those are my only guesses.
You shouldn't call the createState manually. For implementing such a thing I prefer to use a stream instead, which is pretty much easy to handle.
timerStream.dart
import 'dart:async';
class TimerStream {
StreamController _streamController;
StreamSink<bool> get timerSink =>
_streamController.sink;
Stream<bool> get timerStream =>
_streamController.stream;
TimerStream() {
_streamController = StreamController<bool>();
}
dispose() {
_streamController?.close();
}
}
main.dart
import 'dart:async';
import 'package:flutter/cupertino.dart';
import './page2.dart';
import './timerStream.dart';
void main() => runApp(MyApp());
TimerStream stream = TimerStream();
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return CupertinoApp(
title: 'Splash Test',
theme: CupertinoThemeData(
primaryColor: Color.fromARGB(255, 0, 0, 255),
),
home: MyHomePage(title: 'Splash Test Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool textBool = false;
void changeTest(context) async {
Navigator.push(context, CupertinoPageRoute(builder: (context) => Page2(stream: stream,)));
Timer.periodic(Duration (seconds: 5), (Timer t) {
stream.timerSink.add(true);
});
}
#override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
child: Center(
child: CupertinoButton(
child: Text('To Splash'),
onPressed: () => changeTest(context),
),
),
);
}
}
page2.dart
import 'package:flutter/material.dart';
import 'timerStream.dart';
class Page2 extends StatefulWidget {
TimerStream stream;
Page2({this.stream});
#override
State<StatefulWidget> createState() => new PageState();
}
class PageState extends State<Page2> {
bool textChanger = false;
bool firstText = true;
Text myText() {
if (textChanger) {
Text text1 = new Text('Text One',
style: TextStyle(color: Color.fromARGB(255, 0, 0, 0)));
return text1;
} else {
Text text1 = new Text('Text Two',
style: TextStyle(color: Color.fromARGB(255, 0, 0, 0)));
return text1;
}
}
void changeText() {
if (!firstText) {
if (textChanger) {
print('Change One');
setState(() {
textChanger = false;
});
} else {
print('Change Two');
setState(() {
textChanger = true;
});
}
} else {
firstText = false;
}
}
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new Container(
child: Center(
child: myText()
)
),);
}
#override
void initState() {
super.initState();
widget.stream.timerStream.listen((onData) {
changeText();
});
}
}
Note: If you want, instead of writing true to the stream you can toggle the value and use that in your page2 to change the text.
Like title states, how can one access the state of a StatefulWidget from the StatefulWidget.
Background:
I have a star rating widget that consists of 5 "StarWidget"s in a row. The StarWidget class is just an Icon with a detector wrapped around it (not using IconButton because it has a very large size). The StarWidget stores whether it is selected or not in a corresponding State object and accordingly displays a solid or outline icon.
In my main widget, I have access to the StatefulWidget objects, and would like to configure their states.
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
class StarRatingWidget extends StatefulWidget {
#override
_StarRatingWidgetState createState() {
return _StarRatingWidgetState();
}
}
class _StarRatingWidgetState extends State<StarRatingWidget>
implements StarSelectionInterface {
//Properties
int _currentRating = 0;
List<RatingStarWidget> starWidgets = [];
//Methods
#override
void initState() {
super.initState();
starWidgets.add(
RatingStarWidget(
starSelectionInterface: this,
starPosition: 0,
),
);
starWidgets.add(
RatingStarWidget(
starSelectionInterface: this,
starPosition: 1,
),
);
starWidgets.add(
RatingStarWidget(
starSelectionInterface: this,
starPosition: 2,
),
);
starWidgets.add(
RatingStarWidget(
starSelectionInterface: this,
starPosition: 3,
),
);
starWidgets.add(
RatingStarWidget(
starSelectionInterface: this,
starPosition: 4,
),
);
}
#override
Widget build(BuildContext buildContext) {
return Row(
children: starWidgets,
);
}
//Star Selection Interface Methods
void onStarSelected(_RatingStarWidgetState starWidgetState) {
print("listener: star selected ${starWidgetState._starPosition}");
//a new, rating has been selected, update rating
if (_currentRating != starWidgetState._starPosition) {
_currentRating = (starWidgetState._starPosition + 1);
}
//same star as rating has been selected, set rating to 0
else {
_currentRating = 0;
}
//update stars according to rating
for(int i = 1; i <= 5; i++) {
//what should I do here?!
}
}
}
class RatingStarWidget extends StatefulWidget {
//Properties
final int starPosition;
final StarSelectionInterface starSelectionInterface;
//Constructors
RatingStarWidget({this.starSelectionInterface, this.starPosition});
//Methods
#override
_RatingStarWidgetState createState() {
return _RatingStarWidgetState(starSelectionInterface, starPosition);
}
}
class _RatingStarWidgetState extends State<RatingStarWidget> {
//Properties
int _starPosition;
bool _isSelected = false;
StarSelectionInterface selectionListener;
//Constructors
_RatingStarWidgetState(this.selectionListener, this._starPosition);
//Methods
#override
Widget build(BuildContext buildContext) {
return AnimatedCrossFade(
firstChild: GestureDetector(
child: Icon(
FontAwesomeIcons.star,
size: 14,
),
onTap: () {
print("star: selected");
selectionListener.onStarSelected(this);
},
),
secondChild: GestureDetector(
child: Icon(
FontAwesomeIcons.solidStar,
size: 14,
),
onTap: () {
selectionListener.onStarSelected(this);
},
),
duration: Duration(milliseconds: 300),
crossFadeState:
_isSelected ? CrossFadeState.showSecond : CrossFadeState.showFirst,
);
}
}
class StarSelectionInterface {
void onStarSelected(_RatingStarWidgetState starWidgetState) {}
}
The Flutter way is to rebuild widgets whenever it is necessary. Don't be afraid to build widgets, they are cheap for the SDK, specially in this case for simple stars.
Accessing another widget state requires more work than just rebuilding it. To access the state you should use keys or you should add special methods in the widget itself.
In this case, where the star is rebuilt no matter what, it is even better and simpler to use plain stateless widgets because the selected state can be provided by the parent in the moment of rebuilding.
And since the state is stored in the parent widget, I think it is better no to store it as wall in each one of the individual stars.
Next is a very simple solution that follows that idea. But yes, it still rebuilds the stars.
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(body: Center(child: StarRatingWidget())),
);
}
}
class StarRatingWidget extends StatefulWidget {
#override
_StarRatingWidgetState createState() {
return _StarRatingWidgetState();
}
}
class _StarRatingWidgetState extends State<StarRatingWidget> {
int _currentRating = 0;
List<Widget> buildStars() {
List<RatingStarWidget> starWidgets = [];
for (int i = 0; i < 5; i++) {
starWidgets.add(
RatingStarWidget(
clickCallback: () => setState(() {
_currentRating = i + 1;
}),
highlighted: _currentRating > i,
),
);
}
return starWidgets;
}
#override
Widget build(BuildContext buildContext) {
return Row(
children: buildStars(),
);
}
}
class RatingStarWidget extends StatelessWidget {
//Properties
final VoidCallback clickCallback;
final bool highlighted;
//Constructors
RatingStarWidget({this.clickCallback, this.highlighted});
#override
StatelessElement createElement() {
print("Element created");
return super.createElement();
}
//Methods
#override
Widget build(BuildContext buildContext) {
return GestureDetector(
onTap: () {
clickCallback();
},
child: AnimatedCrossFade(
firstChild: Icon(
FontAwesomeIcons.star,
size: 14,
),
secondChild: Icon(
FontAwesomeIcons.solidStar,
size: 14,
),
duration: Duration(milliseconds: 300),
crossFadeState:
highlighted ? CrossFadeState.showSecond : CrossFadeState.showFirst,
),
);
}
}
I wrote my own example similar to yours. What I do here is:
Initial star rate is -1 because arrays start from 0 ;) and I create stars with position, current star rate and the callback function. We will use this callback function to update the value in the ScreenOne.
In Star widget, we have a local bool selected with default value false and we assign it a value inside the build function based on the position of the star and current rate. And we have setSelected() function which runs the callback function and updates currentRate with the value of star position.
Check the video example here.
class ScreenOne extends StatefulWidget {
#override
_ScreenOneState createState() => _ScreenOneState();
}
class _ScreenOneState extends State<ScreenOne> {
int currentRate = -1; //since array starts from 0, set non-selected as -1
List<Star> starList = []; //empty list
#override
void initState() {
super.initState();
buildStars(); //build starts here on initial load
}
Widget buildStars() {
starList = [];
for (var i = 0; i < 5; i++) {
starList.add(Star(
position: i,
current: currentRate,
updateParent: refresh, //this is callback
));
}
}
refresh(int index) {
setState(() {
currentRate = index; //update the currentRate
});
buildStars(); //build stars again
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
title: Text("Test page 1"),
),
body: Container(
child: Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: starList,
),
),
),
);
}
}
class Star extends StatefulWidget {
final Function(int index) updateParent; //callback
final int position; //position of star
final int current; //current selected star from parent
const Star({Key key, this.position, this.updateParent, this.current})
: super(key: key);
#override
_StarState createState() => _StarState();
}
class _StarState extends State<Star> {
bool selected = false;
void setSelected() {
widget.updateParent(widget.position);
}
#override
Widget build(BuildContext context) {
if (widget.current >= widget.position) {
selected = true;
} else {
selected = false;
}
return GestureDetector(
child: AnimatedCrossFade(
firstChild: Icon(Icons.star_border),
secondChild: Icon(Icons.star),
crossFadeState:
selected ? CrossFadeState.showSecond : CrossFadeState.showFirst,
duration: Duration(milliseconds: 300),
),
onTap: () {
setSelected();
},
);
}
}
I am using imagecarousel package for displaying images from the network. I want to keep onPressed function for images in the slide.
new ImageCarousel(
<ImageProvider>[
new NetworkImage('http://www.hilversum.ferraridealers.com/siteasset/ferraridealer/54f07ac8c35b6/961/420/selected/0/0/0/54f07ac8c35b6.jpg'),
new NetworkImage('http://auto.ferrari.com/en_EN/wp-content/uploads/sites/5/2017/08/ferrari-portofino-reveal-2017-featured-new.jpg'),
new NetworkImage('http://www.hilversum.ferraridealers.com/siteasset/ferraridealer/54f07ac8c35b6/961/420/selected/0/0/0/54f07ac8c35b6.jpg'),
],
interval: new Duration(seconds: 1),
)
After making some modifications to Image Carousel, I was able to implement click event (other events also possible). Here is the sample code.
import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
class ImageCarousel extends StatefulWidget {
final List<ImageProvider> imageProviders;
final double height;
final TargetPlatform platform;
final Duration interval;
final TabController tabController;
final BoxFit fit;
// Images will shrink according to the value of [height]
// If you prefer to use the Material or Cupertino style activity indicator set the [platform] parameter
// Set [interval] to let the carousel loop through each photo automatically
// Pinch to zoom will be turned on by default
ImageCarousel(this.imageProviders,
{this.height = 250.0, this.platform, this.interval, this.tabController, this.fit = BoxFit.cover});
#override
State createState() => new _ImageCarouselState();
}
TabController _tabController;
class _ImageCarouselState extends State<ImageCarousel> with SingleTickerProviderStateMixin {
#override
void initState() {
super.initState();
_tabController = widget.tabController ?? new TabController(vsync: this, length: widget.imageProviders.length);
if (widget.interval != null) {
new Timer.periodic(widget.interval, (_) {
_tabController.animateTo(_tabController.index == _tabController.length - 1 ? 0 : ++_tabController.index);
});
}
}
#override
void dispose() {
_tabController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return new SizedBox(
height: widget.height,
child: new TabBarView(
controller: _tabController,
children: widget.imageProviders.map((ImageProvider provider) {
return new CarouselImageWidget(widget, provider, widget.fit, widget.height);
}).toList(),
),
);
}
}
class CarouselImageWidget extends StatefulWidget {
final ImageCarousel carousel;
final ImageProvider imageProvider;
final BoxFit fit;
final double height;
CarouselImageWidget(this.carousel, this.imageProvider, this.fit, this.height);
#override
State createState() => new _CarouselImageState();
}
class _CarouselImageState extends State<CarouselImageWidget> {
bool _loading = true;
Widget _getIndicator(TargetPlatform platform) {
if (platform == TargetPlatform.iOS) {
return new CupertinoActivityIndicator();
} else {
return new Container(
height: 40.0,
width: 40.0,
child: new CircularProgressIndicator(),
);
}
}
#override
void initState() {
super.initState();
widget.imageProvider.resolve(new ImageConfiguration()).addListener((i, b) {
if (mounted) {
setState(() {
_loading = false;
});
}
});
}
#override
Widget build(BuildContext context) {
return new Container(
height: widget.height,
child: _loading
? _getIndicator(widget.carousel.platform == null ? defaultTargetPlatform : widget.carousel.platform)
: new GestureDetector(
child: new Image(
image: widget.imageProvider,
fit: widget.fit,
),
onTap: () {
int index = int.parse(_tabController.index.toString());
switch(index){
//Implement you case here
case 0:
case 1:
case 2:
default:
print(_tabController.index.toString());
}
},
),
);
}
}
void main(){
runApp(new MaterialApp(
home: new Scaffold(
appBar: new AppBar(
title: new Text("Demo"),
),
body: new ImageCarousel(
<ImageProvider>[
new NetworkImage(
'http://wallpaper-gallery.net/images/images/images-2.jpg'),
new NetworkImage(
'http://wallpaper-gallery.net/images/images/images-10.jpg'),
new NetworkImage(
'http://wallpaper-gallery.net/images/images/images-4.jpg'),
],
interval: new Duration(seconds: 5),
)
),
));
}
Hope it helps..!!
I am trying to build a countdown widget. Currently, I got the structure to work. I only struggle with the countdown itself. I tried this approach using the countdown plugin:
class _Countdown extends State<Countdown> {
int val = 3;
void countdown(){
CountDown cd = new CountDown(new Duration(seconds: 4));
cd.stream.listen((Duration d) {
setState((){
val = d.inSeconds;
});
});
}
#override
build(BuildContext context){
countdown();
return new Scaffold(
body: new Container(
child: new Center(
child: new Text(val.toString(), style: new TextStyle(fontSize: 150.0)),
),
),
);
}
}
However, the value changes very weirdly and not smooth at all. It start twitching. Any other approach or fixes?
It sounds like you are trying to show an animated text widget that changes over time. I would use an AnimatedWidget with a StepTween to ensure that the countdown only shows integer values.
import 'package:flutter/material.dart';
void main() {
runApp(new MaterialApp(
home: new MyApp(),
));
}
class Countdown extends AnimatedWidget {
Countdown({ Key key, this.animation }) : super(key: key, listenable: animation);
Animation<int> animation;
#override
build(BuildContext context){
return new Text(
animation.value.toString(),
style: new TextStyle(fontSize: 150.0),
);
}
}
class MyApp extends StatefulWidget {
State createState() => new _MyAppState();
}
class _MyAppState extends State<MyApp> with TickerProviderStateMixin {
AnimationController _controller;
static const int kStartValue = 4;
#override
void initState() {
super.initState();
_controller = new AnimationController(
vsync: this,
duration: new Duration(seconds: kStartValue),
);
}
#override
Widget build(BuildContext context) {
return new Scaffold(
floatingActionButton: new FloatingActionButton(
child: new Icon(Icons.play_arrow),
onPressed: () => _controller.forward(from: 0.0),
),
body: new Container(
child: new Center(
child: new Countdown(
animation: new StepTween(
begin: kStartValue,
end: 0,
).animate(_controller),
),
),
),
);
}
}
The countdown() method should be called from the initState() method of the State object.
class _CountdownState extends State<CountdownWidget> {
int val = 3;
CountDown cd;
#override
void initState() {
super.initState();
countdown();
}
...
Description of initState() from the Flutter docs:
The framework calls initState. Subclasses of State should override
initState to perform one-time initialization that depends on the
BuildContext or the widget, which are available as the context and
widget properties, respectively, when the initState method is called.
Here is a full working example:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:countdown/countdown.dart';
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Countdown Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new CountdownWidget();
}
}
class _CountdownState extends State<CountdownWidget> {
int val = 3;
CountDown cd;
#override
void initState() {
super.initState();
countdown();
}
void countdown(){
print("countdown() called");
cd = new CountDown(new Duration(seconds: 4));
StreamSubscription sub = cd.stream.listen(null);
sub.onDone(() {
print("Done");
});
sub.onData((Duration d) {
if (val == d.inSeconds) return;
print("onData: d.inSeconds=${d.inSeconds}");
setState((){
val = d.inSeconds;
});
});
}
#override
build(BuildContext context){
return new Scaffold(
body: new Container(
child: new Center(
child: new Text(val.toString(), style: new TextStyle(fontSize: 150.0)),
),
),
);
}
}
class CountdownWidget extends StatefulWidget {
#override
_CountdownState createState() => new _CountdownState();
}
based on #raju-bitter answer, alternative to use async/await on countdown stream
void countdown() async {
cd = new CountDown(new Duration(seconds:4));
await for (var v in cd.stream) {
setState(() => val = v.inSeconds);
}
}
Why not use a simple TweenAnimationBuilder its easy to use and you don't need to manage any stream controllers or worry about using streams and disposing them off etc;
TweenAnimationBuilder<double>(
duration: Duration(seconds: 10),
tween: Tween(begin: 100.0, end: 0.0),
onEnd: () {
print('Countdown ended');
},
builder: (BuildContext context, double value, Widget child) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 5),
child: Text('${value.toInt()}',
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.bold,
fontSize: 40)));
}),
here's the dartpad example to playaround
output:
originally answered here
Countdown example using stream, not using setState(...) therefore its all stateless.
this borrow idea from example flutter_stream_friends
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:countdown/countdown.dart';
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
static String appTitle = "Count down";
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: appTitle,
theme: new ThemeData(
primarySwatch: Colors.purple,
),
home: new StreamBuilder(
stream: new CounterScreenStream(5),
builder: (context, snapshot) => buildHome(
context,
snapshot.hasData
// If our stream has delivered data, build our Widget properly
? snapshot.data
// If not, we pass through a dummy model to kick things off
: new Duration(seconds: 5),
appTitle)),
);
}
// The latest value of the CounterScreenModel from the CounterScreenStream is
// passed into the this version of the build function!
Widget buildHome(BuildContext context, Duration duration, String title) {
return new Scaffold(
appBar: new AppBar(
title: new Text(title),
),
body: new Center(
child: new Text(
'Count down ${ duration.inSeconds }',
),
),
);
}
}
class CounterScreenStream extends Stream<Duration> {
final Stream<Duration> _stream;
CounterScreenStream(int initialValue)
: this._stream = createStream(initialValue);
#override
StreamSubscription<Duration> listen(
void onData(Duration event),
{Function onError,
void onDone(),
bool cancelOnError}) =>
_stream.listen(onData,
onError: onError, onDone: onDone, cancelOnError: cancelOnError);
// The method we use to create the stream that will continually deliver data
// to the `buildHome` method.
static Stream<Duration> createStream(int initialValue) {
var cd = new CountDown(new Duration(seconds: initialValue));
return cd.stream;
}
}
The difference from stateful is that reload the app will restart counting. When using stateful, in some cases, it may not restart when reload.