flutter data not retrieved from firestore when using stream - flutter

DatabaseService databaseService = new DatabaseService();
Stream questionsSnapshot;
so Im using a stream and and a database service to retrieve data (questions and answers of a quiz) to my listView builder
the called function from database service is
getAquizData(String quizId) async{
return await Firestore.instance
.collection("quiz")
.document(quizId)
.collection("questionReponses")
.snapshots();
}
the init state function
#override
void initState() {
databaseService.getAquizData(widget.quizId).then((value){
questionsSnapshot = value;
setState(() {});
});
super.initState();
}
my listViewBuilder
#override
Widget build(BuildContext context) {
return Scaffold(
// an appbar
) ,
body: Container(
child: Column(children: <Widget>[
StreamBuilder(
stream: questionsSnapshot,
builder: (context, snapshot) {
return snapshot.data == null
? Container(child: Text("empty"),) : ListView.builder(
shrinkWrap: true,
physics: ClampingScrollPhysics(),
itemCount: snapshot.data.documents.length,
itemBuilder: (context,index){
return QuizPlayTile(
questionModel: getQuestionModelFromDatasnapshot(
snapshot.data.documents[index]),
index: index,
);
});
},
)
],)
)
);
}
}
when running it just show the word empty for a second and then it shows questions without answers
[1]: https://i.stack.imgur.com/E4CS4.jpg

We can use stream builder without calling it in initState method. Following code works for me. user quizStreamer in build method.
quizStreamer(){
return StreamBuilder(
stream: Firestore.instance
.collection('quiz')
.document(quizId)
.collection('questionReponses')
.snapshots(),
builder: (context, AsyncSnapshot<QuerySnapshot> snapshot) {
//loadWidgets method takes snapshot and render defined widgets
return loadWidgets(snapshot);
},
);
}

Related

displaying data from different firestore collections

I'm attempting display data from two diffrent collections within firestore , I treied to nest both streambuilds so i can particulary display the data as one stream , however I keep on getting the error bad state field doesnt exist with doc snapshot how can i fixing thus error , or is there another much more effective method i can use to display data from two diffrent collections in one class?
below is screenshot of the data(s) i want to display:
class OrderStream extends StatelessWidget {
static const String route = "/Order";
final CollectionReference meal =
FirebaseFirestore.instance.collection("menu");
final CollectionReference profile =
FirebaseFirestore.instance.collection("users");
OrderStream({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder(
stream: profile.snapshots(),
builder: (context, AsyncSnapshot<QuerySnapshot> streamSnapshot) {
return StreamBuilder(
stream: meal.snapshots(),
builder:
(context, AsyncSnapshot<QuerySnapshot> streamSnapshot) {
if (!streamSnapshot.hasData) {
return const SizedBox(
height: 250,
child: Center(
child: CircularProgressIndicator(),
),
);
} else {
return ListView.builder(
itemCount: streamSnapshot.data!.docs.length,
itemBuilder: (context, index) {
final DocumentSnapshot documentSnapshot =
streamSnapshot.data!.docs[index];
return Column(
children: [
Text( documentSnapshot['price'],)
Text( documentSnapshot['name'],)
]
),
),
}
This is probably happening due to similar name for both snapshots.
The best way to check this is by renaming the snapshot for individual Streambuilder().
StreamBuilder(
stream: profile.snapshots(),
builder: (context, AsyncSnapshot<QuerySnapshot> profileStreamSnapshot) {
return StreamBuilder(
stream: meal.snapshots(),
builder:
(context, AsyncSnapshot<QuerySnapshot> mealStreamSnapshot) {
if (!streamSnapshot.hasData) {
//modified (renamed snapshot variable) code here
}
You can merge those two streams into 1 using library like rxdart which has combineLatest2 method although you can also use something like StreamZip to get the same effect.
I have used rxdart combineLatest2 as follows:
import 'package:rxdart/rxdart.dart';//import ⇐
class MyHomePage extends StatelessWidget {
final CollectionReference profile =
FirebaseFirestore.instance.collection("users");
final CollectionReference meal =
FirebaseFirestore.instance.collection("menu");
MyHomePage({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder(
stream: Rx.combineLatest2(profile.snapshots(), meal.snapshots(),
(QuerySnapshot profileSnapshot, QuerySnapshot mealSnapshot) {
return [...profileSnapshot.docs, ...mealSnapshot.docs];
}),
builder: (context, AsyncSnapshot<List<DocumentSnapshot>> snapshot) {
if (!snapshot.hasData) {
return const SizedBox(
height: 250,
child: Center(
child: CircularProgressIndicator(),
),
);
} else {
return ListView.builder(
itemCount: snapshot.data!.length,
itemBuilder: (context, index) {
final DocumentSnapshot documentSnapshot =
snapshot.data![index];
final Map<String, dynamic> data =
documentSnapshot.data() as Map<String, dynamic>;
if (data.containsKey("price") && data.containsKey("name")) {
return Column(
children: [Text(data["price"]), Text(data["name"])],
);
} else {
return Container();
}
},
);
}
}),
);
}
}
You can also use Stream.merge() as follows:
final Stream<QuerySnapshot> mealsStream = meal.snapshots();
final Stream<QuerySnapshot> profilesStream = profile.snapshots();
//.. All that Scaffold stuff
stream: Stream.merge([mealsStream, profilesStream]),

Correct way to load ListView data source initially

I have a stateful widget whose state builds a ListView. The ListView gets its data from an http API. I am using a Future<void> method called getData to retrieve this data and populate a List<> with it before calling setState.
My question is where should I call getData when this screen first launches? If I call it in initState(), I get the following error in the debug console:
[VERBOSE-2:ui_dart_state.cc(198)] Unhandled Exception: dependOnInheritedWidgetOfExactType<_InheritedTheme>() or dependOnInheritedElement() was called before _EventListState.initState() completed.
If I wrap the call to getData in a delayed Future, I do not see the error. Here's my code:
class _EventListState extends State<EventList> {
Future<void> getData() async {
events = [];
events = await Network.getUsers(context);
setState(() {});
}
List<Event> events = [];
#override
initState() {
super.initState();
getData(); // this cause the error
// Future.delayed(Duration(seconds: 1), getData); // this works
}
#override
build(context) {
return PlatformScaffold(
iosContentPadding: true,
body: ListView.builder(
padding: const EdgeInsets.all(10),
physics: const AlwaysScrollableScrollPhysics(),
itemCount: events.length,
itemBuilder: (context, index) => Text(events[index].summary),
),
);
}
}
Forcing a delay to retrieve the data does not feel right, so is there a better way?
Use FutureBuilder.
List<Event> events = [];
#override
Widget build(BuildContext context) {
return FutureBuilder(
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return PlatformScaffold(
iosContentPadding: true,
body: ListView.builder(
padding: const EdgeInsets.all(10),
physics: const AlwaysScrollableScrollPhysics(),
itemCount: events.length,
itemBuilder: (context, index) => Text(events[index].summary),
),
);
} else if (snapshot.hasError) {
return Center(child: Text('Error: ${snapshot.error}'));
} else {
return Center(child: Text('Please wait its loading...'));
}
},
future: getData(),
);
}
Future<void> getData() async {
events = [];
events = await Network.getUsers(context);
}

Flutter : Class 'Future<List<String>>' has no instance getter 'length'

I have a List of String saved on SharedPreference in Flutter app, I want to call it inside Provider and using it in a widget.
Provider :
get myTeam => getMyTeam();
Future<List<String>> getMyTeam() async {
Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
final SharedPreferences prefs = await _prefs;
return prefs.getStringList('team');
}
I used it in future builder :
Widget build(BuildContext context) {
return Consumer<GeneralProvider>(
builder: (context, generalProvider, child) {
var items = generalProvider.myTeam;
return FutureBuilder(
future: items,
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return ListTile(
title: Text('${items[index].club}'),
);
});
} else {
return Text('bad');
}
});
});
}
I get error : Class 'Future<List<String>>' has no instance getter 'length'
I tied with many solutions in questions here like :
Class 'Future<dynamic>' has no instance getter 'length'
but it wasn't solved
Change itemCount: items.length into itemCount: snapshot.length,
And Future builder to FutureBuilder<List<String>(...etc).
Will look like this in the end:
Widget build(BuildContext context) {
return Consumer<GeneralProvider>(
builder: (context, generalProvider, child) {
var items = generalProvider.myTeam;
return FutureBuilder<List<String>>(
future: items,
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.length,
itemBuilder: (context, index) {
return ListTile(
// title: Text('${items[index].club}'),//This will likely throw an error also, because items is a List<String>, there is no method called "club" for Lists.
//Replace it with this to validate:
title: Text(snapshot[index]),//This
);
});
} else {
return Text('bad');
}
});
});
}

Error trying to build a ListView in a Flutter FutureBuilder

I am new to Flutter and building a small app to record my expenses and learn a bit.
I am using Hive to store data. Now I am building a page which targets to show all the previously saved entries. I do this by creating a List with all the data and then trying to use a FutureBuilder to show the data in a ListView.
This is the code so far:
class LogScreen extends StatefulWidget {
const LogScreen({Key? key}) : super(key: key);
#override
_LogScreenState createState() => _LogScreenState();
}
class _LogScreenState extends State<LogScreen> {
get futureEntries => getEntries();
#override
void initState() {
// TODO: implement initState
super.initState();
}
#override
Widget build(BuildContext context) {
return FutureBuilder<Widget>(
future: futureEntries,
builder: (BuildContext context, AsyncSnapshot<Widget> snapshot) {
if (snapshot.hasData) {
return Container(
child: ListView.builder(
itemCount: futureEntries.length,
itemBuilder: (context, index) {
Entry currentEntry = Hive.box<Entry>('entriesBox').getAt(index);
return ListTile(
title: Text('${currentEntry.description}'),
);
},
),
);
} else {
return CircularProgressIndicator();
}
}
);
}
Future<List> getEntries() async {
List listEntries = await DbHelper().getListEntries();
print(listEntries);
return listEntries;
}
}
I am getting the following error though:
The following _TypeError was thrown building LogScreen(dirty, state: _LogScreenState#75644):
type 'Future<List<dynamic>>' is not a subtype of type 'Future<Widget>?'
The relevant error-causing widget was:
LogScreen file:///home/javier/StudioProjects/finanzas/lib/main.dart:55:14
When the exception was thrown, this was the stack:
#0 _LogScreenState.build (package:finanzas/log_screen.dart:29:17)
Could someone please tell me what I am doing wrong and suggest a solution? I come from Python and am having a though time with all these types :-P
Thanks in advance.
The generic type of FutureBuilder<T>() should correspond to the data type your Future will return, not what the builder is building. In your case you have FutureBuilder<Widget> so it expects a Future<Widget>, but your getEntries returns a Future<List<dynamic>>. So this is what the error is hinting at. Your code should probably look like this:
return FutureBuilder<List<Entry>>(
future: futureEntries,
builder: (BuildContext context, AsyncSnapshot<List<Entry>> snapshot) {
if (snapshot.hasData) {
return Container(
child: ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
Entry currentEntry = snapshot.data[index];
return ListTile(
title: Text('${currentEntry.description}'),
);
},
),
);
} else {
return CircularProgressIndicator();
}
}
);
Also note that i replaced the references in your ListView.builder from directly referencing your future to using the data inside the snapshot
Alright. After some research, here's the code that got to work:
Widget build(BuildContext context) {
return FutureBuilder<List>(
future: futureEntries,
builder: (BuildContext context, AsyncSnapshot<List> snapshot) {
if (snapshot.hasData) {
return Container(
child: ListView.builder(
itemCount: snapshot.data!.length,
itemBuilder: (context, index) {
Entry currentEntry = snapshot.data![index];
return ListTile(
title: Text('${currentEntry.description}'),
);
},
),
);
} else {
return CircularProgressIndicator();
}
}
);
}
Future<List> getEntries() async {
List listEntries = await DbHelper().getListEntries();
print(listEntries);
return listEntries;
}
I don't know yet exactly what the exclamation marks after 'data' do, but they did the trick.

Unable to show Circular progress indicator when FutureBuilder is loading data

I am trying to show Circular Progress Indicator while my data in Future Builder loads, I tried two methods to add it however both of them didn't work. How can I achieve the desired result?
My code
class _MyHomePageState extends State<MyHomePage>{
#override MyHomePage get widget => super.widget;
#override
Widget build(BuildContext context){
//To show the ListView inside the Future Builder
Widget createTasksListView(BuildContext context, AsyncSnapshot snapshot) {
var values = snapshot.data;
return ListView.builder(
itemCount: values == null ? 0 : values.length,
itemBuilder: (BuildContext context, int index) {
return values.isNotEmpty ? Ink(
.....
) : new CircularProgressIndicator(); //TRIED TO ADD CIRCULAR INDICATOR HERE
},
);
}
//Future Builder widget
Column cardsView = Column(
children: <Widget>[...
Expanded(
child: FutureBuilder(
future: //API CALL,
initialData: [],
builder: (context, snapshot) {
if (!snapshot.hasData) return Center(child: CircularProgressIndicator()); //CIRCULAR INDICATOR
return createTasksListView(context, snapshot);
}),
),
],
);
return Scaffold(
...);
}
}
Try with:
FutureBuilder(
future: //API CALL,
initialData: [],
builder: (context, snapshot) {
if (!snapshot.hasData)
return Center(child: CircularProgressIndicator()); //CIRCULAR INDICATOR
else
return createTasksListView(context, snapshot);
}),
),
When the value of the future is an empty list, which is your initialData, you are rendering a ListView with 0 items, so you cannot render a CircularProgressIndicator in its itemBuilder.