Flutter, Dart, Firestore: How can I send user data retrieved from firestore to a different screen? - firebase

I have a screen where a list of users are shown using stream builder to retrieve user data from firestore.
StreamBuilder(
stream: Collection
.where('WorkType', isEqualTo: widget.worktype)
.snapshots(),
builder: (context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
return Container(
height: 600,
child: ListView.builder(
itemCount: snapshot.data.docs.length,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
height: 100,
child: GestureDetector(
onTap: () {
//Navigate to Screen two
},
child: Card(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: <Widget>[
Text(
snapshot.data.docs[index].data()['Name'],
style: kRobotoSlab.copyWith(
fontSize: 20)),
Text(
snapshot.data.docs[index]
.data()['Address'],
style: kRobotoSlab.copyWith(
fontSize: 15),
),
Text(
snapshot.data.docs[index]
.data()['Phone Number'],
style: kRobotoSlab.copyWith(
fontSize: 15),
),
],
),
),
),
),
),
);
}),
);
} else if (snapshot.hasError) {
return Text(snapshot.error.toString());
} else {
return CircularProgressIndicator();
}
},
),
I want that once a user clicks on the card it will navigate to screen 2 which will show the user profile with the data retrieved from firestore
e.g. Card 1 : Name => Samia Address=> USA Number=> 4659848668 user will press on it and it will navigate to screen 2 with the information of Samia's.
How can I achieve it?

Let's say you call the new screen DetailScreen, you should have it take a Map of preferably an object of the items you want to display in it for example:
in the DetailScreen definition you can have:
class DetailScreen extends StatelessWidget {
// Declare a field that holds the Item.
final User user;
// In the constructor, require a user.
DetailScreen({Key? key, required this.user}) : super(key: key);
#override
Widget build(BuildContext context) {
// Use the Todo to create the UI.
return Scaffold(
appBar: AppBar(
title: Text(user.name),
),
body: Padding(
padding: EdgeInsets.all(16.0),
child: Text(user.address),
),
);
}
}
Then in your GestureDetector onTap method you can then do something like:
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailScreen(user: users[index]),
),
);
Since you are not using a model like my example above, you can make the second screen accept a Map field, then you pass in snapshot.data.docs[index].data() as the value when you are navigating to it.
So this will now read:
class DetailScreen extends StatelessWidget {
// Declare a field that holds the Item.
final Map user;
// In the constructor, require a user map.
DetailScreen({Key? key, required this.user}) : super(key: key);
#override
Widget build(BuildContext context) {
// Use the Todo to create the UI.
return Scaffold(
appBar: AppBar(
title: Text(user['Name']),
),
body: Padding(
padding: EdgeInsets.all(16.0),
child: Text(user['Address']),
),
);
}
}
And while navigating you just do
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailScreen(user: snapshot.data.docs[index].data()),
),
);

Related

Error: The argument type 'Stream<PostsRecord>' can't be assigned to the parameter type 'Record'

New to flutter here (and coding in general) but have an issue that I cannot figure out to solve. I am navigating to a page based off of a variable and need to pass in a Record. However, I cannot figure out how to pass a Record from the reference already on the page (postReference). I only want to read this document if the button is pressed also. Using Firebase/Firestore as the backend. Any help is greatly appreciated!
Error
Error: The argument type 'Stream' can't be assigned to the parameter type 'PostsRecord'.
Code snippet below. I cannot figure out how to properly query the record to then pass to the Widget Builder.
class NotificationPageWidget extends StatefulWidget {
const NotificationPageWidget({Key key}) : super(key: key);
#override
_NotificationPageWidgetState createState() => _NotificationPageWidgetState();
}
class _NotificationPageWidgetState extends State<NotificationPageWidget> {
final scaffoldKey = GlobalKey<ScaffoldState>();
#override
Widget build(BuildContext context) {
return Scaffold(
key: scaffoldKey,
appBar: AppBar(
automaticallyImplyLeading: true,
title: Text(
'Notifications',
style: FlutterFlowTheme.title1,
),
),
body: SafeArea(
child: Column(
mainAxisSize: MainAxisSize.max,
children: [
Expanded(
child: StreamBuilder<List<UserNotificationsRecord>>(
stream: queryUserNotificationsRecord(
queryBuilder: (userNotificationsRecord) =>
userNotificationsRecord
.where('notifiedUsers',
arrayContains: currentUserReference)
.orderBy('notificationTime', descending: true),
),
builder: (context, snapshot) {
// Customize what your widget looks like when it's loading.
if (!snapshot.hasData) {
return Center(
child: SizedBox(
width: 40,
height: 40,
child: CircularProgressIndicator(
color: FlutterFlowTheme.secondaryColor,
),
),
);
}
List<UserNotificationsRecord>
listViewUserNotificationsRecordList = snapshot.data;
return ListView.builder(
padding: EdgeInsets.zero,
scrollDirection: Axis.vertical,
itemCount: listViewUserNotificationsRecordList.length,
itemBuilder: (context, listViewIndex) {
final listViewUserNotificationsRecord =
listViewUserNotificationsRecordList[listViewIndex];
return InkWell(
onTap: () {
if (listViewUserNotificationsRecord.initialPageName == '/commentsPage') {
final postRef = PostsRecord.getDocument(listViewUserNotificationsRecord.postReference);
Navigator.push(context, MaterialPageRoute(builder: (context) => CommentsPageWidget(
activityRecord: postRef,
//This is where the error is occurring. Looking for a Record and not a Reference.
)
)
);
};
},
);
},
);
},
),
),
],
),
),
);
}
}

Firebase doesn't work cause of null-safety (DART/FLUTTER)

I'm using/learning Firebase for my database works. My snapshot's coming like _jsonQuerySnapshot or _jsonDocumentSnapshot. But it had to be QuerySnapshot or DocumentSnapshot. Because of this I have to encode and decode my snapshot for use my datas.
If I'm not using encode decode json I'm getting null or object errors all the time.
Here is my class extends from state
class _MyHomePageState extends State<MyHomePage> {
final _firestore = FirebaseFirestore.instance;
#override
Widget build(BuildContext context) {
CollectionReference moviesRef=_firestore.collection('movies');
DocumentReference babaRef = _firestore.collection('movies').doc('Baba');
return Scaffold(
backgroundColor: Colors.grey,
appBar: AppBar(
title: Text('FireStore Crud'),
),
body: Center(
child: Container(
child: Column(
children: [
StreamBuilder<QuerySnapshot>(
stream: moviesRef.snapshots(),
builder: (BuildContext context,AsyncSnapshot asyncSnapshot){
List<DocumentSnapshot>listOfDocumentSnapshot=asyncSnapshot.data.docs;
return Flexible(
child: ListView.builder(
itemCount: listOfDocumentSnapshot.length,
itemBuilder: (context,index){
Text('${listOfDocumentSnapshot[index].data()['name']}' ,style: TextStyle(fontSize: 24),);
},
),
);
},
),
],
),
),
),
);
}
}
and this is my error .
First of all, check your data is null or not and then use [] on it. Probably, listOfDocumentSnapshot[index].data() is null. If it is null, render another UI such as loading screen. Namely, your loading screen must be showed until reach the data.
for example:
builder: (BuildContext context,AsyncSnapshot asyncSnapshot){
List<DocumentSnapshot>? listOfDocumentSnapshot = asyncSnapshot.data.docs;
if(!listOfDocumentSnapshot.hasData || listOfDocumentSnapshot == null){
return LoadingScreen(); //etc.
}
return Flexible(
child: ListView.builder(
itemCount: listOfDocumentSnapshot.length,
itemBuilder: (context,index){
Text('${listOfDocumentSnapshot[index].data()['name']}' ,style: TextStyle(fontSize: 24),);
},
),
);
},
Futures (asynchronous programmes) need some time to get data and you have to make your UI wait until you get your data. e.g. database connections, read/write somethings to/from somewhere etc.
For more detail you can read this article.

"Read Data from Firebase", not working after update 2.8.1

Solution
Added code before return
if (!snapshot.hasData) {
return const Scaffold(
body: Center(
child: CircularProgressIndicator(
color: Colors.red,
)),
);
}
important! - if (!snapshot.hasData)
I try show data from FireBase, I have DB on Firestore.
I updated today Flutter (2.8.0 -> 2.8.1) , code worked before update
My code
import 'package:cloud_firestore/cloud_firestore.dart';
class FireService extends StatefulWidget {
#override
_FireServiceState createState() => _FireServiceState();
}
class _FireServiceState extends State<FireService> {
final Stream<QuerySnapshot> _usersStream = FirebaseFirestore.instance
.collection('chats/uHdJ4WvHjvphsBUpb9yk/visits')
.snapshots();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Visits'),
),
body: StreamBuilder(
stream: _usersStream,
builder:
(BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
return ListView(
children: snapshot.data!.docs.map((DocumentSnapshot document) {
Map<String, dynamic> data =
document.data()! as Map<String, dynamic>;
return Card(
child: ListTile(
title: Text(
data['company'],
style: TextStyle(fontSize: 19, color: Colors.black),
),
subtitle: Text(
data['number_visit'].toString(),
),
leading: Icon(
Icons.local_offer,
size: 40,
color: Colors.red,
),
),
);
}).toList(),
);
}),
);
}
}
After starting an error occurs
enter image description here
Please, write a new code so I understand the error. Thanks
UPD 1.
I tried an option
children: snapshot.data?.docs.map((DocumentSnapshot document) {
a new mistake - the whole block of code with a red line
enter image description here
Your line snapshot.data!.docs.map should use the nullish coalescing instead of the !.
The line should be:
snapshot.data?.docs.map
data?. will gracefully break if data is null

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.

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

Resources