Can't retrieve a number of document into firestore flutter - flutter

I have a small problem, I need to retrieve into my list a collection retrieved by StreamBuilder from Firestore.
I am using snapshot.data.documents.lenght but once I add it I got error:
Class 'DocumentSnapshot' has no instance getter 'documents'.
Receiver: Instance of 'DocumentSnapshot'
Tried calling: documents
this is my code:
Stream<DocumentSnapshot> getDatabase() async* {
FirebaseUser user = await FirebaseAuth.instance.currentUser();
yield* Firestore.instance
.collection('dataCollection')
.document(user.uid)
.snapshots();
}
#override
Widget build(BuildContext context,) {
return StreamBuilder(
stream: getDatabase(),
builder: (context, snapshot,) {
if (snapshot.data != null) {
return Column(
children: <Widget>[
Container(
height: 500,
child: ListView.builder(
shrinkWrap: true,
itemCount: 2,
itemBuilder: (BuildContext context, int index) {
return Card(
color: Color(0xFF1f2032),
elevation: 15,
child: Text(
snapshot.data['phone']..

just change your code as following
Stream dataStream
then
#override
void initState() {
getDatabase().then((value) {
dataStream = value;
setState(() {});
});
super.initState();
}
the funcion getDatbase()
getDatabase() async {
FirebaseUser user = await FirebaseAuth.instance.currentUser();
yield* Firestore.instance
.collection('dataCollection')
.document(user.uid)
.snapshots();
}
then
#override
Widget build(BuildContext context,) {
return StreamBuilder(
stream: dataStream,
builder: (context, snapshot,) {
if (snapshot.data != null) {
return Column(
children: <Widget>[
Container(
height: 500,
child: ListView.builder(
shrinkWrap: true,
itemCount: 2,
itemBuilder: (BuildContext context, int index) {
return Card(
color: Color(0xFF1f2032),
elevation: 15,
child: Text(
snapshot.data['phone']..

Try this,
StreamBuilder(
stream: stream,
builder: (BuildContext context,
AsyncSnapshot<List<DocumentSnapshot>> snapshots) {
if (snapshots.connectionState == ConnectionState.active &&
snapshots.hasData) {
return Expanded(
child: ListView.builder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemCount: snapshots.data.length,
itemBuilder: (BuildContext context, int index) {
//do something with snapshot.
}
}
return Container();
},
),
);
} else {
return Container();
}
},
),
Initialise your stream like this,
Stream<DocumentSnapshot> stream;
Future<dynamic> getDatabase() async {
FirebaseUser user = await FirebaseAuth.instance.currentUser();
setState(() {
stream=Firestore.instance
.collection('dataCollection')
.document(user.uid)
.snapshots();
});
}
You can call getDatabase() in initState.
Update:-
This is your full code.
class DataCo extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
backgroundColor: Colors.blue,
),
body: Column(
children: [
CollectData(),
],
),
);
}
}
class CollectData extends StatefulWidget {
#override
_CollectDataState createState() => _CollectDataState();
}
class _CollectDataState extends State<CollectData> {
final String phone;
final String wife;
final String location;
_CollectDataState({this.phone, this.wife, this.location});
#override
void initState() {
super.initState();
getDatabase();
}
Stream<DocumentSnapshot> stream;
Future<dynamic> getDatabase() async {
FirebaseUser user = await FirebaseAuth.instance.currentUser();
setState(() {
stream=Firestore.instance
.collection('dataCollection')
.document(user.uid)
.snapshots();
});
}
#override
Widget build(BuildContext context,) {
return StreamBuilder(
stream: stream,
builder: (BuildContext context,
AsyncSnapshot<DocumentSnapshot> snapshots) {
if (snapshots.connectionState == ConnectionState.active &&
snapshots.hasData) {
return Expanded(
child: ListView.builder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemCount: snapshots.data.length,
itemBuilder: (BuildContext context, int index) {
//do something with snapshot.
}
}
return Container();
},
),
);
} else {
return Container();
}
},
);
}
}
class NoData extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Column(
children: [
Text('No Data available'),
],
);
}
}

Related

Flutter fetch nested future

I have a method that fetches a PatientLog from SQLite.However, This PatientLog table mapped to an object with a class named PatientLog. Inside this PatientLog class, several other objects such as Speciality, AttendingPhysician, Course, etc. I need to map these PatienLog records to a local object. However, I have to use nested Futures. I need to retrieve the data from this nested Future. Think of Future of Future.
This is my fetch method
Future<List<Future<PatientLog>>> getForms() async {
Database db = await instance.getDatabase;
List<Map<String, dynamic>> forms =
await db.query(_tablePatientLog, orderBy: 'id DESC');
Institute? institute;
AttendingPhysician? attendingPhysician;
Speciality? speciality;
Course? course;
List<Future<PatientLog>> list = forms.map((myMap) async {
int? courseId = myMap['course_id'] as int?;
int? specialityId = myMap['speciality_id'] as int?;
int? attendingId = myMap['attending_id'] as int?;
int? instituteId = myMap['institute_id'] as int?;
if (courseId != null) {
await getCourse(courseId).then((value) => course=value);
}
if (attendingId != null) {
await getAttending(attendingId).then((value) => attendingPhysician=value);
}
if (specialityId != null) {
await getSpeciality(specialityId).then((value) => speciality=value);
}
if (instituteId != null) {
await getInstitute(instituteId).then((value) => institute=value);
}
return PatientLog.fromMap(
myMap, institute, course, attendingPhysician, speciality);
}).toList();
return list;
}
I need to display that information on a screen. I get an error type 'List<Future<PatientLog>>' is not a subtype of type 'Future<Object?>?'
class _DraftsState extends State<Drafts> {
final SQFLiteHelper _helper = SQFLiteHelper.instance;
#override
void initState() {
super.initState();
_refresh();
}
late List<Future<PatientLog>> fromDatabase;
Future<dynamic> _refresh() async {
await _helper.getForms().then((value) async{
setState(() {
fromDatabase = value;
});
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder(
future: _helper.getForms(),
builder: (BuildContext context,
AsyncSnapshot snapshot) {
if (snapshot.hasData && snapshot.data!.isEmpty) {
return Center(
child: Text(
"Henüz kaydedilmiş taslak bulunmamaktadır.",
textAlign: TextAlign.center,
style: TEXT_STYLE,
));
}
if (snapshot.hasError) {
return Center(
child: Text(
'Sanırım bir şeyler ters gitti.',
style: TEXT_STYLE,
));
}
if (snapshot.connectionState == ConnectionState.done) {
return RefreshIndicator(
backgroundColor: Colors.grey[700],
color: LIGHT_BUTTON_COLOR,
onRefresh: _refresh,
child: SizedBox(
height: MediaQuery.of(context).size.height,
child: ListView.builder(
shrinkWrap: true,
physics: const BouncingScrollPhysics(
parent: AlwaysScrollableScrollPhysics(),
),
itemCount: snapshot.data!.length,
itemBuilder: (BuildContext context, int index) {
return FutureBuilder(
future: snapshot.data,
builder: (context,innerSnap) {
return Text(innerSnap.toString());/*CustomListTile(
formData: innerSnap.data[index],
index: index,
routeTo: 1,
isDeletable: true,
);*/
}
);
},
),
),
);
}
return const Center(
child: Text("Nothing")//spinkit,
);
}),
);
}
}

Avoid ListView's unwanted refresh

As the following animation displays, when I tap one of the list items that StreamBuilder() is querying, it shows the items data on the right darker container (it's always Instance of '_JsonQueryDocumentSnapshot'). But at the same time in each tap, the whole list is refreshing itself, which is not very cost-effective I believe.
How can I avoid this unwanted refresh?
Answers with GetX state management dependency are also welcome.
class Schedule extends StatefulWidget {
#override
_ScheduleState createState() => _ScheduleState();
}
class _ScheduleState extends State<Schedule> {
final FirebaseFirestore _db = FirebaseFirestore.instance;
final DateTime _yesterday = DateTime.now().subtract(Duration(days: 1));
var _chosenData;
#override
Widget build(BuildContext context) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: StreamBuilder<QuerySnapshot>(
stream: _db.collection('Schedule').where('date', isGreaterThan: _yesterday).limit(10).orderBy('date').snapshots(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.active) {
return ListView.builder(
itemCount: snapshot.data!.docs.length,
itemBuilder: (context, index) {
var data = snapshot.data!.docs[index];
return ListTile(
leading: Icon(Icons.person),
title: Text(data['project'], style: TextStyle(fontWeight: FontWeight.bold)),
subtitle: Text(data['parkour']),
onTap: () {
setState(() {_chosenData = data;});
},
);
},
);
} else {
return Center(child: CupertinoActivityIndicator());
}
},
),
),
VerticalDivider(),
Expanded(
child: Container(
alignment: Alignment.center,
color: Colors.black26,
child: Text('$_chosenData'),
),
),
],
);
}
}
To me the easiest solution would be just make it stateless and use a Getx class.
class ScheduleController extends GetxController {
var chosenData;
void updateChosenData(var data) {
chosenData = data;
update();
}
}
And your Schedule.dart would look like this:
class Schedule extends StatelessWidget {
final FirebaseFirestore _db = FirebaseFirestore.instance;
final DateTime _yesterday = DateTime.now().subtract(Duration(days: 1));
#override
Widget build(BuildContext context) {
final controller = Get.put(ScheduleController());
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: StreamBuilder<QuerySnapshot>(
stream: _db
.collection('Schedule')
.where('date', isGreaterThan: _yesterday)
.limit(10)
.orderBy('date')
.snapshots(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.active) {
return ListView.builder(
itemCount: snapshot.data!.docs.length,
itemBuilder: (context, index) {
var data = snapshot.data!.docs[index];
return ListTile(
leading: Icon(Icons.person),
title: Text(data['project'],
style: TextStyle(fontWeight: FontWeight.bold)),
subtitle: Text(data['parkour']),
onTap: () => controller.updateChosenData(data), // calls method from GetX class
);
},
);
} else {
return Center(child: CupertinoActivityIndicator());
}
},
),
),
VerticalDivider(),
Expanded(
child: Container(
alignment: Alignment.center,
color: Colors.black26,
child: GetBuilder<ScheduleController>(
builder: (controller) => Text('${controller.chosenData}'), // only this rebuilds
),
),
),
],
);
}
}
This way the listview.builder never rebuilds, only the Text widget directly inside the GetBuilder gets rebuilt when you selected a different ListTile.
Calling setState() notifies the framework that the state of Schedule has changed, which causes a rebuild of the widget and so your StreamBuilder.
You could move your stream logic to an upper level of the widget tree. So, setState() will not trigger a rebuild of StreamBuilder.
class ParentWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance
.collection('Schedule')
.where(
'date',
isGreaterThan: DateTime.now().subtract(Duration(days: 1)),
)
.limit(10)
.orderBy('date')
.snapshots(),
builder: (context, snapshot) {
return Schedule(snapshot: snapshot); // Pass snapshot to Schedule
},
);
}
}
Another approach would be using Stream.listen in initState() which is called once. This way your stream won't be subscribed for each time setState() is called.
...
late StreamSubscription<QuerySnapshot> _subscription;
#override
void initState() {
_subscription = _db
.collection('Schedule')
.where('date', isGreaterThan: _yesterday)
.limit(10)
.orderBy('date')
.snapshots()
.listen((QuerySnapshot querySnapshot) {
setState(() {
_querySnapshot = querySnapshot;
});
});
super.didChangeDependencies();
}
#override
void dispose() {
_subscription.cancel(); // Cancel the subscription
super.dispose();
}
...

Infinite loop FutureBuilder when use Provider

I have method like this in my provider file to get a list of items :
Future<void> getServerMeals() async {
final QueryBuilder<ParseObject> parseQuery =
QueryBuilder<ParseObject>(ParseObject('UsersEaten'));
final ParseResponse apiResponse = await parseQuery.query();
if (apiResponse.success && apiResponse.results != null) {
List<dynamic>? apiRes = apiResponse.results;
List<EatenItem> newMeals = apiRes!
.map((e) => EatenItem(
id: e['objectId'],
eatenCal: e['eatenCal'],
eatenTitle: e['eatenTitle'],
eatenImageUrl: e['eatenImg'],
userId: e['objectId'],
))
.toList();
_eatenMeals = newMeals;
print(apiResponse.results);
notifyListeners();
}
}
and in my main screen I have a StatelessWidget and FutureBuilder to get that list :
class TimelineWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Container(
child: FutureBuilder(
future: Provider.of<EatenMeals>(context, listen: false).getServerMeals(),
builder: (ctx, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(),
);
}
return Consumer<EatenMeals>(
builder: (ctx, mealsList, child) => ListView.builder(
itemCount: mealsList.eatenMeals.length,
itemBuilder: (ctx, index) =>
DailyEaten(mealsList.eatenMeals[index]),
),
);
},
),
),
],
);
}
as you can see there is nothing in my build method and I've tried to use Consumer as future to avoid infinite loop but it doesn't work for me please tell me what's wrong here I appreciate if u explain with code snippet

About preventing Listview.Builder() to rebulding it all items

I am getting users data from firebase by stream builder,
And I am rendering those data using Listview.builder(),
one problem is that when I get snapshot from firestore my all items in listview.builder() is rebuilding.
how do I prevent this thing reRendering or Rebuilding()
Hear is my Code
class GetUser extends StatelessWidget {
#override
Widget build(BuildContext context) {
print("// <1> Use StreamBuilder");
return StreamBuilder<QuerySnapshot>(
// <2> Pass `Stream<QuerySnapshot>` to stream
stream: FirebaseFirestore.instance
.collection('users')
.orderBy('createdAt', descending: false)
.snapshots(),
builder: (context, snapshot) {
if (snapshot.hasData) {
// <3> Retrieve `List<DocumentSnapshot>` from snapshot
final List<DocumentSnapshot> documents = snapshot.data.docs;
//documents.forEach((doc) => print(doc));
print(documents.length);
return SingleChildScrollView(
scrollDirection: Axis.vertical,
child: Column(
children: [
SizedBox(
height: MediaQuery.of(context).size.height,
child: ListView.builder(
addAutomaticKeepAlives: true,
addRepaintBoundaries: false,
itemCount: documents.length,
itemBuilder: (context, i) {
return Items(
doc: documents[i],
);
},
),
)
],
),
);
} else if (snapshot.hasError) {
return Text("Error");
} else {
return Text("Waiting");
}
});
}
}
//this Item class is invoked every time I add a user to the collection
class Item extends StatefulWidget {
final DocumentSnapshot doc;
const Item({Key key, this.doc}) : super(key: key);
#override
_ItemState createState() => _ItemState();
}
class _ItemState extends State<Item> {
#override
Widget build(BuildContext context) {
print("Build called ${widget.doc.id}");
return Text("${widget.doc["full_name"]}--${widget.doc.id}");
}
}

what is missing here ? Flutter - single selection

i'm creating a quiz app and i want to make some questions with single selection answer,
this is what it looks like for now :
Full code :
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
class question14 extends StatefulWidget {
#override
_question14State createState() => _question14State();
}
class _question14State extends State<question14> {
final isSelected = [false, false, false];
#override
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder(
stream: Firestore.instance
.collection('numberzz')
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) return const Text('Loading ...');
return ListView.builder(
scrollDirection: Axis.horizontal,
padding: EdgeInsets.fromLTRB(100.0, 0.0, 0.0, 0.0),
itemExtent: 200.0,
itemCount: snapshot.data.documents.length,
itemBuilder: (BuildContext context, int index) {
final DocumentSnapshot document =
snapshot.data.documents[index];
return
Container(
padding: EdgeInsets.fromLTRB(0.0, 300.0, 0.0, 450.0),
child: ListTile(
contentPadding: isSelected[index]
? EdgeInsets.all(0.0)
: EdgeInsets.all(25.0),
title: Image.network(
document['number'],
),
selected: !isSelected[index],
onTap: () {
Firestore.instance.runTransaction((transaction) async {
DocumentSnapshot freshSnap =
await transaction.get(document.reference);
await transaction.update(freshSnap.reference, {
'vote': freshSnap['vote'] + 1,
});
});
setState(() {
isSelected[index] = !isSelected[index];
});
},
),
);
},
);
);
}
}
thanks you so much for your help !
Instead of using list of booleans, you can just store the selected index.
I used selectedIndex to have the answer's selection which is selected by the user at last.
Updated code:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
class question14 extends StatefulWidget {
#override
_question14State createState() => _question14State();
}
class _question14State extends State<question14> {
int selectedIndex = 0;//for unselect, you can use null
#override
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder(
stream: Firestore.instance.collection('numberzz').snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) return const Text('Loading ...');
return ListView.builder(
scrollDirection: Axis.horizontal,
padding: EdgeInsets.fromLTRB(100.0, 0.0, 0.0, 0.0),
itemExtent: 200.0,
itemCount: snapshot.data.documents.length,
itemBuilder: (BuildContext context, int index) {
final DocumentSnapshot document =
snapshot.data.documents[index];
return Container(
padding: EdgeInsets.fromLTRB(0.0, 300.0, 0.0, 450.0),
child: ListTile(
contentPadding: selectedIndex == index
? EdgeInsets.all(0.0)
: EdgeInsets.all(25.0),
title: Image.network(
document['number'],
),
selected: selectedIndex == index,
onTap: () {
Firestore.instance.runTransaction((transaction) async {
DocumentSnapshot freshSnap =
await transaction.get(document.reference);
await transaction.update(freshSnap.reference, {
'vote': freshSnap['vote'] + 1,
});
});
setState(() {
selectedIndex = index;
});
},
),
);
},
);
}));
}
}