How to dynamically populate MediaItem from firestore in audio_service? - just-audio

I have been trying to dynamically populate MediaItem with some audio data from firestore.
I am using the exact plugin example, but this time mediaItems is being sourced dynamically from firestore. I have reviewed my code multiple times, but I can't figure out what I am doing wrong.
Here are my attempts:
First I fetched the song data using a StreamBuilder and passed it as a DocumentSnapshot List to the AudioServicePlayer() page.
List<DocumentSnapshot> _list;
_list = snapshot.data.docs;
Flexible(
child: ListView.builder(
itemCount: 1,
itemBuilder: (BuildContext context, int index) {
return InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => JustAudioPlaylist(
songs: [_list[index]],
),
));
},
child:
Container(child: Center(child: Text('My Playlists'))),
);
}),
)
I successfully received the QueryDocumentSnapshot as expected. But when I tried to populate MediaItem with widget.songs List, it returns just a blank white page with no error. I can't figure out what I am doing wrong here;
class AudioServicePlayer extends StatefulWidget {
static const String id = 'audio-service';
List<DocumentSnapshot> songs = [];
AudioServicePlayer({this.songs});
#override
_AudioServicePlayerState createState() => _AudioServicePlayerState();
}
class _AudioServicePlayerState extends State<AudioServicePlayer> {
MediaLibrary _mediaLibrary = MediaLibrary();
#override
void initState() {
_mediaLibrary._items.addAll(widget.songs
.map((song) => MediaItem(
// This can be any unique id, but we use the audio URL for convenience.
id: song['song'],
album: "Science Friday",
title: song['songTitle'],
artist: song['artist']['artistName'],
duration: Duration(milliseconds: 5739820),
artUri: Uri.parse(song['songImage']).toString(),
))
.toList());
super.initState();
}

Related

How to passing data API with bottom navigation bar on flutter

I have an app that routes to a homescreen after login. This class contains a bottom navigation bar with 3 routes inside. body, forum, and profile. Inside body/ mainscreen i want to display an weather widget. I get repo code for add some weather widget. This weather widget need refresh/loading for initialize to get data from API, i want to put this loading class inside my Homescreen. but my problem is the bottom navigator bar not calling/showing on mainscreen/body. I think its only calling Get API DATA on this
Navigator.pushReplacementNamed(context, "/body", arguments: {
"weatherData": WeatherData.fromJson(data),
"selectedLocation": arguments
});
not Bottom navigation bar
_layoutPage = [Body(), Forum(), Profile()];
My question is How to merge bottom navigaton bar and this calling API data,
so every routes to body is calling loading to get api data and show bottom navigation bar.
this is my Homescreen
class HomeScreen extends StatefulWidget {
HomeScreen({Key key, this.title}) : super(key: key);
final String title;
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
int _selectedIndex = 0;
#override
void initState() {
Provider.of<FirebaseOperations>(context, listen: false)
.initUserData(context);
super.initState();
}
final _layoutPage = [Body(), Forum(), Profile()];
void _onTapItem(int index) {
setState(() {
_selectedIndex = index;
});
}
// Weather
String apiKey = "MY API KEY";
SimpleLocationResult arguments;
getData({lat, lon}) async {
String latitude = lat == null ? "-6.2146" : lat.toString();
String longitude = lon == null ? "106.8451" : lon.toString();
Response response = await get(
"https://api.openweathermap.org/data/2.5/weather?units=metric&lat=$latitude&lon=$longitude&appid=$apiKey");
Map data = jsonDecode(response.body);
Navigator.pushReplacementNamed(context, "/body", arguments: {
"weatherData": WeatherData.fromJson(data),
"selectedLocation": arguments
});
}
#override
Widget build(BuildContext context) {
//loading weather
arguments = ModalRoute.of(context).settings.arguments;
Future.delayed(
Duration(seconds: 1),
() => {
arguments != null
? getData(lat: arguments.latitude, lon: arguments.longitude)
: getData()
});
return Scaffold(
body: _layoutPage.elementAt(_selectedIndex),
bottomNavigationBar: BottomNavigationBar(
backgroundColor: Colors.white,
selectedItemColor: kDarkGreenColor,
unselectedItemColor: kMainColor,
selectedLabelStyle:
TextStyle(color: kMainColor, fontFamily: 'Roboto', fontSize: 14.0),
unselectedLabelStyle: TextStyle(
color: Colors.grey[600], fontFamily: 'Roboto', fontSize: 12.0),
showUnselectedLabels: true,
items: <BottomNavigationBarItem>[
BottomNavigationBarItem(icon: Icon(EvaIcons.home), label: 'Home'),
BottomNavigationBarItem(
icon: Icon(EvaIcons.messageSquare), label: 'Forum'),
BottomNavigationBarItem(
icon: Icon(EvaIcons.person), label: 'Profile'),
],
currentIndex: _selectedIndex,
onTap: _onTapItem,
),
);
}
}

How to dynamically populate AudioSource with multiple audio data from firestore in just_audio?

I have been trying to dynamically populate AudioSource.uri() with data from firestore.
I uploaded some songs into firestore database and I wanted to use the data for a just_audio playlist in my app. I have done everything possible, and I really am not sure why its not working.
I don't want to add the song urls and other data statically as shown in the plugin example.
Here are my attempts:
First I fetched the song data using a StreamBuilder and passed it as a DocumentSnapshot List to the JustAudioPlaylist() page;
List<DocumentSnapshot> _list;
_list = snapshot.data.docs;
Flexible(
child: ListView.builder(
itemCount: 1,
itemBuilder: (BuildContext context, int index) {
return InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => JustAudioPlaylist(
songs: [_list[index]],
),
));
},
child:
Container(child: Center(child: Text('My Playlists'))),
);
}),
)
Then, here's the JustAudioPlaylist page where I expected to retrieve and populate the AudioSource.uri().
class JustAudioPlaylist extends StatefulWidget {
final List songs;
JustAudioPlaylist({this.songs});
#override
_JustAudioPlaylistState createState() => _JustAudioPlaylistState();
}
class _JustAudioPlaylistState extends State<JustAudioPlaylist> {
AudioPlayer _player;
int _addedCount = 0;
var _playlist;
#override
void initState() {
_playlist
.addAll(widget.songs.map((song) => ConcatenatingAudioSource(children: [
AudioSource.uri(
Uri.parse(song['song']),
tag: AudioMetadata(
album: "Science Friday",
title: song['songTitle'],
artwork: song['songImage'],
),
),
])));
I am not sure why its not working, but it produces an error "addAll was called on null". Please can anyone help?
Your relevant code is:
_playlist.addAll(...);
The error means _playlist is null. That is, _playlist is an uninitialised variable and doesn't actually contain any playlist object. I can see you declare the variable so it starts off empty:
var _playlist;
But you never actually store anything into this variable, like _playlist = ...something.... So your _playlist variable starts off null and continues to remain null.
You could do this instead:
_playlist = ConcatenatingAudioSource(children: []);
// and then later...
_playlist.addAll(widget.songs.map(...etc...));
Although addAll is intended for dynamically modifying the playlist after it's already created. But in your case, you know which songs you want to play at initialisation time, so you may as well just initialise the playlist right at the beginning and you won't have to add to it later:
_playlist = ConcatenatingAudioSource(
children: widget.songs.map(...etc...)
);

Why doesn't my FutureBuilder return any data?

I have a api that returns a list of data.
When I retrieve that data I use a FutureBuilder to show the list of data.
But for some reason it won't show my data even though when I print the response I can see that I got a correct response.
This is the error that i got:
══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
I/flutter (11846): The following assertion was thrown building FutureBuilder<List<BasicDiskInfo>>(dirty, state:
I/flutter (11846): _FutureBuilderState<List<BasicDiskInfo>>#a0948):
I/flutter (11846): A build function returned null.
I/flutter (11846): The offending widget is: FutureBuilder<List<BasicDiskInfo>>
I/flutter (11846): Build functions must never return null. To return an empty space that causes the building widget to
I/flutter (11846): fill available room, return "new Container()". To return an empty space that takes as little room as
I/flutter (11846): possible, return "new Container(width: 0.0, height: 0.0)".
I don't know what to do. Help me?
API
static Future<List<BasicDiskInfo>> fetchAllDisks() async {
final response = await http.get('link');
if (response.statusCode == 200) {
Iterable list = json.decode(response.body);
var disks = new List<BasicDiskInfo>();
disks = list.map((model) => BasicDiskInfo.fromJson(model)).toList();
print(disks[0]);
return disks;
} else {
throw Exception('Failed to load disks');
}
}
Page
class Disks extends StatelessWidget {
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: API.fetchAllDisks(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.waiting:
return new CircularProgressIndicator();
default:
if (snapshot.hasError) {
return new Text('Error: ${snapshot.error}');
} else {
print(snapshot.data);
createListView(context, snapshot);
}
}
},
);
}
Widget createListView(BuildContext context, AsyncSnapshot snapshot) {
List<BasicDiskInfo> disks = snapshot.data;
return new ListView.builder(
itemCount: disks.length,
itemBuilder: (context, index) {
return GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SpecificDiskPage(
diskId: disks[index].id,
),
));
},
child: Card(
child: Column(
children: <Widget>[
Row(
children: <Widget>[
Text(disks[index].name),
Spacer(),
Text(disks[index].driveType),
Spacer(),
Text(disks[index].driveFormat),
],
),
Row(
children: <Widget>[
Text(disks[index].totalSize.toString()),
Spacer(),
Text(disks[index].totalFreeSpace.toString()),
],
),
],
),
),
);
},
);
}
}
BasicDiskInfo
class BasicDiskInfo {
int id;
String name;
String driveType;
String driveFormat;
int totalSize;
int totalFreeSpace;
BasicDiskInfo(
{this.id,
this.name,
this.driveType,
this.driveFormat,
this.totalSize,
this.totalFreeSpace});
factory BasicDiskInfo.fromJson(Map<String, dynamic> json) {
return BasicDiskInfo(
id: json['id'],
name: json['name'],
driveType: json['driveType'],
driveFormat: json['driveFormat'],
totalSize: json['totalSize'],
totalFreeSpace: json['totalFreeSpace']);
}
}
The FutureBuilder should return a list with the data from the api
There is an error in your build method. You didn't return createListView(context, snapshot); in your default case.

Trying to make radio buttons with using Streambuilder and Bloc in Flutter. but don't work

I tried to make radiobuttons by using Streambuilder and Bloc.
so I made streamcontroler and when radiobuttons clicked,
I made streamcontrl.add(value) implemented, but Streambuilder don't listen
that stream. I tested onchanged value of radio. and
Please figure out what's wrong with it.
This is full code.
import 'package:flutter/material.dart';
import 'dart:async';
void main(){
runApp(new MaterialApp(
home: new MyApp(),
));
}
class bloc {
StreamController <int> ctrl = StreamController() ;
get blocvalue => ctrl.stream;
void setvalue (value ) {
ctrl.sink.add(value) ; }
}
class MyApp extends StatefulWidget {
#override
_State createState() => new _State();
}
class _State extends State<MyApp>{
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Name here'),
),
body: new Container(
padding: new EdgeInsets.all(15.0),
child: new Center(
child: new Column(
children: <Widget>[
StreamBuilder(
stream: bloc().blocvalue,
initialData: 0,
builder: (BuildContext context, AsyncSnapshot <int> snapshot)
{
List<Widget> list = new List<Widget>();
for(int i = 0; i < 3; i++){
list.add(new RadioListTile(
value: i,
groupValue: snapshot.data,
onChanged: bloc().setvalue,
activeColor: Colors.green,
controlAffinity: ListTileControlAffinity.trailing,
title: new Text('Item: ${i}'),
dense: true,
// subtitle: new Text('sub title'),
));
}
return Column(children: list,); })
],
),
),
),
);
}
}
As mentioned by pksink in the comments, you're creating a new bloc inside build by calling setting it in StreamBuilder as bloc().blocValue. What you can do here is declare it as final myBloc = bloc(); outside of the bloc and set it on your StreamBuilder as myBloc.blocValue. With this, a new instance of bloc won't be created with every rebuild of Widget build.

Flutter Shared Preferences saving values, not displaying

I have a list of courses. The user marks each course complete using a checkbox on the ListTile.
I implemented Shared Preferences so the list of completed courses persists when the user closes the app.
The values are saving, but when the app is closed (in the emulator or through the IDE) and reopened, the UI shows the value as false (Even when the Terminal says the value is True).
When I hot restart, the UI shows the value as True (Which was expected from the start). I haven't been able to get the UI to show correctly using the emulator buttons or on a device.
How can I get the UI to show the values correctly right away?
SharedPreferences prefs;
void getResult(Course course) async {
prefs = await SharedPreferences.getInstance();
results[course.courseResult] = prefs.getBool(course.courseResult) ?? false;
print('${course.courseTitle} Result: ${results[course.courseResult]}');
setState(() {
results[course.courseResult];
});
}
Future<bool> setResult(Course course) async {
prefs = await SharedPreferences.getInstance();
print ('${course.courseTitle} SET TO ${results[course.courseResult]}');
return prefs.setBool(course.courseResult, results[course.courseResult]);
}
initState() {
super.initState();
getResult(widget.entry);
}
Future onChanged(bool value, Course course) {
setState(() {
results[course.courseResult] = value;
});
return setResult(course);
}
Here is the full code (Though I did shorten the lists for space purposes, and left out the pages that aren't affected by this error...)
import 'package:flutter/material.dart';
import 'main.dart';
import 'CourseList.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'dart:async';
import 'package:url_launcher/url_launcher.dart';
import 'package:intl/intl.dart';
class LearningPlan extends StatefulWidget{
LearningPlanState createState() => new LearningPlanState();
}
class LearningPlanState extends State<LearningPlan> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: MyAppBar(
title: Text('Learning Plan'),
),
drawer: MyDrawer(),
body: ListView.builder(
itemBuilder: (BuildContext context, int index) =>
new CourseTile(courseList[index]),
itemCount: courseList.length,
),
);
}
}
class CourseTile extends StatefulWidget {
CourseTile(this.entry);
final Course entry;
CourseTileState createState() => new CourseTileState();
}
class CourseTileState extends State<CourseTile> {
//Detail Card
Future<Null> _launched; // ignore: unused_field
Future<Null> _launchInWebViewOrVC(String url) async {
if (await canLaunch(url)) {
await launch(url, forceSafariVC: false, forceWebView: false);
} else {
throw 'Could not launch $url';
}
}
Widget selfDirectedURL(Course course) {
if (course.courseMethod == 'Self-Directed') {
return new IconButton(
icon: Icon(Icons.cloud_download),
onPressed: () => setState(() {
_launched = _launchInWebViewOrVC(course.courseURL);
}),
);
} else {
return new Container();
}
}
Future<Null> courseDetails(Course course) async {
await showDialog(
context: context,
child: new SimpleDialog(
title: Text(course.courseTitle),
children: <Widget>[
Stack(
children: <Widget>[
Center(child: Image.asset(course.courseImage,
colorBlendMode: BlendMode.lighten,
color: fkBlue25,
height: 200.0,
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(course.courseDescription),
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
selfDirectedURL(course),
FlatButton(
onPressed: (){
Navigator.pop(context);
},
child: Text('OK'),
),
],
),
],
));
}
//CheckBox Constructors
SharedPreferences prefs;
void getResult(Course course) async {
prefs = await SharedPreferences.getInstance();
results[course.courseResult] = prefs.getBool(course.courseResult) ?? false;
print('${course.courseTitle} Result: ${results[course.courseResult]}');
setState(() {
results[course.courseResult];
});
}
Future<bool> setResult(Course course) async {
prefs = await SharedPreferences.getInstance();
print ('${course.courseTitle} SET TO ${results[course.courseResult]}');
return prefs.setBool(course.courseResult, results[course.courseResult]);
}
initState() {
super.initState();
getResult(widget.entry);
}
Future onChanged(bool value, Course course) async {
final result = await setResult(course);
setState(() {
results[course.courseResult] = value;
});
return result;
}
//Main Tile
Widget buildTiles(Course course) {
return Card(
shape: Border.all(
color: fkBlue,
),
margin: EdgeInsets.all(16.0),
elevation: 8.0,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: ListTile(
title: Text(course.courseTitle),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(course.courseCode),
Text(course.courseMethod)
],
),
leading: SizedBox(
height: 60.0,
width: 60.0,
child: Image.asset(course.courseImage)),
trailing: Column(
children: <Widget>[
Text(results[course.courseResult] ? 'Complete' : 'Incomplete',
),
Checkbox(
value: results[course.courseResult],
onChanged: (bool value) {
onChanged(value, course);
if (value == true) {
snackBarCompleted(course);
} else {
snackBarUnCompleted(course);
}
},
),
]
),
onTap: () {
courseDetails(course);
}
),),
);
}
#override
Widget build(BuildContext context) {
return buildTiles(widget.entry);
}
void snackBarCompleted(course) {
Scaffold.of(context).showSnackBar(
SnackBar(content: Text(
'${course.courseTitle} completed on ${DateFormat.yMd().format(DateTime.now()).toString()}'
),
backgroundColor: fkBlue,
duration: Duration(seconds: 3),
),
);
}
void snackBarUnCompleted(course) {
Scaffold.of(context).showSnackBar(
SnackBar(content: Text('${course.courseTitle} no longer marked \"Complete\"'
),
duration: Duration(seconds: 3),
),
);
}
}
//Learning Schedule Page
class LearningSchedule extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: MyAppBar(
title: Text('Schedule'),
),
drawer: MyDrawer(),
body: ListView.builder(
itemBuilder: (BuildContext context, int index) =>
new LearningScheduleBuilder(courseList[index]),
itemCount: courseList.length,
),
);
}
}
class LearningScheduleBuilder extends StatelessWidget {
LearningScheduleBuilder(this.entry);
final Course entry;
Widget buildList (Course course) {
return Text(course.courseTitle,
style: new TextStyle(color: results[course.courseResult] ? Colors.grey : fkBlue),);
}
#override
Widget build(BuildContext context) {
return buildList(entry);
}
}
final List<Course> courseList = <Course>[
new Course(
courseTitle: 'Company Orientation',
coursePreReq: 'N/A',
courseCode: 'HR',
courseURL: '',
courseMethod: 'Facilitator-Led',
courseImage: 'assets/courseImage/logo.png',
courseDescription:
'Company overview; Benefits package and documents; Ethics and Compliance Training, Introduction to learning programs; Computer orientation; Lab tour; Safety training.',
courseAudience: 'BCAE BCCC ITAE ITCC TCTAE TCTCC PlasmaCC PlasmaAE',
courseResult: 'result1',
),
new Course(
courseTitle: 'Intro to Learning Program',
coursePreReq: 'N/A',
courseCode: 'Nicole Asma',
courseURL: '',
courseMethod: 'Facilitator-Led',
courseImage: 'assets/courseImage/logo.png',
courseDescription:
'Overview of onboarding program; Components of North America University; Support available for all learning units; introduction to Learning and Development Team Overview of WebEx calls.',
courseAudience: 'BCAE BCCC ITAE ITCC TCTAE TCTCC PlasmaCC PlasmaAE',
courseResult: 'result2',
),
class Course {
final String courseTitle;
final String coursePreReq;
final String courseCode;
final String courseDescription;
final String courseImage;
final String courseMethod;
final String courseURL;
final String courseAudience;
final String courseResult;
const Course({
this.courseTitle,
this.coursePreReq,
this.courseCode,
this.courseDescription,
this.courseImage,
this.courseMethod,
this.courseURL,
this.courseAudience,
this.courseResult,
});
Course.fromMap(Map<String, dynamic> map)
: courseTitle = map['courseTitle'],
coursePreReq = map['coursePreReq'],
courseCode = map['courseCode'],
courseDescription = map['courseDescription'],
courseImage = map['roocourseImagem'],
courseMethod = map['courseMethod'],
courseURL = map['courseURL'],
courseAudience = map['courseAudience'],
courseResult = map['courseResult'];
}
Map results = {
'result1': false,
'result2': false,
'result3': false,
'result4': false,
Could you make this little change? :
Change this :
Future onChanged(bool value, Course course) {
setState(() {
results[course.courseResult] = value;
});
return setResult(course);
}
To this:
Future onChanged(bool value, Course course) async {
final result = await setResult(course);
setState(() {
results[course.courseResult] = value;
});
return result;
}
UPDATE
Replace your initState method with this:
_onLayoutDone(_){
getResult(widget.entry);
}
#override
void initState() {
WidgetsBinding.instance.addPostFrameCallback(_onLayoutDone);
super.initState();
}