Get array collection of an object from firebase flutter - firebase

i'm trying to fetch Products collection from specific user, and the request isn't working.
here is my code:
the first request function:
Stream<QuerySnapshot<Object>> get productsUser {
return usersCollection.doc(uid).collection("Products").snapshots();
}
and here where I try to present the Products array I fetch (or didn't...):
class _ProductPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
final GivitUser givitUser = Provider.of<GivitUser>(context);
final DatabaseService db = DatabaseService(uid: givitUser.uid);
return StreamBuilder<QuerySnapshot>(
stream: db.productsUser,
builder: (context, snapshotProduct) {
if (snapshotProduct.hasError) {
return Text('Something went wrong');
}
if (snapshotProduct.connectionState == ConnectionState.waiting) {
return Loading();
}
return Container(
color: Colors.blue[100],
height: 400.0,
alignment: Alignment.topCenter,
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: snapshotProduct.data.docs.map(
(DocumentSnapshot document) {
var snapshotdata = document.data() as Map;
Product product =
Product.productFromDocument(snapshotdata, document.id);
print(product.name);
return Container(
child: Text(product.name),
);
},
).toList(),
),
),
);
});
}
}
);
Thanks to everyone who will help! :)

You can either create a StatefulWidget, and store the result of the fetch as state, or you can use a StreamBuilder to manage the state for you, and automatically rebuild the widget tree each time a new snapshot is received. In either case, the following two guides may also be helpful:
Streams
Async/Await
Here's an example of how you might use StreamBuilder in your case:
Widget build(BuildContext context) {
return Container(
color: Colors.blue[100],
height: 400.0,
alignment: Alignment.topCenter,
child: SingleChildScrollView(
child: StreamBuilder<QuerySnapshot<Object>>(
stream: usersCollection.doc(uid).collection("Products").snapshots(),
builder: (context, asyncSnapshot) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: asyncSnapshot.data.data.docs.map(
(DocumentSnapshot document) {
var snapshotdata = document.data() as Map;
Product product =
Product.productFromDocument(snapshotdata, document.id);
print(product.name);
return Container(
child: Text(product.name),
);
},
).toList(),
),
),
),
);
}

Related

Flutter/Dart/Firebase - wait until data has loaded before displaying

I am trying to create a list of unique "events" in my app. I have created a couple of functions to extract the data from firebase:
// event list from snapshot
List<String> _eventsFromSnapshot(QuerySnapshot snapshot) {
return snapshot.docs.map(
(doc) {
return doc['event'].toString() ?? '';
},
).toList();
}
//get events data
Stream<List<String>> get events {
return productCollection.snapshots().map(_eventsFromSnapshot);
}
I then want to build my list view in another screen. I have implemented my StreamProvider in the root page of my homescreen:
class OurHomePage extends StatefulWidget {
#override
_OurHomePageState createState() => _OurHomePageState();
}
class _OurHomePageState extends State<OurHomePage> {
#override
Widget build(BuildContext context) {
return StreamProvider<List<Product>>.value(
value: OurDatabase().products.handleError((e) {
print(e.toString());
}),
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Align(
alignment: Alignment.center,
child: Column(
children: [
OurHeadline(),
AllCards(),
HowItWorks(),
],
),
),
),
),
);
}
}
And then I create a function to return the list of Strings and use that in my stateless widget:
class AllCards extends StatelessWidget {
#override
Widget build(BuildContext context) {
final List<String> uniqueEventList = getListOfEvents(context);
return Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
width: double.infinity,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [Text('Browse all Cards'), Text('Shop All')],
mainAxisAlignment: MainAxisAlignment.spaceBetween,
),
SizedBox(
height: 125,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: uniqueEventList.length,
itemBuilder: (context, i) {
return Container(
decoration: BoxDecoration(
border: Border.all(),
),
width: 160.0,
child: Center(
child: Text(uniqueEventList[i]),
),
);
},
),
)
],
),
),
);
}
List<String> getListOfEvents(BuildContext context) {
final uniqueEvents = Provider.of<List<Product>>(context);
final List<String> list = [];
for (var item in uniqueEvents) {
list.add(item.event);
}
return list.toSet().toList();
}
}
The problem is that whenever I switch pages, for a split second I get this message and an error appears:
The getter 'iterator' was called on null.
Receiver: null
Tried calling: iterator
Which indicates to me that I need to use some sort of async functionality to wait for the events data to finish loading, but is there a simple way to do this without going for something like a Future builder?
Any help would be appreciated!

How to display items from Firestore by recently added in Flutter?

There's a problem which I'm trying to solve, it is displaying data by recently added to Firestore, through Flutter. What can be done in my case?
In React I would achieve this with useState hook, how can this be achieved in Flutter?
I read about .sort(); method, is that a right way of doing this?
Code:
Form.dart
class FormText extends StatelessWidget {
final String _labelText = 'Enter your weight..';
final String _buttonText = 'Save';
final _controller = TextEditingController();
final dateFormat = new DateFormat.yMMMMd().add_jm();
final _collection =
FirebaseFirestore.instance.collection('weightMeasurement');
void saveItemToList() {
final weight = _controller.text;
if (weight.isNotEmpty) {
_collection.add({
'weight': weight,
'time': dateFormat.format(DateTime.now()),
});
} else {
return null;
}
_controller.clear();
}
#override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
Expanded(
child: TextField(
keyboardType: TextInputType.number,
controller: _controller,
decoration: InputDecoration(
labelText: _labelText,
),
),
),
FlatButton(
color: Colors.blue,
onPressed: saveItemToList,
child: Text(
_buttonText,
style: TextStyle(
color: Colors.white,
),
),
),
],
);
}
}
Measurements.dart
class RecentMeasurement {
Widget buildList(QuerySnapshot snapshot) {
return ListView.builder(
reverse: false,
itemCount: snapshot.docs.length,
itemBuilder: (context, index) {
final doc = snapshot.docs[index];
return Dismissible(
background: Container(color: Colors.red),
key: Key(doc.id),
onDismissed: (direction) {
FirebaseFirestore.instance
.collection('weightMeasurement')
.doc(doc.id)
.delete();
},
child: ListTile(
title: Expanded(
child: Card(
margin: EdgeInsets.all(30.0),
child: Column(
children: <Widget>[
Text('Current Weight: ' + doc['weight'] + 'kg'),
Text('Time added: ' + doc['time'].toString()),
],
),
),
),
),
);
},
);
}
}
Layout.dart
class Layout extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(30.0),
child: Column(
children: <Widget>[
FormText(),
StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance
.collection('weightMeasurement')
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) return LinearProgressIndicator();
return Expanded(
child: RecentMeasurement().buildList(snapshot.data),
);
}),
],
),
);
}
}
You can try order by . Here is an example
firestoreDb.collection("weightMeasurement")
.orderBy("date", Query.Direction.ASCENDING)
You have to use "orderBy" on your collection, but previously You have to store something called timestamp. Make sure when You upload Your items to Firebase to also upload DateTime.now() along with Your items so You can order them by time. Do not forget to use Ascending direction since it will show you Your items ordered correctly.

Getting data from cloud firestore onto a listview in flutter

I am trying to pull data from a firebase cloud firestore collection (events) onto a list view, I’m not sure if I am implementing this correctly, when I run the app I get the error 'MappedListIterable' is not a subtype of type 'Widget'. This is my first time working with firebase cloud firestore and I could really use some help in better understanding this error.
This is where the list view is being initialized:
import 'package:flutter/material.dart';
import 'package:rallie_app/utils/event_summary.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
class HomeList extends StatelessWidget {
Firestore db = Firestore.instance;
#override
Widget build(BuildContext context) {
return Expanded(
child: StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection('events').snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot){
// count of events
final int eventCount = snapshot.data.documents.length;
if (snapshot.hasError)
return new Text('Error: ${snapshot.error}');
switch (snapshot.connectionState){
case ConnectionState.waiting:
return Center(child: CircularProgressIndicator());
default:
return new ListView.builder(
itemCount: eventCount ,
itemBuilder: (context, index) {
final DocumentSnapshot document = snapshot.data.documents[index];
return new EventSummary(document);
}
);
}
})
);
}
}
These are the list view items I wish to build :
import 'package:flutter/material.dart';
import 'package:rallie_app/model/events.dart';
import 'package:rallie_app/ui/detail/detail_page.dart';
import 'package:rallie_app/services/firestore_service.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'dart:async';
class EventSummary extends StatefulWidget {
//TODO: Event summary constructor with event model class initialized in it
final DocumentSnapshot event;
EventSummary(this.event);
#override
_EventSummaryState createState() => _EventSummaryState();
}
class _EventSummaryState extends State<EventSummary> {
#override
Widget build(BuildContext context) {
final userThumbnail = new Container(
margin: EdgeInsets.symmetric(vertical: 16.0),
alignment: FractionalOffset.centerLeft,
child: Hero(
tag: "user-image-${widget.event.data['id']}",
child: CircleAvatar(
backgroundImage: AssetImage(widget.event['event_poster_image']),
// backgroundColor: Colors.white,
maxRadius: 40.0,
),
),
);
final eventCardContent = Container(
margin: new EdgeInsets.only(left: 46.0),
decoration: new BoxDecoration(
shape: BoxShape.rectangle,
color: new Color(0xFFFFFFFF),
borderRadius: new BorderRadius.circular(8.0),
image: DecorationImage(
image: AssetImage(widget.event.data['event_image']),
fit: BoxFit.fill,
),
),
);
Widget _eventValue(){
return Column(
children: <Widget>[
Container(
height: 150.0,
margin: const EdgeInsets.symmetric(
vertical: 16.0,
horizontal: 24.0,
),
child: new Stack(
children: <Widget>[
eventCardContent,
userThumbnail,
],
),
),
Container(
margin: const EdgeInsets.only(left: 70.0, bottom: 20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
widget.event.data['event_name'],
textAlign: TextAlign.start,
),
Row(
//crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
widget.event.data['event_date'],
textAlign: TextAlign.start,
),
SizedBox(
width: 110,
),
IconButton(
icon: Icon(Icons.share),
splashColor: Colors.orange,
tooltip: 'Share button',
onPressed: () =>
debugPrint('Share btn tapped'),
)
],
),
Text(
widget.event.data['event_attending'],
textAlign: TextAlign.start,
),
],
),
)
],
);
}
return new GestureDetector(
onTap: () => Navigator.of(context).push(
new PageRouteBuilder(
pageBuilder: (_, __, ___) => new DetailPage(widget.event.data['id']),
transitionsBuilder:
(context, animation, secondaryAnimation, child) =>
new FadeTransition(opacity: animation, child: child),
),
),
child: StreamBuilder(
stream: Firestore.instance.collection('events').snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) return Text('Loading data... Please wait');
return snapshot.data.documents.map(
(document) => _eventValue()
);
}),
);
}
}
In Your Code - Edit - widget.event['id'] to - widget.event.data['id'] & So On same with Other Places where you have Snapshot variable used...
As Per Documentation - DocumentSnapshot
A DocumentSnapshot contains data read from a document in your Cloud
Firestore database. The data can be extracted with .data()
widget.event is - DocumentSnapshot & to read the data you need to use .data Method.
Also the Error you are Getting is of Code :
child: StreamBuilder(
stream: Firestore.instance.collection('events').snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) return Text('Loading data... Please wait');
return snapshot.data.documents.map(
(document) => Column(
......
Here Builder is Expecting a Widget as a return value not 'MappedListIterable' -
snapshot.data.documents.map(
(document) // is Wrong return value for StreamBuilder.
You Need Modify Your Code to return a widget here.

Loading only specific data from Firestore using StreamBuilder in Flutter/Dart

I am trying to load data from Firestore using StreamBuilder.
In my collection called 'connect' there are may documents.
But I want to load only specific documents.
'connections' is a List that contains some keys in 'connect' collection.
'hello' is a QuerySnapshot List that contains some documents in 'collection' collection.
For example, if my DB contains documents with documentID of followings:
-LK5SAToCPhI1Zp5W_bL
-LK5Ypv0HeDCwcN4K41M
-LK5j-OGtNjMpgklUB4B
-LK5mOih9wuz5ZSebXMn
a list 'connections' contains only a portion such as:
-LK5SAToCPhI1Zp5W_bL
-LK5Ypv0HeDCwcN4K41M
In a StreamBuilder I want to load only documents that have same name in connections. How can I load only specific documents?
Please Help me!
Firestore.instance.collection('connect')
.snapshots()
.listen((docSnap) {
for (DocumentSnapshot docs in docSnap.documents) {
if (connections.isNotEmpty) {
for (String keys in connections) {
if (docs.documentID.toString() == keys) {
hello.add(docs);
}
}
}
}
});
#override
Widget build(BuildContext context) {
return new Scaffold(
body: Center(
child: Column(
children: <Widget>[
Row(
children: <Widget>[
Padding(
padding: EdgeInsets.only(bottom: 60.0),
),
Expanded(
child: Text(
"Invitation!",
style: TextStyle(fontWeight: FontWeight.bold),
textAlign: TextAlign.center,
),
),
],
),
Flexible(
child: StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection('connect').snapshots(),
builder:
(BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot) {
if (!snapshot.hasData) return const Text("Loading ... ");
final int messageCount = snapshot.data.documents.length;
return ListView.builder(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
itemCount: messageCount,
itemBuilder: (_, int index) {
final DocumentSnapshot document =
snapshot.data.documents[index];
if (document.documentID ==
hello[index].documentID) {
return Container(
child: Row(
children: <Widget>[
Text("Hello $messageCount")
],
),
);
} else {
return Container(
child: Row(
children: <Widget>[Text("Wrong")],
),
);
}
});
},
),
),
_buildLayout(),
],
)));
}

Using TextField inside a Streambuilder

How do we add a TextField inside a StreamBuilder?
I have a TextField / TextFormField as one of the widgets inside the builder function of either a StreamBuilder or FutureBuilder, whenever we try to interact with the textfield it just refreshes the entire builder widget and calls the stream/future again.
body: StreamBuilder(
stream: getClientProfile().snapshots(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.active) {
print(snapshot.data.data);
Client tempClient = Client.from(snapshot.data);
print('details = ${tempClient.representative.email} ${tempClient
.address.location} ${tempClient.businessDescription}');
return Container(
child: Column(
children: <Widget>[
TextFormField(
)
],
),
);
} else if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
} else {
return Center(
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: Icon(Icons.error),
),
Text('Error loading data')
],
),
);
}
}),
and firestore function
DocumentReference getClientProfile() {
return _firestore.collection(SELLERS_COLLECTION).document(_uid);
}
What I want to achieve, is to have a form with pre-filled data from firestore document, basically an edit form. Is there any other way I could achieve the same or am I doing something wrong structurally ?
EDIT:
code after suggested edits.
import 'package:flutter/material.dart';
import 'Utils/globalStore.dart';
import 'models/client_model.dart';
import 'dart:async';
class EditProfileInformation extends StatefulWidget {
#override
EditProfileInformationState createState() {
return new EditProfileInformationState();
}
}
class EditProfileInformationState extends State<EditProfileInformation> {
Stream dbCall;
final myController = TextEditingController();
#override
void initState() {
// TODO: implement initState
super.initState();
dbCall = getClientProfile().snapshots();
myController.addListener(_printLatestValue);
}
_printLatestValue() {
print("Second text field: ${myController.text}");
}
#override
void dispose() {
myController.removeListener(_printLatestValue);
myController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
// key: _scaffoldKey,
appBar: AppBar(
title: Text(
'Edit profile',
style: TextStyle(),
),
),
body: StreamBuilder(
stream: dbCall,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.active) {
print(snapshot.data.data);
Client tempClient = Client.from(snapshot.data);
print('details = ${tempClient.representative.email} ${tempClient
.address.location} ${tempClient.businessDescription}');
return Container(
child: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
controller: myController,
),
)
],
),
);
} else if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
} else {
return Center(
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: Icon(Icons.error),
),
Text('Error loading data')
],
),
);
}
}),
floatingActionButton: FloatingActionButton(
onPressed: () {
},
child: Icon(Icons.done),
),
);
}
}
In order to use a StreamBuilder correctly you must ensure that the stream you are using is cached on a State object. While StreamBuilder can correctly handle getting new events from a stream, receiving an entirely new Stream will force it to completely rebuild. In your case, getClientProfile().snapshots() will create an entirely new Stream when it is called, destroying all of the state of your text fields.
class Example extends StatefulWidget {
#override
State createState() => new ExampleState();
}
class ExampleState extends State<Example> {
Stream<SomeType> _stream;
#override
void initState() {
// Only create the stream once
_stream = _firestore.collection(collection).document(id);
super.initState();
}
#override
Widget build(BuildContext context) {
return new StreamBuilder(
stream: _stream,
builder: (context, snapshot) {
...
},
);
}
}
EDIT: it sounds like there are other problems which I cannot diagnose from the code snippet you provided.

Resources