I am currently writing an app using Flutter and Dart. On a button onPressed event I would like to invoke an action that executes after timeLeft seconds unless it is cancelled by correctly entering a pin. Additionally, I would like to use the value timeLeft in a Text widget.
This would require a structure with the following functionality:
executing a function after an x amount of seconds
this function should execute unless some event (e.g. entering a pin correctly) has occurred.
the timeLeft value should be accessible to be used in a Text widget and should update as the timer progresses.
I am wondering how to do this according to flutter's and dart's best practices. For state management I am using the provider pattern so preferably this approach is compatible with the provider pattern.
This is what I have tried so far:
class Home extends ChangeNotifier {
int secondsLeft = 10;
void onPressedEmergencyButton(BuildContext context) {
countDown();
showDialog<void>(
context: context,
builder: (context) {
return ScreenLock(
title: Text(
"Sending message in ${context.read<Home>().secondsLeft} seconds"),
correctString: '1234',
canCancel: false,
didUnlocked: () {
Navigator.pop(context);
},
);
},
);
}
void countDown() {
Future.delayed(const Duration(seconds: 1), () {
secondsLeft =- 1;
notifyListeners();
if (secondsLeft <= 0) {
// Do something
return;
}
});
}
}
You can use CancelableOperation from async package.
Simplifying code-snippet and about _cancelTimer(bool) , this bool used to tell widget about true = time end, and on cancel false like _cancelTimer(false);, rest are described on code-comments.
class TS extends StatefulWidget {
const TS({Key? key}) : super(key: key);
#override
State<TS> createState() => _TSState();
}
class _TSState extends State<TS> {
Timer? _timer;
final Duration _refreseRate = const Duration(seconds: 1);
CancelableOperation? _cancelableOperation;
Duration taskDuration = const Duration(seconds: 5);
bool isSuccess = false;
_initTimer() {
if (_cancelableOperation != null) {
_cancelTimer(false);
}
_cancelableOperation = CancelableOperation.fromFuture(
Future.delayed(Duration.zero),
).then((p0) {
_timer = Timer.periodic(_refreseRate, (timer) {
setState(() {
taskDuration -= _refreseRate;
});
if (taskDuration <= Duration.zero) {
/// task complete on end of duration
_cancelTimer(true);
}
});
}, onCancel: () {
_timer?.cancel();
setState(() {});
});
}
_cancelTimer(bool eofT) {
// cancel and reset everything
_cancelableOperation?.cancel();
_timer?.cancel();
_timer = null;
taskDuration = const Duration(seconds: 5);
isSuccess = eofT;
setState(() {});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (isSuccess)
Container(
height: 100,
width: 100,
color: Colors.green,
),
if (_timer != null)
Text("${taskDuration.inSeconds}")
else
const Text("init Timer"),
],
),
),
floatingActionButton: Row(
mainAxisSize: MainAxisSize.min,
children: [
FloatingActionButton(
child: const Text("init"),
onPressed: () {
_initTimer();
},
),
FloatingActionButton(
child: const Text("Cancel"),
onPressed: () {
_cancelTimer(false);
},
),
],
),
);
}
}
You can use the Timer class to run a function after a set Duration. It doesn't give you the time remaining, but you can calculate it yourself.
Here is a quick implementation I put together:
import 'dart:async';
import 'package:flutter/material.dart';
void main() async {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.light(),
home: const Scaffold(
body: Center(
child: Countdown(),
),
),
);
}
}
class Countdown extends StatefulWidget {
const Countdown({Key? key}) : super(key: key);
#override
_CountdownState createState() => _CountdownState();
}
class _CountdownState extends State<Countdown> {
bool active = false;
Timer? timer;
Timer? refresh;
Stopwatch stopwatch = Stopwatch();
Duration duration = const Duration(seconds: 5);
_CountdownState() {
// this is just so the time remaining text is updated
refresh = Timer.periodic(
const Duration(milliseconds: 100), (_) => setState(() {}));
}
void start() {
setState(() {
active = true;
timer = Timer(duration, () {
stop();
onCountdownComplete();
});
stopwatch
..reset()
..start();
});
}
void stop() {
setState(() {
active = false;
timer?.cancel();
stopwatch.stop();
});
}
void onCountdownComplete() {
showDialog(
context: context,
builder: (context) => const AlertDialog(
title: Text('Countdown was not stopped!'),
),
);
}
int secondsRemaining() {
return duration.inSeconds - stopwatch.elapsed.inSeconds;
}
#override
void dispose() {
timer?.cancel();
refresh?.cancel();
stopwatch.stop();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
if (active) Text(secondsRemaining().toString()),
if (active)
TextButton(onPressed: stop, child: const Text('Stop'))
else
TextButton(onPressed: start, child: const Text('Start')),
],
);
}
}
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),
]),
]),
),
),
);
}
}
I wrote a code like this:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_sms_inbox/flutter_sms_inbox.dart';
class Inbox extends StatefulWidget {
const Inbox({Key? key}) : super(key: key);
#override
State<Inbox> createState() => _InboxState();
}
SmsQuery query = new SmsQuery();
List<SmsMessage> AllMessages = [];
Timer? _timer;
class _InboxState extends State<Inbox> {
#override
void initState() {
GetAllMessages();
_timer = Timer.periodic(const Duration(seconds: 1), (Timer t) {
GetAllMessages();
print("refreshed");
print(AllMessages); // output: []
});
super.initState();
}
Future<void> GetAllMessages() async {
Future.delayed(Duration.zero, () async {
List<SmsMessage> messages = await query.querySms(
kinds: [SmsQueryKind.inbox],
count: 50,
);
setState(() {
AllMessages = messages;
});
});
}
#override
void dispose() {
_timer?.cancel();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
physics: const BouncingScrollPhysics(),
child: Page(),
),
);
}
}
Widget Page() {
if (AllMessages == null) {
return Text("No message");
} else {
Column(
children: [
const SizedBox(height: 25),
Container(
child: Column(
children: AllMessages.map((msg) {
return Container(
child: Card(
child: ListTile(
leading: Text("${msg.body}"),
title: Text("${msg.address}"),
),
),
);
}
).toList(),
),
),
],
);
}
return SizedBox();
}
I'm trying to access and list SMS's on the phone. I save the SMS in the list called AllMessages. If there is no SMS, I want it to show a Text named No message but it doesn't.
It shows blank as in the picture:
Why could this be? How can I solve the problem? Thanks in advance for your help.
I come here asking for help to understand if it is possible to put one or several functions outside a StatefulWidget followig an example that I found in the internet.
The following code example is working and it is to connect over Wifi to a ESP32 Microcontroller.
Inside the StatefulWidget there are some functions like for example void channelconnect(), void initState() etc. My question is how I can put these functions outside the StatefulWidget in another Widget. Is it possible to seperate the Logic from the UI?
void main() => runApp(
const GetMaterialApp(home: MyApp()),
);
final key = GlobalKey<WebSocketLed1State>();
//----------------------------------------------------------------
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const MaterialApp(
home: WebSocketLed1(),
);
}
}
//---------------------------------------------------------------------------
class WebSocketLed1 extends StatefulWidget {
const WebSocketLed1({Key? key}) : super(key: key);
#override
State createState() => WebSocketLed1State();
}
class WebSocketLed1State extends State<WebSocketLed1> {
late bool ledstatus; //boolean value to track LED status, if its ON or OFF
late IOWebSocketChannel channel;
late bool connected; //boolean value to track if WebSocket is connected
var val = -255.0;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("LED - ON/OFF NodeMCU"),
backgroundColor: Colors.redAccent),
body: Container(
alignment: Alignment.topCenter, //inner widget alignment to center
padding: const EdgeInsets.all(20),
child: Column(
children: [
Container(
child: connected
? const Text("WEBSOCKET: CONNECTED")
: const Text("DISCONNECTED")),
Container(
child: ledstatus
? const Text("LED IS: ON")
: const Text("LED IS: OFF")),
Container(
margin: const EdgeInsets.only(top: 30),
child: TextButton(
//button to start scanning
onPressed: () {
//on button press
if (ledstatus) {
//if ledstatus is true, then turn off the led
//if led is on, turn off
sendcmd("poweroff");
ledstatus = false;
} else {
//if ledstatus is false, then turn on the led
//if led is off, turn on
sendcmd("poweron");
ledstatus = true;
}
setState(() {});
},
child: ledstatus
? const Text("TURN LED OFF")
: const Text("TURN LED ON"))),
SizedBox(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.symmetric(horizontal: 30),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
'Brightness: '
'${((255 - val.toInt().abs()) / 255 * 100).toInt()}%',
style: const TextStyle(
color: Colors.black,
fontSize: 24.0,
fontWeight: FontWeight.w500,
),
),
RotatedBox(
quarterTurns: 3,
child: SizedBox(
width: 200,
child: Slider.adaptive(
value: val,
min: -255,
max: 0,
onChanged: sendMessage,
),
),
)
],
),
),
],
),
),
],
)),
);
}
#override
void initState() {
ledstatus = false; //initially leadstatus is off so its FALSE
connected = false; //initially connection status is "NO" so its FALSE
Future.delayed(Duration.zero, () async {
channelconnect(); //connect to WebSocket wth NodeMCU
});
super.initState();
}
void channelconnect() {
//function to connect
try {
channel = IOWebSocketChannel.connect(
"ws://192.168.204.65:81"); //channel IP : Port
channel.stream.listen(
(message) {
// ignore: avoid_print
print(message);
setState(() {
if (message == "connected") {
connected = true; //message is "connected" from NodeMCU
} else if (message == "poweron:success") {
ledstatus = true;
} else if (message == "poweroff:success") {
ledstatus = false;
}
});
},
onDone: () {
//if WebSocket is disconnected
// ignore: avoid_print
print("Web socket is closed");
setState(() {
connected = false;
});
},
onError: (error) {
// ignore: avoid_print
print(error.toString());
},
);
} catch (_) {
// ignore: avoid_print
print("error on connecting to websocket.");
}
}
Future<void> sendcmd(String cmd) async {
if (connected == true) {
if (ledstatus == false && cmd != "poweron" && cmd != "poweroff") {
// ignore: avoid_print
print("Send the valid command");
} else {
channel.sink.add(cmd); //sending Command to NodeMCU
}
} else {
channelconnect();
// ignore: avoid_print
print("Websocket is not connected.");
}
}
void sendMessage(double v) {
try {
setState(() {
val = v.roundToDouble();
});
channel.sink.add('clear\n');
channel.sink.add('${val.toInt().abs()}\n');
} catch (e) {
debugPrint(e.toString());
}
}
}
I suggest you to define a class and define your function in it and then call functions you want any where you want.
Thanks #最白目, thanks #Abraams gtr for your help.
Also thanks #Tomerikoo for making a review on my inicial question. :)
Like i told, it was the first time for me, here asking for help and i am not familiar with stackoverflow.
After searching on the internet for some info for my quetion i could find a solution.
It can be that it is not the best way to make it, but it is working. For sure there is a better way to make it.
I will continuo working on it. Of course if there is somebody with a better method i would appreciate another point of view.
Flutter and Dart are completely new Land for me.
Follow my approach for my inicial question.
void main() {
runApp(
const GetMaterialApp(home: MyApp()),
);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return GetBuilder(
init: MessageController(),
builder: (GetxController controller) {
return const WebSocketLed();
},
);
}
}
class WebSocketLed extends StatelessWidget {
const WebSocketLed({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Slider'),
),
body: Container(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
Brightness1(),
Slider1(),
],
),
));
}
}
class Slider1 extends StatelessWidget {
const Slider1({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final messageController = Get.put(MessageController());
return SizedBox(
child: GetBuilder<SliderController>(
init: SliderController(),
builder: (ctrl) => SizedBox(
child: Slider(
value: ctrl.quality,
min: 0,
max: 255,
divisions: 255,
label: ctrl.quality.round().toString(),
onChanged: (double value) {
ctrl.setQuality(value);
},
onChangeEnd: (messageController.sendMessage),
onChangeStart: (messageController.sendMessage)),
),
));
}
}
class Brightness1 extends StatelessWidget {
const Brightness1({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return SizedBox(
child: GetBuilder<SliderController>(
init: SliderController(),
builder: (ctrl) => SizedBox(
child: Text(
ctrl.quality.round().toString(),
),
)));
}
}
class MessageController extends GetxController {
late IOWebSocketChannel channel;
late bool connected;
var val = 255.0;
void sendMessage(double v) {
try {
val = v.roundToDouble();
channel.sink.add('clear\n');
channel.sink.add('${val.toInt().abs()}\n');
} catch (e) {
debugPrint(e.toString());
}
}
#override
void onInit() {
super.onInit();
try {
channel = IOWebSocketChannel.connect(
"ws://192.168.75.5:81"); //channel IP : Port
channel.stream.listen(
(message) {
// ignore: avoid_print
print(message);
},
onDone: () {
//if WebSocket is disconnected
// ignore: avoid_print
print("Web socket is closed");
},
onError: (error) {
// ignore: avoid_print
print(error.toString());
},
);
} catch (_) {
// ignore: avoid_print
print("error on connecting to websocket.");
}
}
#override
void dispose() {
channel.sink.close();
super.dispose();
}
}
//--------------------------------------------------------------
class SliderController extends GetxController {
static SliderController get to => Get.find();
double quality = 255;
void setQuality(double quality) {
this.quality = quality;
update();
}
}
I want to create a custom widget in which when I click a button,
it expands to a widget something like this [ - 0 + ]
if the user taps '-' or '+', the button should remain expanded and should decrement or increment the value for the respective taps. The current value should be reflected.
if the user does not tap anything after expansion, the widget should collapse to its previous state after some x duration (say 2 seconds) from the previous tap.
Here's is my code
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: MyWidget(),
),
),
);
}
}
class MyWidget extends StatefulWidget {
#override
MyWidgetState createState() => MyWidgetState();
}
class MyWidgetState extends State<MyWidget> {
bool isExpanded = false;
int quantity = 0;
int duration = 3;
#override
Widget build(BuildContext context) {
return !isExpanded
? ElevatedButton(
child: Text('Expand (current: $quantity)'),
onPressed: expansionHandler)
: Card(
elevation: 5.0,
child: Container(
color: Colors.blue,
width: 200,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
IconButton(
icon: const Icon(Icons.remove),
onPressed: decrementValue),
Text('$quantity'),
IconButton(
icon: const Icon(Icons.add),
onPressed: incrementValue),
])));
}
void incrementValue() {
setState(() {
duration += 3;
quantity++;
});
}
void decrementValue() {
setState(() {
duration += 3;
quantity--;
quantity = quantity < 0 ? 0 : quantity;
});
}
void expansionHandler() async {
setState(() {
isExpanded = true;
});
await Future.delayed(Duration(seconds: duration+3)/*not sure if this can work or not*/, () {
setState(() {
isExpanded = false;
duration = 2;
});
});
}
}
DartPad Link: https://dartpad.dev/?id=c22ea14c649cc2858f6d09270a62a0d5&null_safety=true
Test this Widget. I comment down the duration changes for fast testing.
I think the problem with expansionHandler with Future that it is activated when you click the 1st time. On second and next click, it generates a new instance instead of replacing the current one.
According to you question, we to check the current state of Timer, where 'Future.delay' can't handle in this case.
Hope this is what you need.
class MyWidget extends StatefulWidget {
#override
MyWidgetState createState() => MyWidgetState();
}
class MyWidgetState extends State<MyWidget> {
bool isExpanded = false;
int quantity = 0;
int duration = 3;
Timer? timer;
setUpTimer() async {
print("startTimer");
setState(() {
isExpanded = true;
});
timer = await Timer.periodic(Duration(seconds: duration), (timer) {
setState(() {
isExpanded = false;
duration = 2;
});
timer.cancel();
print("done with time $duration");
});
}
_reset() {
if (timer != null && timer!.isActive) timer!.cancel();
setUpTimer();
}
#override
void dispose() {
if (timer != null && timer!.isActive) timer!.cancel();
super.dispose();
}
#override
Widget build(BuildContext context) {
return !isExpanded
? ElevatedButton(
child: Text('Expand (current: $quantity)'),
onPressed: _reset,
)
: Card(
elevation: 5.0,
child: Container(
color: Colors.blue,
width: 200,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
IconButton(
icon: const Icon(Icons.remove),
onPressed: decrementValue),
Text('$quantity'),
IconButton(
icon: const Icon(Icons.add),
onPressed: incrementValue,
),
],
),
),
);
}
void incrementValue() {
setState(() {
// duration += 3;
///for fast testing
quantity++;
});
_reset();
}
void decrementValue() {
setState(() {
// duration += 3;
quantity--;
quantity = quantity < 0 ? 0 : quantity;
});
}
}
I wanted to Download a Image with its progress and message. I wanted to show it in a dialog. When ever I click Download Button the Image gets downloaded and the Container pops up, but it does not Show any value.
The below code uses Image_downloader package. Raised button downloads the image and display the Blank Container without any value;
import 'dart:async';
import 'dart:io';
import 'Download.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:image_downloader/image_downloader.dart';
void main() => runApp(HomePage());
class HomePage extends StatefulWidget {
#override
HomePageState createState() => HomePageState();
}
class HomePageState extends State<HomePage> {
String message = "";
String path = "";
int _progress = 0;
#override
void initState() {
super.initState();
ImageDownloader.callback(onProgressUpdate: (String imageId, int progress) {
setState(() {
_progress = progress;
});
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Plugin example app'),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
RaisedButton(
onPressed: () {
_downloadImage(
"https://images.unsplash.com/photo-1503023345310-bd7c1de61c7d?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&w=1000&q=80",
);
showDialog(
context: context,
builder: (_) => FunkyOverlay(progress: _progress, message: message,),
);
},
child: Text("default destination"),
),
],
),
),
),
);
}
Future<void> _downloadImage(String url,
{AndroidDestinationType destination, bool whenError = false}) async {
String fileName;
String path;
try {
String imageId;
if (whenError) {
imageId = await ImageDownloader.downloadImage(url).catchError((error) {
if (error is PlatformException) {
var path = "";
if (error.code == "404") {
print("Not Found Error.");
} else if (error.code == "unsupported_file") {
print("UnSupported FIle Error.");
path = error.details["unsupported_file_path"];
}
setState(() {
message = error.toString();
path = path;
});
}
print(error);
}).timeout(Duration(seconds: 10), onTimeout: () {
print("timeout");
});
} else {
if (destination == null) {
imageId = await ImageDownloader.downloadImage(url);
} else {
imageId = await ImageDownloader.downloadImage(
url,
destination: destination,
);
}
}
if (imageId == null) {
return;
}
fileName = await ImageDownloader.findName(imageId);
path = await ImageDownloader.findPath(imageId);
} on PlatformException catch (error) {
setState(() {
message = error.message;
});
return;
}
if (!mounted) return;
setState(() {
message = 'Image Downloaded';
});
}
}
This is the Pop up Container Part
import 'package:flutter/material.dart';
class FunkyOverlay extends StatefulWidget {
String message;
int progress;
FunkyOverlay({#required this.message, #required this.progress});
#override
State<StatefulWidget> createState() => FunkyOverlayState(message, progress);
}
class FunkyOverlayState extends State<FunkyOverlay>
with SingleTickerProviderStateMixin {
String message;
int progress;
FunkyOverlayState(this.message, this.progress);
AnimationController controller;
Animation<double> scaleAnimation;
#override
void initState() {
super.initState();
controller =
AnimationController(vsync: this, duration: Duration(milliseconds: 450));
scaleAnimation =
CurvedAnimation(parent: controller, curve: Curves.elasticInOut);
controller.addListener(() {
setState(() {});
});
controller.forward();
}
#override
Widget build(BuildContext context) {
return Center(
child: Material(
color: Colors.transparent,
child: ScaleTransition(
scale: scaleAnimation,
child: Container(
decoration: ShapeDecoration(
color: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15.0),
),
),
child: Padding(
padding: const EdgeInsets.all(50.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text('Downloaded: $progress'),
Text(message)
],
),
),
),
),
),
);
}
}
You can copy paste run full code below
You can use StreamBuilder to receive progress from onProgressUpdate
class HomePageState extends State<HomePage> {
...
#override
void initState() {
super.initState();
_events = new StreamController<int>.broadcast();;
_events.add(0);
ImageDownloader.callback(onProgressUpdate: (String imageId, int progress) {
setState(() {
print("progress $progress");
_progress = progress;
_events.add(progress);
});
return StreamBuilder<int>(
stream: _events.stream,
builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
return Center(
...
children: <Widget>[
Text('Downloaded: ${snapshot.data.toString()}'),
Text(message)
working demo
full code
import 'package:flutter/material.dart';
import 'dart:async';
import 'dart:io';
import 'package:flutter/services.dart';
import 'package:image_downloader/image_downloader.dart';
void main() => runApp(MaterialApp(home: HomePage()));
class HomePage extends StatefulWidget {
#override
HomePageState createState() => HomePageState();
}
StreamController<int> _events;
class HomePageState extends State<HomePage> {
String message = "";
String path = "";
int _progress = 0;
#override
void initState() {
super.initState();
_events = new StreamController<int>.broadcast();
;
_events.add(0);
ImageDownloader.callback(onProgressUpdate: (String imageId, int progress) {
setState(() {
print("progress $progress");
_progress = progress;
_events.add(progress);
if (progress == 100) {
Navigator.pop(context);
}
});
});
}
#override
void dispose() {
// TODO: implement dispose
_events.close();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Plugin example app'),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
RaisedButton(
onPressed: () {
_events.add(0);
_downloadImage(
"https://images.unsplash.com/photo-1503023345310-bd7c1de61c7d?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&w=1000&q=80",
);
showDialog(
context: context,
builder: (_) => FunkyOverlay(
progress: _progress,
message: message,
),
);
},
child: Text("default destination"),
),
],
),
),
);
}
Future<void> _downloadImage(String url,
{AndroidDestinationType destination, bool whenError = false}) async {
String fileName;
String path;
try {
String imageId;
if (whenError) {
imageId = await ImageDownloader.downloadImage(url).catchError((error) {
if (error is PlatformException) {
var path = "";
if (error.code == "404") {
print("Not Found Error.");
} else if (error.code == "unsupported_file") {
print("UnSupported FIle Error.");
path = error.details["unsupported_file_path"];
}
setState(() {
message = error.toString();
path = path;
});
}
print(error);
}).timeout(Duration(seconds: 10), onTimeout: () {
print("timeout");
});
} else {
if (destination == null) {
imageId = await ImageDownloader.downloadImage(url);
} else {
imageId = await ImageDownloader.downloadImage(
url,
destination: destination,
);
}
}
if (imageId == null) {
print("imageId is null");
return;
}
fileName = await ImageDownloader.findName(imageId);
path = await ImageDownloader.findPath(imageId);
} on PlatformException catch (error) {
setState(() {
message = error.message;
});
return;
}
if (!mounted) return;
setState(() {
message = 'Image Downloaded';
});
}
}
class FunkyOverlay extends StatefulWidget {
String message;
int progress;
FunkyOverlay({#required this.message, #required this.progress});
#override
State<StatefulWidget> createState() => FunkyOverlayState(message, progress);
}
class FunkyOverlayState extends State<FunkyOverlay>
with SingleTickerProviderStateMixin {
String message;
int progress;
FunkyOverlayState(this.message, this.progress);
AnimationController controller;
Animation<double> scaleAnimation;
#override
void initState() {
super.initState();
controller =
AnimationController(vsync: this, duration: Duration(milliseconds: 450));
scaleAnimation =
CurvedAnimation(parent: controller, curve: Curves.elasticInOut);
controller.addListener(() {
setState(() {});
});
controller.forward();
}
#override
Widget build(BuildContext context) {
print("StreamBuilder build");
return StreamBuilder<int>(
stream: _events.stream,
builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
print("snapshot.data ${snapshot.data.toString()}");
return Center(
child: Material(
color: Colors.transparent,
child: ScaleTransition(
scale: scaleAnimation,
child: Container(
decoration: ShapeDecoration(
color: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15.0),
),
),
child: Padding(
padding: const EdgeInsets.all(50.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text('Downloaded: ${snapshot.data.toString()}'),
Text(message)
],
),
),
),
),
),
);
});
}
}