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();
}
}
Related
I'm new to Flutter and followed this tutorial https://suragch.medium.com/background-audio-in-flutter-with-audio-service-and-just-audio-3cce17b4a7d to setup Just Audio and audioservice. I'm building my app for Android at the moment.
I'm having 3 issues:
The main problem is that when I use context.go (from GoRouter) to move to the player screen, audio should start playing, but it doesn't, I have to manually press the Play button.
Additionaly when I load a playlist and press Next, it also doesn't start playing automatically.
Finally, sometimes if I leave the player for other screen and come back, I can't press the Play button.
Here are the 3 files I'm using:
Player screen:
import 'package:audio_service/audio_service.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:podfic_app/screens/chapterlist_screen.dart';
import 'package:podfic_app/utils/api_service.dart';
import './just_audio/notifiers/play_button_notifier.dart';
import './just_audio/notifiers/progress_notifier.dart';
import './just_audio/page_manager.dart';
import './just_audio/services/service_locator.dart';
import 'package:audio_video_progress_bar/audio_video_progress_bar.dart';
class Player extends StatefulWidget {
const Player({super.key});
#override
State<Player> createState() => _PlayerState();
}
class _PlayerState extends State<Player> {
#override
void initState() {
super.initState();
getIt<PageManager>().init();
}
#override
void dispose() {
getIt<PageManager>().dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
// startPlaying();
return Scaffold(
body: SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 25.0),
child: Column(
children: [
// const SizedBox(height: 10),
// back button and menu button
Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () => context.pop()),
const Text('Listening to'),
IconButton(
icon: const Icon(Icons.playlist_play_outlined),
onPressed: () => context.push('/chapters')),
],
),
const SizedBox(height: 25),
// cover art, artist name, song name
const CurrentMetadata(),
const SizedBox(height: 30),
const SizedBox(height: 20),
// linear bar
const AudioProgressBar(),
const SizedBox(height: 30),
// previous song, pause play, skip next song
Align(
alignment: Alignment.topCenter,
child: Container(
width: double.infinity,
padding: const EdgeInsets.all(0),
child: Stack(children: [
Container(
child: const ChangeSpeedButton(),
),
Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
PreviousSongButton(),
PlayButton(),
NextSongButton(),
],
),
),
]),
),
),
],
),
),
),
);
}
}
// class CurrentSongTitle extends StatelessWidget {
// const CurrentSongTitle({Key? key}) : super(key: key);
// #override
// Widget build(BuildContext context) {
// final pageManager = getIt<PageManager>();
// return ValueListenableBuilder<String>(
// valueListenable: pageManager.currentSongTitleNotifier,
// builder: (_, title, __) {
// return Padding(
// padding: const EdgeInsets.only(top: 8.0),
// child: Text(title, style: const TextStyle(fontSize: 40)),
// );
// },
// );
// }
// }
class AddRemoveSongButtons extends StatelessWidget {
const AddRemoveSongButtons({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final pageManager = getIt<PageManager>();
return Padding(
padding: const EdgeInsets.only(bottom: 20.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
FloatingActionButton(
onPressed: pageManager.add,
child: const Icon(Icons.add),
),
FloatingActionButton(
onPressed: pageManager.remove,
child: const Icon(Icons.remove),
),
],
),
);
}
}
class AudioProgressBar extends StatelessWidget {
const AudioProgressBar({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final pageManager = getIt<PageManager>();
return ValueListenableBuilder<ProgressBarState>(
valueListenable: pageManager.progressNotifier,
builder: (_, value, __) {
return ProgressBar(
progress: value.current,
buffered: value.buffered,
total: value.total,
onSeek: pageManager.seek,
);
},
);
}
}
class AudioControlButtons extends StatelessWidget {
const AudioControlButtons({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
height: 60,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: const [
PreviousSongButton(),
PlayButton(),
NextSongButton(),
],
),
);
}
}
class PreviousSongButton extends StatelessWidget {
const PreviousSongButton({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final pageManager = getIt<PageManager>();
return ValueListenableBuilder<bool>(
valueListenable: pageManager.isFirstSongNotifier,
builder: (_, isFirst, __) {
return IconButton(
icon: const Icon(Icons.skip_previous),
iconSize: 32,
onPressed: (isFirst) ? null : pageManager.previous,
);
},
);
}
}
class PlayButton extends StatelessWidget {
const PlayButton({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final pageManager = getIt<PageManager>();
return ValueListenableBuilder<ButtonState>(
valueListenable: pageManager.playButtonNotifier,
builder: (_, value, __) {
switch (value) {
case ButtonState.loading:
return Container(
margin: const EdgeInsets.all(8.0),
width: 48.0,
height: 48.0,
child: const CircularProgressIndicator(),
);
case ButtonState.paused:
return Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primary,
borderRadius: BorderRadius.circular(200)),
child: IconButton(
icon: const Icon(Icons.play_arrow),
iconSize: 48,
color: Colors.white,
onPressed: pageManager.play,
));
case ButtonState.playing:
return Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primary,
borderRadius: BorderRadius.circular(200)),
child: IconButton(
icon: const Icon(Icons.pause),
iconSize: 48,
color: Colors.white,
onPressed: pageManager.pause,
));
}
},
);
}
}
class NextSongButton extends StatelessWidget {
const NextSongButton({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final pageManager = getIt<PageManager>();
return ValueListenableBuilder<bool>(
valueListenable: pageManager.isLastSongNotifier,
builder: (_, isLast, __) {
return IconButton(
icon: const Icon(Icons.skip_next),
iconSize: 32,
onPressed: (isLast) ? null : pageManager.next,
);
},
);
}
}
class ChangeSpeedButton extends StatefulWidget {
const ChangeSpeedButton({Key? key}) : super(key: key);
#override
State<ChangeSpeedButton> createState() => _ChangeSpeedButtonState();
}
class _ChangeSpeedButtonState extends State<ChangeSpeedButton> {
var selectedValue = 1.0;
#override
Widget build(BuildContext context) {
final pageManager = getIt<PageManager>();
return DropdownButtonHideUnderline(
child: DropdownButton(
icon: const Visibility(
visible: false, child: Icon(Icons.arrow_downward)),
value: selectedValue
.toString(), //we set a value here depending on the button pressed, and call the pagemanager method assing it the value.
items: dropdownItems,
onChanged: (value) {
setState(() {
selectedValue = double.parse(value.toString());
});
pageManager.changeSpeed(selectedValue);
}),
);
}
List<DropdownMenuItem<String>> get dropdownItems {
List<DropdownMenuItem<String>> menuItems = [
const DropdownMenuItem(value: "0.75", child: Text("0.75x")),
const DropdownMenuItem(value: "1.0", child: Text("1.0x")),
const DropdownMenuItem(value: "1.25", child: Text("1.25x")),
const DropdownMenuItem(value: "1.5", child: Text("1.5x")),
const DropdownMenuItem(value: "1.75", child: Text("1.75x")),
const DropdownMenuItem(value: "2.0", child: Text("2.0x")),
];
return menuItems;
}
}
class CurrentMetadata extends StatelessWidget {
const CurrentMetadata({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final pageManager = getIt<PageManager>();
return ValueListenableBuilder<MediaItem?>(
valueListenable: pageManager.currentSongMetadataNotifier,
builder: (context, mediaItem, child) {
if (mediaItem == null) {
return SizedBox.shrink();
}
return Column(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Image.network((mediaItem.artUri ?? '').toString()),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(mediaItem.extras!['chapterName'],
overflow: TextOverflow.fade,
style: Theme.of(context)
.textTheme
.titleMedium
?.copyWith(fontWeight: FontWeight.w700)),
const SizedBox(height: 6),
Text(
mediaItem.title,
overflow: TextOverflow.fade,
style: Theme.of(context).textTheme.titleSmall,
),
const SizedBox(height: 6),
Text(
'By ${mediaItem.artist}',
overflow: TextOverflow.fade,
style: Theme.of(context).textTheme.titleSmall,
),
],
),
IconButton(
icon: const Icon(Icons.favorite),
color: Colors.red,
onPressed: () {}),
],
),
)
],
);
// Text(mediaItem.album ?? ''),
// Text(mediaItem.displaySubtitle ?? ''),
},
);
}
}
PageManager.dart
import 'package:flutter/foundation.dart';
import 'package:podfic_app/models/getpodficlist_model.dart';
import 'package:podfic_app/models/series_model.dart';
import 'package:podfic_app/notifiers/chapterObject_notifier.dart';
import 'package:podfic_app/utils/api_service.dart';
import 'notifiers/play_button_notifier.dart';
import 'notifiers/progress_notifier.dart';
import 'notifiers/repeat_button_notifier.dart';
import 'services/playlist_repository.dart';
import 'package:audio_service/audio_service.dart';
import 'services/service_locator.dart';
class PageManager {
// Listeners: Updates going to the UI
final currentSongMetadataNotifier = ValueNotifier<MediaItem?>(null);
final playlistNotifier = ValueNotifier<List<String>>([]);
final progressNotifier = ProgressNotifier();
final repeatButtonNotifier = RepeatButtonNotifier();
final isFirstSongNotifier = ValueNotifier<bool>(true);
final playButtonNotifier = PlayButtonNotifier();
final isLastSongNotifier = ValueNotifier<bool>(true);
final isShuffleModeEnabledNotifier = ValueNotifier<bool>(false);
final _audioHandler = getIt<AudioHandler>();
List<Chapter>? chapterObject = getIt<ChapterObject>().chapterObject.value;
// Events: Calls coming from the UI
void init() async {
await loadPlaylist();
_listenToChangesInPlaylist();
_listenToPlaybackState();
_listenToCurrentPosition();
_listenToBufferedPosition();
_listenToTotalDuration();
_listenToChangesInSong();
// _startPlaying();
}
Future<void> loadPlaylist() async {
final mediaItems = seriesItems.chapterList?.map((chapter) {
final _podficInfo = chapter.podficInfo;
return MediaItem(
id: _podficInfo.id.toString(),
artUri: Uri.parse(_podficInfo.coverArt!),
artist: seriesItems.author,
title: seriesItems.title!,
extras: {
'url': _podficInfo.url,
'chapterName':
"Chapter ${_podficInfo.chapterNumber}: ${_podficInfo.title}"
},
);
}).toList() ??
[];
_audioHandler.addQueueItems(mediaItems);
}
// void _setInitialPlaylist() {
// final _seriesInfo = getIt<SeriesInfo>();
// final _playlist = SeriesInfo(chapterList: chapterItems);
// }
void play() => _audioHandler.play();
void pause() => _audioHandler.pause();
void seek(Duration position) => _audioHandler.seek(position);
void previous() => _audioHandler.skipToPrevious();
void next() => _audioHandler.skipToNext();
void repeat() {}
void shuffle() {}
void add() async {
//don't need this for now.
final songRepository = getIt<PlaylistRepository>();
final song = await songRepository.fetchAnotherSong();
final mediaItem = MediaItem(
id: song['id'] ?? '',
album: song['album'] ?? '',
title: song['title'] ?? '',
extras: {'url': song['url']},
);
_audioHandler.addQueueItem(mediaItem);
}
void remove() {
//don't need this for now.
final lastIndex = _audioHandler.queue.value.length - 1;
if (lastIndex < 0) return;
_audioHandler.removeQueueItemAt(lastIndex);
}
void dispose() {
_audioHandler.stop();
}
void _listenToChangesInPlaylist() {
_audioHandler.queue.listen((playlist) {
if (playlist.isEmpty) {
playlistNotifier.value = [];
currentSongMetadataNotifier.value = '' as MediaItem?;
} else {
final newList = playlist.map((item) => item.title).toList();
playlistNotifier.value = newList;
}
_updateSkipButtons();
});
}
void _listenToPlaybackState() {
_audioHandler.playbackState.listen((playbackState) {
final isPlaying = playbackState.playing;
final processingState = playbackState.processingState;
if (processingState == AudioProcessingState.loading ||
processingState == AudioProcessingState.buffering) {
playButtonNotifier.value = ButtonState.loading;
} else if (!isPlaying) {
playButtonNotifier.value = ButtonState.paused;
} else if (processingState != AudioProcessingState.completed) {
playButtonNotifier.value = ButtonState.playing;
logger.i('Reproduciendo');
} else {
_audioHandler.seek(Duration.zero);
_audioHandler.pause();
}
});
}
void _listenToCurrentPosition() {
AudioService.position.listen((position) {
final oldState = progressNotifier.value;
progressNotifier.value = ProgressBarState(
current: position,
buffered: oldState.buffered,
total: oldState.total,
);
});
}
void _listenToBufferedPosition() {
_audioHandler.playbackState.listen((playbackState) {
final oldState = progressNotifier.value;
progressNotifier.value = ProgressBarState(
current: oldState.current,
buffered: playbackState.bufferedPosition,
total: oldState.total,
);
});
}
void _listenToTotalDuration() {
_audioHandler.mediaItem.listen((mediaItem) {
final oldState = progressNotifier.value;
progressNotifier.value = ProgressBarState(
current: oldState.current,
buffered: oldState.buffered,
total: mediaItem?.duration ?? Duration.zero,
);
});
}
void _listenToChangesInSong() {
_audioHandler.mediaItem.listen((mediaItem) {
currentSongMetadataNotifier.value = mediaItem;
_updateSkipButtons();
});
}
void _updateSkipButtons() {
final mediaItem = _audioHandler.mediaItem.value;
final playlist = _audioHandler.queue.value;
if (playlist.length < 2 || mediaItem == null) {
isFirstSongNotifier.value = true;
isLastSongNotifier.value = true;
} else {
isFirstSongNotifier.value = playlist.first == mediaItem;
isLastSongNotifier.value = playlist.last == mediaItem;
}
}
void changeSpeed(value) {
_audioHandler.setSpeed(value);
}
// void _startPlaying(){
// _audioHandler.
// }
}
// class PlaySpeedNotifier extends ValueNotifier {
// PlaySpeedNotifier() : super(initialSpeed);
// static const initialSpeed = 1.0;
// var speed = initialSpeed;
// void changeSpeed(value) {
// speed = value;
// _audio
// }
// }
Audiohandler.dart
import 'package:audio_service/audio_service.dart';
import 'package:just_audio/just_audio.dart';
import 'package:podfic_app/utils/api_service.dart';
Future<AudioHandler> initAudioService() async {
return await AudioService.init(
builder: () => MyAudioHandler(),
config: const AudioServiceConfig(
androidNotificationChannelId: 'com.mycompany.myapp.audio',
androidNotificationChannelName: 'FanPods',
androidNotificationOngoing: true,
androidStopForegroundOnPause: true,
),
);
}
class MyAudioHandler extends BaseAudioHandler {
final _player = AudioPlayer();
final _playlist = ConcatenatingAudioSource(children: []);
MyAudioHandler() {
loadEmptyPlaylist();
_notifyAudioHandlerAboutPlaybackEvents();
_listenForDurationChanges();
_listenForCurrentSongIndexChanges();
}
Future<void> loadEmptyPlaylist() async {
try {
await _player.setAudioSource(_playlist);
} catch (e) {
logger.i('algo paso con loadEemptyplaylist');
}
}
// Future<void> startPlaying() => _player.setAudioSource(_playlist);
#override
Future<void> addQueueItems(List<MediaItem> mediaItems) async {
// manage Just Audio
final audioSource = mediaItems.map(_createAudioSource);
_playlist.addAll(audioSource.toList());
// notify system
final newQueue = queue.value..addAll(mediaItems);
queue.add(newQueue);
logger.i(newQueue);
}
UriAudioSource _createAudioSource(MediaItem mediaItem) {
return AudioSource.uri(
Uri.parse(mediaItem.extras!['url']),
tag: mediaItem,
);
}
#override
Future<void> play() => _player.play();
#override
Future<void> pause() => _player.pause();
void _notifyAudioHandlerAboutPlaybackEvents() {
_player.playbackEventStream.listen((PlaybackEvent event) {
final playing = _player.playing;
playbackState.add(playbackState.value.copyWith(
controls: [
MediaControl.skipToPrevious,
if (playing) MediaControl.pause else MediaControl.play,
MediaControl.skipToNext,
],
systemActions: const {
MediaAction.seek,
},
androidCompactActionIndices: const [0, 1, 3],
processingState: const {
ProcessingState.idle: AudioProcessingState.idle,
ProcessingState.loading: AudioProcessingState.loading,
ProcessingState.buffering: AudioProcessingState.buffering,
ProcessingState.ready: AudioProcessingState.ready,
ProcessingState.completed: AudioProcessingState.completed,
}[_player.processingState]!,
playing: playing,
updatePosition: _player.position,
bufferedPosition: _player.bufferedPosition,
speed: _player.speed,
queueIndex: event.currentIndex,
));
});
}
#override
Future<void> seek(Duration position) => _player.seek(position);
void _listenForDurationChanges() {
_player.durationStream.listen((duration) {
final index = _player.currentIndex;
final newQueue = queue.value;
if (index == null || newQueue.isEmpty) return;
final oldMediaItem = newQueue[index];
final newMediaItem = oldMediaItem.copyWith(duration: duration);
newQueue[index] = newMediaItem;
queue.add(newQueue);
mediaItem.add(newMediaItem);
});
}
#override
Future<void> skipToNext() => _player.seekToNext();
#override
Future<void> skipToPrevious() => _player.seekToPrevious();
void _listenForCurrentSongIndexChanges() {
_player.currentIndexStream.listen((index) {
final playlist = queue.value;
if (index == null || playlist.isEmpty) return;
mediaItem.add(playlist[index]);
logger.i(playlist);
});
}
#override
Future<void> addQueueItem(MediaItem mediaItem) async {
// manage Just Audio
final audioSource = _createAudioSource(mediaItem);
_playlist.add(audioSource);
// notify system
final newQueue = queue.value..add(mediaItem);
queue.add(newQueue);
}
#override
Future<void> removeQueueItemAt(int index) async {
// manage Just Audio
_playlist.removeAt(index);
// notify system
final newQueue = queue.value..removeAt(index);
queue.add(newQueue);
}
#override
Future<void> skipToQueueItem(int index) async {
if (index < 0 || index >= queue.value.length) return;
if (_player.shuffleModeEnabled) {
index = _player.shuffleIndices![index];
}
_player.seek(Duration.zero, index: index);
}
#override
Future<void> stop() async {
await _player.dispose();
return super.stop();
}
}
I tried including pageManager.play in the init section of Player and PageManager, but nothing happens. I think I need to set the audiosource as a concatenatingAudioSource, but I created a method to execute it and passed it _playlist, running it under _listenForCurrentSongIndexChanges(), but nothing happened.
Realized the play method was executing before the init method finished, so simple delay worked.
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 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')),
],
);
}
}
I am working on a web app where users can post stuffs and make them more accessible by associating the posts with tags. so my idea is similar to stackoverflow's way of giving tags to posts, I am creating a Textfield with which will accept only few tags(string values) which I will create from a list and users can put them in their post. But I aint getting how to implement this as textfield has only few keyboardtypes... and I what I want to achieve is if I entered a value from a that list then it should act like a chip text(tag).
or Is there any other way to do this,
your help is appreciated,
thank you
Yes, there is. you can use the flutter_tagging package on the PUB
It has supports for Web
The gif below explains what you want to achieve
You can find an implementation of a Chip Input Field type widget here:
Latest: https://gist.github.com/slightfoot/c6c0f1f1baca326a389a9aec47886ad6
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
// See: https://twitter.com/shakil807/status/1042127387515858949
// https://github.com/pchmn/MaterialChipsInput/tree/master/library/src/main/java/com/pchmn/materialchips
// https://github.com/BelooS/ChipsLayoutManager
void main() => runApp(ChipsDemoApp());
class ChipsDemoApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primaryColor: Colors.indigo,
accentColor: Colors.pink,
),
home: DemoScreen(),
);
}
}
class DemoScreen extends StatefulWidget {
#override
_DemoScreenState createState() => _DemoScreenState();
}
class _DemoScreenState extends State<DemoScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Material Chips Input'),
),
body: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
decoration: const InputDecoration(hintText: 'normal'),
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: ChipsInput<AppProfile>(
decoration: InputDecoration(prefixIcon: Icon(Icons.search), hintText: 'Profile search'),
findSuggestions: _findSuggestions,
onChanged: _onChanged,
chipBuilder: (BuildContext context, ChipsInputState<AppProfile> state, AppProfile profile) {
return InputChip(
key: ObjectKey(profile),
label: Text(profile.name),
avatar: CircleAvatar(
backgroundImage: NetworkImage(profile.imageUrl),
),
onDeleted: () => state.deleteChip(profile),
onSelected: (_) => _onChipTapped(profile),
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
);
},
suggestionBuilder: (BuildContext context, ChipsInputState<AppProfile> state, AppProfile profile) {
return ListTile(
key: ObjectKey(profile),
leading: CircleAvatar(
backgroundImage: NetworkImage(profile.imageUrl),
),
title: Text(profile.name),
subtitle: Text(profile.email),
onTap: () => state.selectSuggestion(profile),
);
},
),
),
),
],
),
);
}
void _onChipTapped(AppProfile profile) {
print('$profile');
}
void _onChanged(List<AppProfile> data) {
print('onChanged $data');
}
Future<List<AppProfile>> _findSuggestions(String query) async {
if (query.length != 0) {
return mockResults.where((profile) {
return profile.name.contains(query) || profile.email.contains(query);
}).toList(growable: false);
} else {
return const <AppProfile>[];
}
}
}
// -------------------------------------------------
const mockResults = <AppProfile>[
AppProfile('Stock Man', 'stock#man.com', 'https://d2gg9evh47fn9z.cloudfront.net/800px_COLOURBOX4057996.jpg'),
AppProfile('Paul', 'paul#google.com', 'https://mbtskoudsalg.com/images/person-stock-image-png.png'),
AppProfile('Fred', 'fred#google.com',
'https://media.istockphoto.com/photos/feeling-great-about-my-corporate-choices-picture-id507296326'),
AppProfile('Bera', 'bera#flutter.io',
'https://upload.wikimedia.org/wikipedia/commons/7/7c/Profile_avatar_placeholder_large.png'),
AppProfile('John', 'john#flutter.io',
'https://upload.wikimedia.org/wikipedia/commons/7/7c/Profile_avatar_placeholder_large.png'),
AppProfile('Thomas', 'thomas#flutter.io',
'https://upload.wikimedia.org/wikipedia/commons/7/7c/Profile_avatar_placeholder_large.png'),
AppProfile('Norbert', 'norbert#flutter.io',
'https://upload.wikimedia.org/wikipedia/commons/7/7c/Profile_avatar_placeholder_large.png'),
AppProfile('Marina', 'marina#flutter.io',
'https://upload.wikimedia.org/wikipedia/commons/7/7c/Profile_avatar_placeholder_large.png'),
];
class AppProfile {
final String name;
final String email;
final String imageUrl;
const AppProfile(this.name, this.email, this.imageUrl);
#override
bool operator ==(Object other) =>
identical(this, other) || other is AppProfile && runtimeType == other.runtimeType && name == other.name;
#override
int get hashCode => name.hashCode;
#override
String toString() {
return 'Profile{$name}';
}
}
// -------------------------------------------------
typedef ChipsInputSuggestions<T> = Future<List<T>> Function(String query);
typedef ChipSelected<T> = void Function(T data, bool selected);
typedef ChipsBuilder<T> = Widget Function(BuildContext context, ChipsInputState<T> state, T data);
class ChipsInput<T> extends StatefulWidget {
const ChipsInput({
Key key,
this.decoration = const InputDecoration(),
#required this.chipBuilder,
#required this.suggestionBuilder,
#required this.findSuggestions,
#required this.onChanged,
this.onChipTapped,
}) : super(key: key);
final InputDecoration decoration;
final ChipsInputSuggestions findSuggestions;
final ValueChanged<List<T>> onChanged;
final ValueChanged<T> onChipTapped;
final ChipsBuilder<T> chipBuilder;
final ChipsBuilder<T> suggestionBuilder;
#override
ChipsInputState<T> createState() => ChipsInputState<T>();
}
class ChipsInputState<T> extends State<ChipsInput<T>> implements TextInputClient {
static const kObjectReplacementChar = 0xFFFC;
Set<T> _chips = Set<T>();
List<T> _suggestions;
int _searchId = 0;
FocusNode _focusNode;
TextEditingValue _value = TextEditingValue();
TextInputConnection _connection;
String get text => String.fromCharCodes(
_value.text.codeUnits.where((ch) => ch != kObjectReplacementChar),
);
bool get _hasInputConnection => _connection != null && _connection.attached;
void requestKeyboard() {
if (_focusNode.hasFocus) {
_openInputConnection();
} else {
FocusScope.of(context).requestFocus(_focusNode);
}
}
void selectSuggestion(T data) {
setState(() {
_chips.add(data);
_updateTextInputState();
_suggestions = null;
});
widget.onChanged(_chips.toList(growable: false));
}
void deleteChip(T data) {
setState(() {
_chips.remove(data);
_updateTextInputState();
});
widget.onChanged(_chips.toList(growable: false));
}
#override
void initState() {
super.initState();
_focusNode = FocusNode();
_focusNode.addListener(_onFocusChanged);
}
void _onFocusChanged() {
if (_focusNode.hasFocus) {
_openInputConnection();
} else {
_closeInputConnectionIfNeeded();
}
setState(() {
// rebuild so that _TextCursor is hidden.
});
}
#override
void dispose() {
_focusNode?.dispose();
_closeInputConnectionIfNeeded();
super.dispose();
}
void _openInputConnection() {
if (!_hasInputConnection) {
_connection = TextInput.attach(this, TextInputConfiguration());
_connection.setEditingState(_value);
}
_connection.show();
}
void _closeInputConnectionIfNeeded() {
if (_hasInputConnection) {
_connection.close();
_connection = null;
}
}
#override
Widget build(BuildContext context) {
var chipsChildren = _chips
.map<Widget>(
(data) => widget.chipBuilder(context, this, data),
)
.toList();
final theme = Theme.of(context);
chipsChildren.add(
Container(
height: 32.0,
child: Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Text(
text,
style: theme.textTheme.subhead.copyWith(
height: 1.5,
),
),
_TextCaret(
resumed: _focusNode.hasFocus,
),
],
),
),
);
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
//mainAxisSize: MainAxisSize.min,
children: <Widget>[
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: requestKeyboard,
child: InputDecorator(
decoration: widget.decoration,
isFocused: _focusNode.hasFocus,
isEmpty: _value.text.length == 0,
child: Wrap(
children: chipsChildren,
spacing: 4.0,
runSpacing: 4.0,
),
),
),
Expanded(
child: ListView.builder(
itemCount: _suggestions?.length ?? 0,
itemBuilder: (BuildContext context, int index) {
return widget.suggestionBuilder(context, this, _suggestions[index]);
},
),
),
],
);
}
#override
void updateEditingValue(TextEditingValue value) {
final oldCount = _countReplacements(_value);
final newCount = _countReplacements(value);
setState(() {
if (newCount < oldCount) {
_chips = Set.from(_chips.take(newCount));
}
_value = value;
});
_onSearchChanged(text);
}
int _countReplacements(TextEditingValue value) {
return value.text.codeUnits.where((ch) => ch == kObjectReplacementChar).length;
}
#override
void performAction(TextInputAction action) {
_focusNode.unfocus();
}
void _updateTextInputState() {
final text = String.fromCharCodes(_chips.map((_) => kObjectReplacementChar));
_value = TextEditingValue(
text: text,
selection: TextSelection.collapsed(offset: text.length),
composing: TextRange(start: 0, end: text.length),
);
_connection.setEditingState(_value);
}
void _onSearchChanged(String value) async {
final localId = ++_searchId;
final results = await widget.findSuggestions(value);
if (_searchId == localId && mounted) {
setState(() => _suggestions = results.where((profile) => !_chips.contains(profile)).toList(growable: false));
}
}
}
class _TextCaret extends StatefulWidget {
const _TextCaret({
Key key,
this.duration = const Duration(milliseconds: 500),
this.resumed = false,
}) : super(key: key);
final Duration duration;
final bool resumed;
#override
_TextCursorState createState() => _TextCursorState();
}
class _TextCursorState extends State<_TextCaret> with SingleTickerProviderStateMixin {
bool _displayed = false;
Timer _timer;
#override
void initState() {
super.initState();
_timer = Timer.periodic(widget.duration, _onTimer);
}
void _onTimer(Timer timer) {
setState(() => _displayed = !_displayed);
}
#override
void dispose() {
_timer.cancel();
super.dispose();
}
#override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return FractionallySizedBox(
heightFactor: 0.7,
child: Opacity(
opacity: _displayed && widget.resumed ? 1.0 : 0.0,
child: Container(
width: 2.0,
color: theme.primaryColor,
),
),
);
}
}
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)
],
),
),
),
),
),
);
});
}
}