I am trying to receive data using a FutureBuilder, but it hangs on the CircularProgressIndicator. I think it's remaining on ConnectionState.waiting but I'm not sure why.
#override
initState() {
_widgetList = getWidgetList();
}
Stream<List<String>> getFriendUUIDS() => Firestore.friends
.doc(gameManager.myself.uuid)
.snapshots()
.map((snapshot) => ((snapshot.data()?.keys)?.toList()) ?? []);
Future<List<MomentWidget>> getWidgetList() async{
List<MomentWidget> widgetList = [];
Set<String> momentIds = Set();
await for (final uuids in getFriendUUIDS()){
for (String uuid in uuids){
DocumentSnapshot<Map<String, dynamic>> values = await Firestore.users
.doc(uuid)
.get();
for (String momentId in values.data()?['moments'] ?? [] ){
momentIds.add(momentId);
}
}
}
for (String momentId in momentIds){
DocumentSnapshot<Map<String, dynamic>> values =
await Firestore.instance.collection('moments').doc(momentId).get();
Map<String, dynamic>? data = values.data()!;
String downloadURL = await storage.ref('moments/$momentId').getDownloadURL();
MomentWidget widget = MomentWidget(numLikes: data['liked_by'].length ,
location: data['location'],
date: data['timestamp'],
names: data['taggedFriends'].toString(),
shotBy: data['taken_by'], image: NetworkImage(downloadURL));
widgetList.add(widget);
}
return widgetList;
}
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
return Container(
height: size.height,
width: size.width,
child: FutureBuilder(
future: _widgetList,
builder: (context, AsyncSnapshot<List<MomentWidget>> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.done:
if (snapshot.hasError) {
return Text(snapshot.error.toString());
} else {
return ListView.builder(
shrinkWrap: true,
scrollDirection: Axis.vertical,
itemBuilder: (context, pos) {
return snapshot.data![pos];
},
);
}
case ConnectionState.waiting:
return Center(
child: CircularProgressIndicator(),
);
default:
return Text('Unhandled State');
}
}
),
);
}
I have tried to get the Future inside of initState(), and have tried to use snapshot.hasData instead, to no avail.
I have encountered a similar problem. When building an object from json , if the types don't match , it can quietly fail. I do not think your widgetList is ever returned. In my case I had a variable "cost" that I thought would be of type int , however in the database it was of type String. It always quietly failed and never showed the markers on the map widget
So:
Check how many times that loop of yours is executed. Probably only once and then it quietly fails
If the above happens:
Makes sure the types of your variables match the ones from the database. Comment out every variable one by one to find where the problem is.
Let me know if it works
Related
In the title I explained what I want to do. I have a bool value named
'turnInvitingPlayer' stored somewhere in a document field in Firestore. The location of the document I know exactly from the instance Variables of GameTable.
This is what i tried:
class GameTable extends StatefulWidget {
GameTable({Key? key,
required this.player,
required this.invitationID,
required this.invitationIdPlayerInvited,
required this.invitationIdPlayerInviting})
: super(key: key);
final Player? player;
final String invitationIdPlayerInvited;
final String invitationIdPlayerInviting;
/// the invitation ID is the doc name of the gambling Table
final String invitationID;
#override
State<GameTable> createState() => _GameTableState();
}
class _GameTableState extends State<GameTable> {
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: FirebaseFirestore.instance
.collection('GameTables')
.doc(widget.invitationID)
.snapshots(),
builder: (context, snapshot) {
if (snapshot.hasData) {
var dataGameTable = snapshot.data! as Map;
var turnInvitingPlayer =
dataGameTable['turnInvitingPlayer'] as bool;
if (turnInvitingPlayer == true) {
return Container(color: Colors.blue);
} else {
return Container(color: Colors.red);
}
} else if (!snapshot.hasData) {
return Container(
child: Text('There is no data'),
);
}
return CircularProgressIndicator();
});
}
}
I am getting the following error when I run the App
Expected a value of type 'Map<dynamic, dynamic>', but got one of type '_JsonDocumentSnapshot'
Can somebody show me a way how I can simple access the bool value of the stream and use it in if Clauses?
Thank's to everybody who will help.
Modify your stream builder as follows:
return StreamBuilder<Map<dynamic,dynamic>>(
stream: FirebaseFirestore.instance
.collection('GameTables')
.doc(widget.invitationID)
.snapshots(),
builder: (context, snapshot) {
if (snapshot.hasData) {
var dataGameTable = snapshot.data! as Map;
var turnInvitingPlayer =
dataGameTable['turnInvitingPlayer'] as bool;
if (turnInvitingPlayer == true) {
return Container(color: Colors.blue);
} else {
return Container(color: Colors.red);
}
} else if (!snapshot.hasData) {
return Container(
child: Text('There is no data'),
);
}
return CircularProgressIndicator();
});
I found the solution i have done following changes:
var dataGameTable = snapshot.data!; // remove as Map
var turnInvitingPlayer = dataGameTable['turnInvitingPlayer'] as bool; // remove this line
Now I have access to the boolean value with simple dataGameTable['turnInvitingPlayer'].
I need to let the consumer widget listen to multiple variables depending on a boolean value.
this is the model class
class Lawyer{
Data? data;
double? distance = 0;
Lawyer({this.data, this.distance});
factory Lawyer.fromJson(Map<String, dynamic> json) =>
Lawyer(data: Data.fromJson(json['listing_data']));
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////
class Data{
String? title;
String? email;
String? phone;
Location? location;
List<String>? logo;
List<String>? cover;
Data({this.title, this.email, this.phone, this.logo, this.cover, this.location});
factory Data.fromJson(Map<String, dynamic> json) {
var logo = json['_job_logo'];
var cover = json['_job_cover'];
var long = json['geolocation_long'];
var lat = json['geolocation_lat'];
return Data(title: json['_job_tagline'], email: json['_job_email'],
location: Location(latitude: json['geolocation_lat'], longitude: json['geolocation_long']),
phone: json['_job_phone'], logo: List<String>.from(logo),
cover: List<String>.from(cover)
);
}
}
and this is the view model notifier
class LawyerAPIServices extends ChangeNotifier{
final url = "https://dalilvision.com/wp-json/wp/v2/job_listing";
List<Lawyer> lawyersList = [];
List<Lawyer> staticLawyersList = [];
Future<List<Lawyer>> fetchLawyers() async{
final response = await get(Uri.parse(url.toString()));
if(response.statusCode == 200){
var dynamicLawyersList = jsonDecode(response.body);
print('$dynamicLawyersList');
lawyersList = List<Lawyer>.from(dynamicLawyersList.map((x) => Lawyer.fromJson(x)));
staticLawyersList = lawyersList;
lawyersList.forEach((element) {print('all lawyers: ${element.data!.location}');});
notifyListeners();
return lawyersList;
}
else{
notifyListeners();
throw Exception(response.statusCode);
}
}
Future<List<Lawyer>> getFullListOfLawyers() async {
notifyListeners();
print('fulll list: ${staticLawyersList.length}');
return staticLawyersList;
}
}
and finally this is the consumer widget
Consumer<LawyerAPIServices>(
builder: (context, value, child) => FutureBuilder(
future: _list,
builder: (BuildContext context, AsyncSnapshot<List<Lawyer>> snapshot) {
if (snapshot.hasData){
return ListView.separated(
physics: const NeverScrollableScrollPhysics(),
scrollDirection: Axis.vertical,
shrinkWrap: true,
separatorBuilder: (context, index) => const Divider(color: Colors.transparent),
itemCount: value.lawyersList.length,
itemBuilder: (context, index) {
return InkWell(
child: LawyerWidget(
title: snapshot.data![index].data!.title!,
email: snapshot.data![index].data!.email!,
phone: snapshot.data![index].data!.phone!,
logo: snapshot.data![index].data!.logo![0],
cover: snapshot.data![index].data!.cover![0]
),
);
}
}
);
}
else if(snapshot.hasError){
return Center(
child: Text(snapshot.error.toString())
);
}
else {
return const CircularProgressIndicator(
strokeWidth: 2,
);
}
},
),
)
In the notifier class there are two lists, the staticLawyerList is initialized only once when getting the list from a network call and then used as a backup list, and the lawyersList is the one that will be manipulated.
what I have done until now is to get the initial value of lawyersList by a network call, then somehow the staticLawyersList values are always equal to lawyersList, even if I made any change or manipulate the lawyersList these changes will automatically reflect on the staticLawyersList which is really weird.
now what I want to achieve exactly is to apply a condition to update the UI with the appropriate list depending on this condition.
if(setByPosition == false){
//update UI with `staticLawyersList`
}
else {
//update UI with `lawyersList`
}
update!!!!!!!!
here's how I update my consumer
CheckboxListTile(
activeColor: Colors.black,
value: isChecked,
onChanged: (value) async {
saveSharedPreferences(value: value!);
if(value == true) {
Provider.of<LawyerAPIServices>(context, listen: false).sortLawyersList(
devicePosition: widget.position, lawyersList: widget.list);
}
else{
Provider.of<LawyerAPIServices>(context, listen: false).getFullListOfLawyers();// the list returned by this function don't applied to the consumer
}
setState(() {
isChecked = value;
Navigator.pop(context);
});
},
title: const Text('Filter by distance'),
),
A few things to consider:
When you do this "staticLawyersList = lawyersList" you actually have two "pointers" to the same list. It works that way for lists, sets, classes, etc.. only basic types as int, double, string are really copied.
You can use this instead: "staticLawyersList = List.from(lawyersList);"
It doesn't seem you need the ChangeNotifier in your LawyerAPIServices. You could create an instance of LawyerAPIServices in the widget you need it and call fetchLawyers. Do it in the initState of a StatefullWidget if you don't want the list to be rebuilt multiple times. In your build method use a FutureBuilder to read the Future and decide what to show in the UI.
class _MyWidget extends State<MyWidget> {
late final LawyerAPIServices lawyerApi;
// Create this variable to avoid calling fetchLawers many times
late final Future<List<Lawyer>> lawyersList;
#override
void initState() {
super.initState();
// Instantiate your API
lawyerApi = LawyerAPIServices();
// This will be called only once, when this Widget is created
lawyersList = lawyerApi.fetchLawyers();
}
#override
Widget build(BuildContext context) {
return FutureBuilder<List<Lawyer>>(
future: lawyersList,
builder: ((context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.done:
if (setByPosition) {
//update UI with `lawyersList`
return _listView(snapshot.data!);
} else {
//update UI with `staticLawyersList`
// Since the Future state is Complete you can be sure that
// the staticLawyersList variable in your API was already set
return _listView(lawyerApi.staticLawyersList);
}
case ConnectionState.none:
return const Text('Error');
default:
return const CircularProgressIndicator.adaptive();
}
}),
);
}
Widget _listView(List<Lawyer> lawyersList) {
return ListView.separated(
physics: const NeverScrollableScrollPhysics(),
scrollDirection: Axis.vertical,
shrinkWrap: true,
separatorBuilder: (context, index) =>
const Divider(color: Colors.transparent),
itemCount: lawyersList.length,
itemBuilder: (context, index) {
return InkWell(
child: LawyerWidget(
title: lawyersList[index].data!.title!,
email: lawyersList[index].data!.email!,
phone: lawyersList[index].data!.phone!,
logo: lawyersList[index].data!.logo![0],
cover: lawyersList[index].data!.cover![0]),
);
});
}
}
If for any reason you need to share the same LawyerAPIServices across multiple widgets, you could instantiate it on the top of your tree and send it down using Provider or as a parameter.
The method getFullListOfLawyers doesn't need to return a Future, since staticLawyersList is a List (not a Future). You could get this list directly using "LawyerAPIServices.staticLawyersList" or maybe something like this could make sense:
Future<List> getFullListOfLawyers() async {
if(staticLawyersList.isEmpty) {
await fetchLawyers();
}
print('fulll list: ${staticLawyersList.length}');
return Future.value(staticLawyersList);
}
as #Saichi-Okuma said that to copy the content of a list you should use staticLawyersList = List.from(lawyersList) because in dart and most of the java compiler programming languages when you use staticLawyersList = lawyersList this means that you are referring to the lawyersList by the staticLawyersList.
then I manipulate the lawyersList as I want with help of staticLawyersList
lawyersList.clear();
lawyersList.addAll(staticLawyersList);
But when I did so, the consumer didn't apply the changes based on the staticLawyersList although the logcat shows that the staticLawyersList length is 10 which is what I want (full list without filtration).
the conclusion of my problem can be listed in two points:
1- the consumer is listening to only one list lawyersList and I think it still exists.
2- the pointer problem as #Saichi-Okuma mentioned.
here are the full code changes
void getFullListOfLawyers() {
lawyersList.clear(); // to make sure that the list is clean from older operations
lawyersList.addAll(staticLawyersList);// the trick
notifyListeners();
}
Future<List<Lawyer>> fetchLawyers() async{
final response = await get(Uri.parse(url.toString()));
if(response.statusCode == 200){
var dynamicLawyersList = jsonDecode(response.body);
print('$dynamicLawyersList');
lawyersList = List<Lawyer>.from(dynamicLawyersList.map((x) => Lawyer.fromJson(x)));
staticLawyersList = List.from(lawyersList);// use this statment instead of staticLawyersList = lawyersList
lawyersList.forEach((element) {print('all lawyers: ${element.data!.location}');});
notifyListeners();
return lawyersList;
}
else{
notifyListeners();
throw Exception(response.statusCode);
}
}
The Consumer Widget gets rebuild every time you call notify notifyListeners, regardless the state of any lists.
Maybe you are not accessing the Instance of the API being consumed. Make sure you are using the 2nd parameter of the Consumer builder.
Consumer<LawyerAPIServices>(builder: (context, lawyerAPI, child) =>
FutureBuilder(
future: lawyerAPI.fetchLawyers(),
builder: ((context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.done:
if (setByPosition) {
//update UI with `lawyersList`
return _listView(snapshot.data!);
} else {
//update UI with `staticLawyersList`
// Since the Future state is Complete you can be sure that
// the staticLawyersList variable in your API was already set
return _listView(lawyerAPI.staticLawyersList);
}
case ConnectionState.none:
return const Text('Error');
default:
return const CircularProgressIndicator.adaptive();
}
}),
I don't think you need the code below for this particular need. It'd override your lawyersList and notify to all listeners even though nothing really changed. Just access your staticLawyersList directly, since it was populated when you called fetchLawyers.
void getFullListOfLawyers() {
lawyersList.clear(); // to make sure that the list is clean from older operations
lawyersList.addAll(staticLawyersList);// the trick
notifyListeners();
}
I'm using Firebase and Flutter to read a List of Objects (EspecieModel). It's working perfect in IOS and Android, however It doesn't work on the Web (an empty List is retrieved).
I'm reading from Firebase as follows ...
Future<List<EspecieModel>> cargarTipoEspecie() async {
final List<EspecieModel> tipoEspecie = [];
Query resp = db.child('PATH/tipoespecie');
resp.onChildAdded.forEach((element) {
final temp = EspecieModel.fromJson(Map<String,dynamic>.from(element.snapshot.value));
temp.idEspecie = element.snapshot.key;
tipoEspecie.add(temp);
});
await resp.once().then((snapshot) {
print("Loaded - ${tipoEspecie.length}");
});
return tipoEspecie;
}
And I'm using a Future Builder to display the information...
FutureBuilder(
future: _tipoEspecieBloc.cargarTipoEspecie(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
// print(snapshot.connectionState);
if (snapshot.connectionState == ConnectionState.done && snapshot.hasData{
// print(snapshot.data);
final _especies = snapshot.data;
return Stack(
children: <Widget>[
ListView.builder(
itemCount: _especies!.length,
itemBuilder: (context, i) {
return _crearItem(context, _especies[i], i);
},
),
],
);
} else if (snapshot.hasError) {
print(snapshot.error);
return Text(snapshot.error.toString());
}
else {
return //CircleProgressIndicator Code
);
}
},
),
I can't identify what I'm doing wrong
How to do a one-time Firebase Query that works well on IOS, Android, and also on the Web??
This won't work:
resp.onChildAdded.forEach((element) {
final temp = EspecieModel.fromJson(Map<String,dynamic>.from(element.snapshot.value));
temp.idEspecie = element.snapshot.key;
tipoEspecie.add(temp);
});
await resp.once().then((snapshot) {
print("Loaded - ${tipoEspecie.length}");
});
return tipoEspecie;
The onChildAdded is not part of the await, so I doubt everything waits the way you seem to want. Just adding await in one place, does not make the rest of your code synchronous.
Instead consider using just once() and then populating your tipoEspecie array by looping over snapshot.value.values (a relatively new addition to the API).
var snapshot = await resp.once();
snapshot.value.values.forEach((node) {
final temp = EspecieModel.fromJson(Map<String,dynamic>.from(node.value));
temp.idEspecie = node.key;
tipoEspecie.add(temp);
});
return tipoEspecie;
Note: I'm not completely sure of the .forEach and the code in there. So if you get errors there, check what type you get back from .values and what node is, to get the correct key and values from it.
I want to use await inside streambuilder. However, if you use async inside, you get an error. On the code below !!!!!!!! That's the part I want to solve. Thank you very much if I can tell you how.
class _MemoStreamState extends State<MemoStream> {
final _fireStore = Firestore.instance;
#override
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: _fireStore
.collection(widget.logInUsrEmail)
.orderBy('id', descending: false)
.snapshots(),
builder: (context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (!snapshot.hasData) return LinearProgressIndicator();
final memos = snapshot.data.documents;
List<MemoMaterial> memoList = [];
for (var memo in memos) {
final memoDocumentID = memo.documentID;
final memoTitle = await PlatformStringCryptor().decrypt(memo.data['title'], _key); !!!!!!!!!!
final memoUsrID = memo.data['usrID'];
final memoUsrPW = memo.data['usrPW'];
final memoText = memo.data['text'];
final memoCreateTime = memo.data['createTime'];
final memoMaterial = MemoMaterial(
logInUsrEmail: widget.logInUsrEmail,
doc: memoDocumentID,
title: memoTitle,
usrID: memoUsrID,
usrPW: memoUsrPW,
text: memoText,
createTime: memoCreateTime,
);
memoList.add(memoMaterial);
}
return Expanded(
child: new ListView.builder(
You should do something like this :
Stream<List<MemoMaterial>> memosStream;
Future<MemoMaterial> generateMemoMaterial(Memo memo) async {
final memoTitle =
await PlatformStringCryptor().decrypt(memo.data['title'], _key);
return MemoMaterial(
logInUsrEmail: widget.logInUsrEmail,
doc: memo.documentID,
title: memoTitle,
usrID: memo.data['usrID'],
usrPW: memo.data['usrPW'],
text: memo.data['text'];,
createTime: memo.data['createTime'],
);
}
#override
void initState() {
memosStream = _fireStore
.collection(widget.logInUsrEmail)
.orderBy('id', descending: false)
.snapshots()
.asyncMap((memos) => Future.wait([for (var memo in memos) generateMemoMaterial(memo)]));
super.initState();
}
#override
Widget build(BuildContext context) {
return StreamBuilder<List<MemoMaterial>>(
stream: memosStream // Use memostream here
asyncMap() will "transform" every new set of Documents into a list of MemoMaterial, and emit this list into the stream when the action is performed.
Future.wait() allows to perform multiple async requests simultaneously.
You can do it using FutureBuilder inside StreamBuilder in following way.
Stream<List<int>> callme() async* {
yield [1, 2, 3, 4, 5, 6];
}
buildwidget() async {
await Future.delayed(Duration(seconds: 1));
return 1;
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: StreamBuilder(
stream: callme(),
builder: (_, sna) {
if (sna.hasData) {
return FutureBuilder(
future: buildwidget(),
builder: (_, snap) {
if (snap.hasData) {
return ListView.builder(
itemCount: sna.data.length,
itemBuilder: (_, index) {
return Text("${sna.data[index]} and ${snap.data}");
},
);
} else {
return CircularProgressIndicator();
}
},
);
} else {
return CircularProgressIndicator();
}
}),
),
);
}
I will prefer to use Getx or Provider State management to Handle the UI if it depends on the async function.
Suppose you want to fetch data from firebase using StreamBuilder() which returns some docs which contains image links then you want to download these images and show from storage. Obviously downloading the image is async type of work. Then you will get error if you show the images with the links you get direct from StreamBuilder().
What you can do is set a variable in getx or provider to show or hide the image Widget. If the Image is being downloaded or not downloaded then set the variable to hide/show the image when the async type of function is completed.
My requirement is to make that StreamBuilder connection state to waiting.
I'm using publish subject, whenever I want to load data in stream builder I'm just adding data to the sink by calling postStudentsToAssign() method, here this method making an API call which takes some time, in that time I to want make that streamBuilder connection state to waiting
Stream Builder:
StreamBuilder(
stream: studentsBloc.studentsToAssign,
// initialData: [],
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
// While waiting for the data to load, show a loading spinner.
return getLoader();
default:
if (snapshot.hasError)
return Center(child: Text('Error: ${snapshot.error}'));
else
return _getDrawer(snapshot.data);
}
}),
Initializing Observable:
final _assignStudentSetter = PublishSubject<dynamic>();
Observable<List<AssignMilestoneModel>> get studentsToAssign =>
_studentsToAssignFetcher.stream;
Method that add's data to Stream:
postStudentsToAssign(int studyingClass, String milestoneId, String subject,
List studentList) async {
var response = await provider.postAssignedStudents(
studyingClass, milestoneId, subject, studentList);
_assignStudentSetter.sink.add(response);
}
You can send null to the stream, so the snapshot.connectionState changes to active. I don't know why and whether it's official solution, but it works (at least now). I found this accidentally.
I would like the Flutter team to explain how to set snapshot's connectionState. It's not clear from StreamBuilder documentation. It seems you should replace the stream with a new one to have snapshot in waiting state. But it's agains the logic you want to implement.
I checked StreamBuilder source to find out that the AsyncSnapshot.connectionState starts as waiting (after stream is connected), after receiving data changes to active. snapshot.hasData returns true if snapshot.data != null. That's how following code works.
class SearchScreen extends StatelessWidget {
final StreamController<SearchResult> _searchStreamController = StreamController<SearchResult>();
final SearchService _service = SearchService();
void _doSearch(String text) async {
if (text?.isNotEmpty ?? false) {
_searchStreamController.add(null);
_searchService.search(text)
.then((SearchResult result) => _searchStreamController.add(result))
.catchError((e) => _searchStreamController.addError(e));
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(children: <Widget>[
SearchBar(
onChanged: (text) => _doSearch(text),
),
StreamBuilder<SearchResult>(
stream: _searchStreamController.stream,
builder: (BuildContext context, AsyncSnapshot<SearchResult> snapshot) {
Widget widget;
if (snapshot.hasData) {
widget = Expanded(
// show search result
);
}
else if (snapshot.hasError) {
widget = Expanded(
// show error
);
}
else if(snapshot.connectionState == ConnectionState.active){
widget = Expanded(
// show loading
);
}
else {
// empty
widget = Container();
}
return widget;
},
),
]),
);
}
}