Get information from Firebase and able to use it with a Gridtile and GestureDetector in Flutter - firebase

So I'm trying to design a Widget which can basically create a view where you can have something like this:
The problem that I'm facing is that for this example I used is calls to API's of Firebase, however, now I'm using the Firebase Auth and Firestore Extentionts and don't want to use the provider package or HTTP request, I have the following Firestore Collection:
As you see the collection name is users but inside has the user uid given by Firebase as a Document and inside that document has all the user Data.
So I need some help to have the following functionalities:
I need to create a ListView preferably ListView Builder so it can show the entire collection so it can display all the users contained in the collection. (Each user is a Document titled with his own User uid).
In the Tile, I want to display just name and Image but once it is clickable it should send me to a new page (the user profile page sending as an argument the uid)
I got this code: but I don't have the logic on how to get the list tile clickable and get the uid of the click user (in the tile) to send the info (uid) to the next page. But once I know how to display the contents of all the documents in the collection (displaying the name (nombre) inside each document as a title and how to capture the uid, I bet I can do the modifications and eliminations.
import 'package:flutter/material.dart';
import 'package:firebase_firestore/firebase_firestore.dart';
class ExpenseList extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection("users").snapshots,
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (!snapshot.hasData) return new Text("No existe registros de Usuario");
return new ListView(children: getExpenseItems(snapshot));
});
}
getExpenseItems(AsyncSnapshot<QuerySnapshot> snapshot) {
return snapshot.data.documents
.map((doc) => ListTile(title: new Text(doc["nombre"], onTap: (){
//Here function to navigate to profile page saving the uid
}))
.toList();
}
}
In that list tile, I should have a Network image as I can have a parameter the image from the document in the collection.
Is there anything I can do?
Kind Regards.

Here you can find an example of a custom build ListView.
var futureBuilder=new FutureBuilder(
future: makecall.firebaseCalls(databaseReference), // async work
builder: (BuildContext context, AsyncSnapshot snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none: return new Text('Press button to start');
case ConnectionState.waiting: return new Text('Loading....');
default:
if (snapshot.hasError)
return new Text('Error: ${snapshot.error}');
else
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index){
return Card(
elevation: 0.0,
child: Padding(
padding: const EdgeInsets.all(0.0),
child: SizedBox(
height: MediaQuery.of(context).size.height*0.15,
width: MediaQuery.of(context).size.width,
child: Card(
elevation: 0,
child: Row(
children: <Widget>[
new Container(
child: Image.network(snapshot.data[index].iconUrl,height: MediaQuery.of(context).size.width*0.3,width: MediaQuery.of(context).size.width*0.3,),
),
Padding(
padding: const EdgeInsets.only(left: 10,right: 5,top: 5),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
new Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Row(
children: <Widget>[
condition('${snapshot.data[index].vegOrNon}')==true? new Image.asset('images/non_veg.png',height: 15,width: 15,): new Image.asset('images/veg.jpg',height: 15,width: 15),
SizedBox(width: 5),
Text(snapshot.data[index].foodtitle, style: TextStyle(fontWeight: FontWeight.w500, fontSize: 20,fontFamily: 'Roboto-Black'),)
],
),
SizedBox(height:10.0),
Row(
children: <Widget>[
new IconTheme(
data: new IconThemeData(
color: Colors.black26),
child: new Icon(Icons.timer,size: 20.0,),
),
Text('${snapshot.data[index].PreptimeInMins} minutes',style: TextStyle(fontWeight: FontWeight.w700,color: Colors.black26),),
],
),
],
)
],
),
),
],
),
),
],
)
)
),
),
);
},
);
}
},
);
I found this example on this guide and I think that it can be usefull for you.
If your issue is how to make a ListView tile clickable you can use the GestureDetector class to wrap your tile and send the UID in the .OnTap event

Related

Get array collection of an object from firebase flutter

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(),
),
),
),
);
}

Flutter/Firestore: Displaying gridtiles in categories

We are creating a recipe app for a school project.
We are using Dart/Flutter for the language and we have recipes stored in a Firestore DB collection called 'recipes' which have sub-collections of ingredients, comments and method. Inside the ingredients collection, there is a field called 'proteins' that contains an array of proteins (beef, pork, poultry, etc)
I have managed to make one big grid view which displays thumbnails of all the recipes currently stored in the DB, but we want to set them in categories by their proteins. I managed to make the individual lists for the categories but they each contain all of the recipes. I don't know now which direction to go to somehow search through the DB and then display them on the page.
This is the code for the current list that is being created.
I thought about somehow creating a search function that would create an array of document ID's which would be then used in the compiling of the lists, but not sure where to start
I'm just trying to get some nudge in the direction of how it would be done and not the code. The process of it if you will.
Thanks in advance
child: Container(
alignment: Alignment.bottomRight,
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height - 100.0,
child: ListView.builder(
itemCount: 4,
itemBuilder: (context, index) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.symmetric(horizontal: 8.0),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
'${categories[index]}',//displays the protein name (beef
style: TextStyle(
color: Colors.red,
fontWeight: FontWeight.bold,
fontSize: 25.0),
),
),
),
Container(
height: 180.0,
child: StreamBuilder(
stream: firestoreDb,
builder: (
context,
snapshot,
) {
if (!snapshot.hasData)
return CircularProgressIndicator();
return ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: snapshot.data.docs.length,
itemBuilder: (context, int index) {
return GestureDetector(
child: Card(
elevation: 5,
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(10)),
child: TestGridTile(
snapshot: snapshot.data,
index: index,
),
),
);
});
}),
),
],
),
),
)),
EDIT TestGridTile code as requested
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:youth_food_movement/recipe/ui/ingredients_page.dart';
//card that displays the recipe information
class TestGridTile extends StatelessWidget {
//snapshot of the database
final QuerySnapshot snapshot;
final int index;
const TestGridTile({Key key, this.snapshot, this.index}) : super(key: key);
static String idNumber;
#override
Widget build(BuildContext context) {
//snaphot of the docs
// ignore: unused_local_variable
var snapshotData = snapshot.docs[index];
var docID = snapshot.docs[index].id;
String recipeID = docID.toString();
return Container(
width: 150,//MediaQuery.of(context).size.width,
height: 150,//MediaQuery.of(context).size.height * 0.25,
//get the image URL
child: FutureBuilder(
future: _getImageURL(docID),
builder: (context, snapshot) {
if (snapshot.hasData) {
//return the image and make it cover the container
return GestureDetector(
child: Image.network(
snapshot.data,
fit: BoxFit.fill,
),
onTap: () {
idNumber = recipeID;
Navigator.push(
context,
MaterialPageRoute(
builder: (BuildContext context) =>
IngredientsPage(recipeID)));
},
);
} else {
return Container(
child: Center(
child: CircularProgressIndicator(),
));
}
}),
);
}
//method to get the image URL
Future _getImageURL(var docID) async {
//declare and instantiate the firebase storage bucket
final FirebaseStorage storage = FirebaseStorage.instanceFor(
bucket: 'gs://youth-food-movement.appspot.com');
//ref string will change so the parameter will be the jpg ID (maybe)
String downloadURL =
await storage.ref('recipe_images/$docID').getDownloadURL();
return downloadURL;
}
}```
I have attached an image of how it looks currently[![current image][1]][1]
[1]: https://i.stack.imgur.com/HiaYi.jpg
Because the ingredience is a collection we don't get it with the with the initial data. You have two options:
As it is now load all recipes and for each of them the ingrediences and for each category filter out the recipes that don't fit in it and show only those that do fit in. If you would share more code I could help you with it. What is behind TestGridTile.
Load for each category only the recipes that fin it by using a query with where clause and the arrayContaines. But for that the array proteins can't be nested in the ingredience subcollection of the collection you are filtering.
Both of the would be much easier if you would move the proteines array to the doc of the recipes collection. You could do that with the client side code or even with Firebase Cloud Functions.

How to get data from two collections in firebase using Flutter

This is my problem:
I have a ListPost StatefulWidget where I want to display a list of widgets that contains the user's account image, the user's name, and the user's posts images(similar to Facebook feeds), however, I have gotten to the point that I need to get that data from two different collections in Firebase (see my firebase collections image below).
The good thing is that I have been able to get that data only from one collection(userFeed) and display that data in my ListPost file in different widgets, however, I do not know how to get data from another collection in Firebase using the same streamBuilder and display all that data I want to display in other widgets in my ListPost screen.
So, my specific question is:
How can I make my ListPost screen to populate data from 2 different collections in Firebase using a stream builder or another type of implementation?
This is the firebase image
This is the complete code for the ListPost screen
import 'package:cached_network_image/cached_network_image.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'models/post_model.dart';
final _stream = Firestore.instance.collection('userFeed').snapshots();
class ListPosts extends StatefulWidget {
#override
_ListPostsState createState() => _ListPostsState();
}
class _ListPostsState extends State<ListPosts> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
//this is the Streambuilder to get the data however it only lets me to get one collection
child: StreamBuilder(
stream: _stream,
builder: (context, snapshot) {
if (!snapshot.hasData) return const Text('Loading...');
return ListView.builder(
itemExtent: 550.0,
itemCount: snapshot.data.documents.length,
itemBuilder: (BuildContext context, int data) {
//here I get the data from the userFeed colecction
Post post = Post.fromDoc(snapshot.data.documents[data]);
return Column(
children: <Widget>[
GestureDetector(
child: Container(
padding: EdgeInsets.symmetric(
horizontal: 16.0,
vertical: 10.0,
),
child: Row(
children: <Widget>[
CircleAvatar(
radius: 25.0,
backgroundColor: Colors.grey,
backgroundImage: post.imageUrl.isEmpty
? AssetImage(
'assets/images/user_placeholder.jpg')
: CachedNetworkImageProvider(post.imageUrl),
),
SizedBox(width: 8.0),
Text(
post.caption,
style: TextStyle(
fontSize: 18.0,
fontWeight: FontWeight.w600,
),
),
],
),
),
),
GestureDetector(
child: Stack(
alignment: Alignment.center,
children: <Widget>[
Container(
height: MediaQuery.of(context).size.width,
decoration: BoxDecoration(
image: DecorationImage(
image:
CachedNetworkImageProvider(post.imageUrl),
fit: BoxFit.cover,
),
),
),
],
),
),
],
);
},
);
},
),
),
);
}
}
UPDATE 05-22-2020 HOW I FIXED THE ISSUE
Credits to the user griffins, he helped me to fix this issue.
This is what I do:
I nested my StreamBuilder so I can use 2 streams at the same time
return StreamBuilder(
stream: _stream,
builder: (context, snapshot1) {
return StreamBuilder(
stream: _stream2,
builder: (context, snapshot2) {
if (!snapshot2.hasData) return const Text('Loading...');
if (!snapshot1.hasData) return const Text('Loading...');
return ListView.builder(
itemExtent: 550.0,
itemCount: snapshot2.data.documents.length,
itemBuilder: (BuildContext context, int data) {
User user = User.fromDoc(snapshot2.data.documents[data]);
Post post = Post.fromDoc(snapshot1.data.documents[data]);
return buildbody(user, post, context);
},
);
},
);
},
);
You can can make you body take a widget ListView and for the Listview children have both your lists.
example
body: ListView(
children: <Widget>[
---list1----
--list2-----
]);
or you can use a custom scroll view
return new Scaffold(
appBar: new AppBar(
title: new Text("Project Details"),
backgroundColor: Colors.blue[800]),
body:
new CustomScrollView(
slivers: <Widget>[
new SliverPadding(padding: const EdgeInsets.only(left: 10.0,right: 10.0,
top: 10.0,bottom: 0.0),
sliver: new SliverList(delegate:
new SliverChildListDelegate(getTopWidgets())),
),
new SliverPadding(padding: const EdgeInsets.all(10.0),
sliver: new SliverList(delegate: new SliverChildListDelegate(
getSfListTiles()
))),
new SliverPadding(padding: const EdgeInsets.all(10.0),
sliver: new SliverList(delegate: new SliverChildListDelegate(
getWorkStatementTiles()
))),
]
)
);
update
from #Rémi Rousselet answer You can nest StreamBuilder
StreamBuilder(
stream: stream1,
builder: (context, snapshot1) {
return StreamBuilder(
stream: stream2,
builder: (context, snapshot2) {
// do some stuff with both streams here
},
);
},
)

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(),
],
)));
}

Resources