I set up flutter_map succesfully, but when I try to filter my map by "City" for example I am getting this error:
The following LateError was thrown building FutureBuilder<List<dynamic>>(dependencies: [MediaQuery],
state: _FutureBuilderState<List<dynamic>>#cb20d):
LateInitializationError: Field '_state' has already been initialized.
The relevant error-causing widget was:
FutureBuilder<List<dynamic>>
My flutter_map implementation is as follow:
late MapController mapController;
Future<List<dynamic>>? futureLocs;
Future<List<dynamic>>? futureLocsFilteredByCity;
bool? isFilterByCity;
PageController pageController = PageController();
double currentZoom = 10.0;
PanelController panelController = PanelController();
#override
void initState() {
super.initState();
mapController = MapController();
pageController = PageController(viewportFraction: 0.7, initialPage: 0);
futureLocs = getAllDogsLocation();
}
Widget build(BuildContext context) {
return Scaffold(
appBar: GenericAppBar(context,
backbutton: true,
title: 'Dogs map',
filterbutton: true, onfilterpress: () {
showDialog(
context: context,
builder: (context) {
return CitiesToFilter(
futureLocs: futureLocs,
onCityPress: (city) {
setState(() {
isFilterByCity = true;
futureLocs = getDogLocationByCity(city);
futureLocs!.then((value) {
if (value.isNotEmpty) {
var latlong = LatLng(
value[0]['latitude'], value[0]['longitude']);
widget.lat = latlong.latitude;
widget.long = latlong.longitude;
}
});
});
});
});
}),
body: FlutterMapCusto(
futureLocs: futureLocs,
mapController: mapController,
pageController: pageController,
lat: widget.lat,
long: widget.long,
panelcontroller: panelController,
),
floatingActionButtonLocation: FloatingActionButtonLocation.endFloat,
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton(
heroTag: Text('CurrentLoc'),
onPressed: () {
setState(() {
mapController.move(
LatLng(widget.lat, widget.long), currentZoom);
});
},
tooltip: 'Current location',
child: const Icon(Icons.location_history),
),
],
));
}
}
where FlutterMapCusto widget is defined as a normal widget with FlutterMap class. I am not including it to avoid boilerplate code here since it is a basic implementation found in the package web. I think the error is coming from mapController..
On the other hand I am fetching my new data filtered by city with the function "getDogLocationByCity(city)" updating my future.
Then we have CitiesToFilter widget:
Widget build(BuildContext context) {
return AlertDialog(
title: Text('Filter'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('Filter by City'),
FloatingActionButton(onPressed: () {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text('Cities:'),
content: SizedBox(
width: MediaQuery.of(context).size.width,
child: FutureBuilder(
future: widget.futureLocs,
builder: (BuildContext context,
AsyncSnapshot<List<dynamic>> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.waiting:
return Text('Loading...');
case ConnectionState.active:
{
return const Center(
child: Text('Loading...'),
);
}
case ConnectionState.done:
if (snapshot.hasError) {
return Text(
'Error: ${snapshot.error}');
}
if (snapshot.hasData) {
return ListView.builder(
itemCount:
snapshot.data!.length,
itemBuilder: (context, index) {
return TextButton(
onPressed: () {
setState(() {
widget.onCityPress( snapshot.data![index]['CityName'] );
});
Navigator.pop(context);
},
child: Text(
snapshot.data![index]
['CityName']));
});
} else {
return const Text(
'No data available');
}
}
},
),
),
);
});
})
],
),
actions: [
TextButton(
onPressed: () {
Navigator.pop(context);
},
child: Text('Close'))
],
);
}
Future method to fetch the data shown in map. This is just a wrapper developed from Back4App to interact with its MongoDb database:
Future<List<dynamic>> getAllDogsLocation() async {
await Future.delayed(const Duration(seconds: 2), () {});
QueryBuilder<ParseObject> queryTodo =
QueryBuilder<ParseObject>(ParseObject('Todo'));
// queryTodo.includeObject(['latitude']);
final ParseResponse apiResponse = await queryTodo.query();
if (apiResponse.success && apiResponse.results != null) {
return apiResponse.results as List<ParseObject>;
} else {
throw Exception('Failed to load data');
}
}
Related
I'm reading a list from my local Json file and i'm trying to sort the list by either number or alphabet and update the UI depend on user choice.
I'm able to filter the List but not really sure how to update the UI when a user press a either button so I would be really appreciated if I can get any help or suggestion.
Right now, I just called one function in my FutureBuilder and not sure how to modify it.
class _SawnawkScreenState extends State<SawnawkScreen> {
#override
Widget build(BuildContext context) {
bool isSwitched = false;
return Scaffold(
body: FutureBuilder(
future: SortbyNumber(), // Need to do something here
builder: (context, data) {
if (data.hasError) {
return Center(child: Text("${data.error}"));
} else if (data.hasData) {
var items = data.data as List<SawnAwkModel>;
return ListView.builder(
itemCount: items == null ? 0 : items.length,
itemBuilder: (context, index) {
return SawnawkCardWidget(
id: items[index].id!,
);
});
} else {
return Center(child: CircularProgressIndicator());
}
},
),
floatingActionButton: SpeedDial(
children: [
SpeedDialChild(
child: Icon(Icons.sort_by_alpha_outlined),
backgroundColor: Colors.white,
label: 'Sort by alphabet',
onTap: () => {
print('sort by alphabet'),
//Do something here
}),
SpeedDialChild(
child: Icon(Icons.sort_by_number),
backgroundColor: Colors.white,
label: 'Sort by number',
onTap: () => {
print('sort by number'),
//Do something here
}),
],
),
);
}
}
Future<List<SawnAwkModel>> SortbyNumber() async {
final jsondata =
await rootBundle.rootBundle.loadString('assets/data/sawnawk_data.json');
final list = json.decode(jsondata) as List<dynamic>;
return list.map((e) => SawnAwkModel.fromJson(e)).toList();
}
Future<List<SawnAwkModel>> SortbyAlphabet() async {
final jsondata =
await rootBundle.rootBundle.loadString('assets/data/sawnawk_data.json');
final list = json.decode(jsondata) as List<dynamic>;
List<SawnAwkModel> profileList =
list.map((e) => SawnAwkModel.fromJson(e)).toList();
profileList.sort((a, b) {
return a.titleFalam.toLowerCase().compareTo(b.titleFalam.toLowerCase());
});
return profileList;
}
In order to update the UI, the code that changes the UI must be in a setState({}) function. In your case, try this:
SpeedDialChild(
child: Icon(Icons.sort_by_alpha_outlined),
backgroundColor: Colors.white,
label: 'Sort by alphabet',
onTap: () => {
print('sort by alphabet'),
setState({
final sorted = await SortbyAlphabet()
//update widget contents with sorted value above
})
}),
Your current code if difficult to update the UI, I suggest storing the ListView.builder items in a variable accessible by the function you want to use to update the UI, and change the contents there, like this:
class _SawnawkScreenState extends State<SawnawkScreen> {
bool isSwitched = false;
List items = [];
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder(
future: SortbyNumber(), // Need to do something here
builder: (context, data) {
if (data.hasError) {
return Center(child: Text("${data.error}"));
} else if (data.hasData) {
items.addAll(data.data as List<SawnAwkModel>);
return ListView.builder(
itemCount: items == null ? 0 : items.length,
itemBuilder: (context, index) {
return SawnawkCardWidget(
id: items[index].id!,
);
});
} else {
return Center(child: CircularProgressIndicator());
}
},
),
floatingActionButton: SpeedDial(
children: [
SpeedDialChild(
child: Icon(Icons.sort_by_alpha_outlined),
backgroundColor: Colors.white,
label: 'Sort by alphabet',
onTap: () async {
setState({
print('sort by alphabet'),
final newItems = await SortbyAlphabet();
items.clear();
items.addAll(newItems);
})
}),
SpeedDialChild(
child: Icon(Icons.sort_by_number),
backgroundColor: Colors.white,
label: 'Sort by number',
onTap: () async {
setState({
print('sort by number'),
final newItems = await SortbyNumber();
items.clear();
items.addAll(newItems);
})
}),
],
),
);
}
}
Future<List<SawnAwkModel>> SortbyNumber() async {
final jsondata =
await rootBundle.rootBundle.loadString('assets/data/sawnawk_data.json');
final list = json.decode(jsondata) as List<dynamic>;
return list.map((e) => SawnAwkModel.fromJson(e)).toList();
}
Future<List<SawnAwkModel>> SortbyAlphabet() async {
final jsondata =
await rootBundle.rootBundle.loadString('assets/data/sawnawk_data.json');
final list = json.decode(jsondata) as List<dynamic>;
List<SawnAwkModel> profileList =
list.map((e) => SawnAwkModel.fromJson(e)).toList();
profileList.sort((a, b) {
return a.titleFalam.toLowerCase().compareTo(b.titleFalam.toLowerCase());
});
return profileList;
}
Please refer to this https://stackoverflow.com/a/70202810/15215450 for example on ValueListenable Builder
Please refer to the below code
final ValueNotifier<List> items = ValueNotifier([]);
floatingActionButton: SpeedDial(
children: [
SpeedDialChild(
child: Icon(Icons.sort_by_alpha_outlined),
backgroundColor: Colors.white,
label: 'Sort by alphabet',
onTap: () => {
print('sort by alphabet'),
//Do something here
items.value.clear();
items.value = await SortbyAlphabet();
items.notifyListeners();
}),
SpeedDialChild(
child: Icon(Icons.sort_by_number),
backgroundColor: Colors.white,
label: 'Sort by number',
onTap: () => {
print('sort by number'),
//Do something here
items.value.clear();
items.value = await SortbyAlphabet();
items.notifyListeners();
}),
],
),
ValueListenableBuilder(
valueListenable: isSwitched,
builder: (context, snapshot, child) {
return ListView.builder(
itemCount: items.value == null ? 0 : items.value.length,
itemBuilder: (context, index) {
return SawnawkCardWidget(
id: items.value[index].id!,
);
});
}));
Try this
late Future<dynamic> _future;
#override
void initState() {
_future = getDoctors();
}
class _SawnawkScreenState extends State<SawnawkScreen> {
#override
Widget build(BuildContext context) {
bool isSwitched = false;
return Scaffold(
body: FutureBuilder(
future: _future, // Need to do something here
builder: (context, data) {
if (data.hasError) {
return Center(child: Text("${data.error}"));
} else if (data.hasData) {
var items = data.data as List<SawnAwkModel>;
return ListView.builder(
itemCount: items == null ? 0 : items.length,
itemBuilder: (context, index) {
return SawnawkCardWidget(
id: items[index].id!,
);
});
} else {
return Center(child: CircularProgressIndicator());
}
},
),
floatingActionButton: SpeedDial(
children: [
SpeedDialChild(
child: Icon(Icons.sort_by_alpha_outlined),
backgroundColor: Colors.white,
label: 'Sort by alphabet',
onTap: () => {
print('sort by alphabet'),
//Do something here
setState(() {. // call setstate to refresh futurebuilder
_future = SortbyAlphabet();
}),
}),
SpeedDialChild(
child: Icon(Icons.sort_by_number),
backgroundColor: Colors.white,
label: 'Sort by number',
onTap: () => {
print('sort by number'),
//Do something here
}),
],
),
);
}
}
Future<List<SawnAwkModel>> SortbyNumber() async {
final jsondata =
await rootBundle.rootBundle.loadString('assets/data/sawnawk_data.json');
final list = json.decode(jsondata) as List<dynamic>;
return list.map((e) => SawnAwkModel.fromJson(e)).toList();
}
Future<List<SawnAwkModel>> SortbyAlphabet() async {
final jsondata =
await rootBundle.rootBundle.loadString('assets/data/sawnawk_data.json');
final list = json.decode(jsondata) as List<dynamic>;
List<SawnAwkModel> profileList =
list.map((e) => SawnAwkModel.fromJson(e)).toList();
profileList.sort((a, b) {
return a.titleFalam.toLowerCase().compareTo(b.titleFalam.toLowerCase());
});
return profileList;
}
I have a problem about filtering the data that I get from the json response. I already put the service initialization in initSate instead of future in FutureBuilder but it's still not working. Maybe I miss something in the filter function?
initState :
void initState() {
doctorService = DoctorService();
_doctorData = doctorService.getDoctors();
super.initState();
}
FutureBuilder:
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Daftar Dokter"),),
body:
FutureBuilder<List<Doctor>>(
future: _doctorData,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if(snapshot.hasError) {
print(snapshot);
return Center(
child: Text("Error"),
);
}
else if (snapshot.hasData){
doctors = snapshot.data;
tempDoctorData = List.from(doctors);
return _buildListView(tempDoctorData);
}
else {
return Center(
child: Container(),
);
}
},
),
floatingActionButton: FloatingActionButton(
child: Icon(
Icons.add,
color: Colors.white,
),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (BuildContext buildContext)=>FormAlbum())
);
},
),
);
}
And this is the filter function
onItemChanged(String value) {
setState(() {
tempDoctorData = doctors.where((element) => element.name.toLowerCase().contains(value.toLowerCase())).toList();
});
}
I'm using FutureProvider to fetch data from a local db with SQflite, and then render a graph in the Consumer child. However, when loading the app, during a brief period an error is shown :
The following StateError was thrown building Consumer<List<Map<String, dynamic>>>(dirty,
dependencies: [_InheritedProviderScope<List<Map<String, dynamic>>>]):
Bad state: No element
After the graph is rendered fine.
How can I catch this loading state so the error disappears and I can show a CircularProgressIndicator() ?
Parent
FutureProvider<List<Map<String, dynamic>>>(
create: (context) {
return RecordsDatabase.instance.getRecords();
},
catchError: (context, error) {
print("error: ${error.toString()}");
return [];
},
initialData: [],
child: HomeCustom(),
)
Child
#override
Widget build(BuildContext context) {
return Consumer<List<Map<String, dynamic>>>(
builder: (context, records, child) {
GraphState graph =GraphState(records: records, context: context);
return ChangeNotifierProvider<GraphState>(
create: (_) => graph,
child: Scaffold(
backgroundColor: Colors.black,
body: Stack(children: [
Center(
child: graph.records.isEmpty
? Text(
'No Records',
style: TextStyle(color: Colors.white, fontSize: 24),
)
: MyGraph()),
Align(
alignment: Alignment.bottomRight,
child: Padding(
padding: const EdgeInsets.only(right: 30, bottom: 50),
child: FloatingActionButton(
child: Icon(Icons.add),
onPressed: _setVisible,
),
),
)
]),
),
);
});
}
}
In the Consumer, check the records value first then return the appropriate widget.
Sample...
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: FutureProvider<List<Map<String, dynamic>>?>(
create: (_) => _getRecords(),
initialData: null,
catchError: (_, __) => <Map<String, dynamic>>[
{'error': 'Something went wrong'}
],
child: HomePage(),
),
);
}
Future<List<Map<String, dynamic>>> _getRecords() async {
final bool isError = false; // set to "true" to check error case
await Future<void>.delayed(const Duration(seconds: 5));
if (isError) {
throw Exception();
}
return <Map<String, dynamic>>[
<String, int>{'item': 1},
<String, String>{'itemTxt': 'one'},
];
}
}
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Consumer<List<Map<String, dynamic>>?>(
builder: (_, List<Map<String, dynamic>>? records, __) {
if (records == null) {
return const CircularProgressIndicator();
} else if (records.isNotEmpty &&
records.first.containsKey('error')) {
return Text(records.first['error'] as String);
}
return Text(records.toString());
},
),
),
);
}
}
I have a StreamBuilder inside my Widget build of UserListDart:
StreamBuilder(
stream: stream.asStream(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if(snapshot.hasData) {
return Expanded(
child: ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text(
snapshot.data[index].firstname + " " +
snapshot.data[index].lastname
),
onTap: () {
Navigator.of(context).push(DetailScreenDart(snapshot.data[index]));
},
);
}
)
);
}
}
...
)
The Stream is defined in the initState:
Future<List> stream;
#override
void initState() {
super.initState();
stream = fetchPost();
}
The fetchPost() is an api call:
Future<List<User>> fetchPost() async {
final response = await http.get('url');
final jsonResponse = json.decode(response.body);
List<User> users = [];
for(var u in jsonResponse){
User user = User(
firstname: u["firstname"],
lastname: u["lastname"],
);
users.add(user);
}
return users;
}
I Navigate to another Page to change for example the firstname (api get updated) and I Navigate back to the UserList:
Navigator.pushReplacement(
context,
new MaterialPageRoute(builder: (context) => new UserListDart())
).then((onValue) {
fetchPost();
});
But the StreamBuilder won't get updated and I don't know why.
Note:
I think the StreamBuilder don't realise that a change has happend when I navigate back. It only applies the changes if I reopen the Page..
You should be using setState and updating your stream variable with the result of the fetchList() call:
Navigator.pushReplacement(
context,
new MaterialPageRoute(builder: (context) => new UserListDart())
).then((onValue) {
setState((){
stream = fetchPost();
});
});
Here's a working example of what you want to achieve:
class StreamBuilderIssue extends StatefulWidget {
#override
_StreamBuilderIssueState createState() => _StreamBuilderIssueState();
}
class _StreamBuilderIssueState extends State<StreamBuilderIssue> {
Future<List<String>> futureList;
List<String> itemList = [
'item 1',
'item 1',
'item 1',
'item 1',
'item 1',
];
#override
void initState() {
futureList = fetchList();
super.initState();
}
#override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Expanded(
child: Center(
child: StreamBuilder(
stream: futureList.asStream(),
builder: (context, snapshot){
if(snapshot.hasData){
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index){
return Text(snapshot.data[index]);
},
);
}else{
return CircularProgressIndicator();
}
},
),
),
),
RaisedButton(
onPressed: goToAnotherView,
child: Text('Next View'),
),
RaisedButton(
onPressed: addItem,
child: Text('AddItem'),
)
],
),
);
}
Future<List<String>> fetchList(){
return Future.delayed(Duration(seconds: 2), (){
return itemList;
});
}
void goToAnotherView(){
Navigator.push(context, MaterialPageRoute(
builder: (context){
return StreamBuilderIssueNewView(addItem);
})
).then((res){
setState(() {
futureList = fetchList();
});
});
}
void addItem(){
itemList.add('anotherItem');
}
}
class StreamBuilderIssueNewView extends StatelessWidget {
final Function buttonAction;
StreamBuilderIssueNewView(this.buttonAction);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Column(
children: <Widget>[
Text('New view'),
RaisedButton(
onPressed: buttonAction,
child: Text('AddItem'),
)
],
),
),
);
}
}
By the way, you could also just use a FutureBuilder as your are not using a real Stream here, just an api fetch and you have to update with setState anyway.
I've been stuck for several days looking for a solution and I can't find it. I need to play mp3 files one behind the other using AudioPlayers and it is impossible for me. I'm using the only tool (as far as I know) that gives you the lib to do this kind of thing, but I can't find how to do it. Thanks for the help.
playLocal(List list) async {
list.forEach((f) async {
final file = new File('${(await getTemporaryDirectory()).path}/voice.mp3');
await file.writeAsBytes((await loadAsset(f)).buffer.asUint8List());
bool finish = false;
if (player.state == null || player.state == AudioPlayerState.COMPLETED) {
await player.play(file.path, isLocal:true);
}
if (player.state != null && player.state != AudioPlayerState.COMPLETED) {
while(!finish){
player.onPlayerCompletion.listen((event){
finish = true;
});
if (finish) {await player.play(file.path, isLocal:true);}
}
}
});
}
You can use package https://pub.dev/packages/assets_audio_player
In assetsAudioPlayer.finished.listen call _next()
code snippet
void _next() {
if (_assetsAudioPlayer.playlist != null) {
_assetsAudioPlayer.playlistNext();
} else {
_currentAssetPosition++;
_open(_currentAssetPosition);
}
}
#override
void initState() {
super.initState();
_assetsAudioPlayer.finished.listen((finished) {
print("paly next");
_next();
});
}
working demo after song1.mp3 finish auto play song2.mp3
full code
import 'package:assets_audio_player/assets_audio_player.dart';
import 'package:assets_audio_player_example/asset_audio_player_icons.dart';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final assets = <String>[
"assets/audios/song1.mp3",
"assets/audios/song2.mp3",
"assets/audios/song3.mp3",
];
final AssetsAudioPlayer _assetsAudioPlayer = AssetsAudioPlayer();
var _currentAssetPosition = -1;
void _open(int assetIndex) {
_currentAssetPosition = assetIndex % assets.length;
_assetsAudioPlayer.open(assets[_currentAssetPosition]);
}
void _playPause() {
_assetsAudioPlayer.playOrPause();
}
void _next() {
if (_assetsAudioPlayer.playlist != null) {
_assetsAudioPlayer.playlistNext();
} else {
_currentAssetPosition++;
_open(_currentAssetPosition);
}
}
void _prev() {
if (_assetsAudioPlayer.playlist != null) {
_assetsAudioPlayer.playlistPrevious();
} else {
_currentAssetPosition--;
_open(_currentAssetPosition);
}
}
#override
void initState() {
super.initState();
_assetsAudioPlayer.finished.listen((finished) {
print("paly next");
_next();
});
}
#override
void dispose() {
_assetsAudioPlayer.stop();
super.dispose();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Plugin example app'),
),
body: Padding(
padding: const EdgeInsets.only(bottom: 48.0),
child: Column(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
RaisedButton(
onPressed: () {
_assetsAudioPlayer
.openPlaylist(Playlist(assetAudioPaths: this.assets));
},
child: Text("Playlist test"),
),
Expanded(
child: StreamBuilder(
stream: _assetsAudioPlayer.current,
initialData: const PlayingAudio(),
builder: (BuildContext context,
AsyncSnapshot<PlayingAudio> snapshot) {
final PlayingAudio currentAudio = snapshot.data;
return ListView.builder(
itemBuilder: (context, position) {
return ListTile(
title: Text(assets[position].split("/").last,
style: TextStyle(
color: assets[position] ==
currentAudio.assetAudioPath
? Colors.blue
: Colors.black)),
onTap: () {
_open(position);
});
},
itemCount: assets.length,
);
},
),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
StreamBuilder(
stream: _assetsAudioPlayer.isLooping,
initialData: false,
builder:
(BuildContext context, AsyncSnapshot<bool> snapshot) {
return RaisedButton(
child: Text(snapshot.data ? "Looping" : "Not looping"),
onPressed: () {
_assetsAudioPlayer.toggleLoop();
},
);
},
),
SizedBox(width: 20),
RaisedButton(
child: Text("Seek to 2:00"),
onPressed: () {
_assetsAudioPlayer.seek(Duration(minutes: 2));
},
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
StreamBuilder(
stream: _assetsAudioPlayer.currentPosition,
initialData: const Duration(),
builder: (BuildContext context,
AsyncSnapshot<Duration> snapshot) {
Duration duration = snapshot.data;
return Text(durationToString(duration));
},
),
Text(" - "),
StreamBuilder(
stream: _assetsAudioPlayer.current,
builder: (BuildContext context,
AsyncSnapshot<PlayingAudio> snapshot) {
Duration duration = Duration();
if (snapshot.hasData) {
duration = snapshot.data.duration;
}
return Text(durationToString(duration));
},
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: [
IconButton(
onPressed: _prev,
icon: Icon(AssetAudioPlayerIcons.to_start),
),
StreamBuilder(
stream: _assetsAudioPlayer.isPlaying,
initialData: false,
builder:
(BuildContext context, AsyncSnapshot<bool> snapshot) {
return IconButton(
onPressed: _playPause,
icon: Icon(snapshot.data
? AssetAudioPlayerIcons.pause
: AssetAudioPlayerIcons.play),
);
},
),
IconButton(
icon: Icon(AssetAudioPlayerIcons.to_end),
onPressed: _next,
),
],
),
],
),
),
),
);
}
}
String durationToString(Duration duration) {
String twoDigits(int n) {
if (n >= 10) return "$n";
return "0$n";
}
String twoDigitMinutes =
twoDigits(duration.inMinutes.remainder(Duration.minutesPerHour));
String twoDigitSeconds =
twoDigits(duration.inSeconds.remainder(Duration.secondsPerMinute));
return "$twoDigitMinutes:$twoDigitSeconds";
}