Flutter Firestore doc get returning null - firebase

I am trying to get a document from a Firestore collection using the following code:
firebase_service.dart:
class FirebaseService {
final firestoreInstance = FirebaseFirestore.instance;
final FirebaseAuth auth = FirebaseAuth.instance;
Map<String, dynamic> getProfile(String uid) {
firestoreInstance.collection("Artists").doc(uid).get().then((value) {
return (value.data());
});
}
}
home_view.dart:
Map<String, dynamic> profile =
firebaseService.getProfile(auth.currentUser.uid);
When stepping through the code the profile variable is null in home_view.dart, but value.data() in firebase_service.dart contains a map. Is there a reason why this value isn't being returned in home_view.dart?

Your code needs a few edits, as the getProfile function is async.
class FirebaseService {
final firestoreInstance = FirebaseFirestore.instance;
final FirebaseAuth auth = FirebaseAuth.instance;
// set the return type to Future<Map<String, dynamic>>
Future<Map<String, dynamic>> getProfile(String uid) async { // insert async here
/// insert a return and await here
return await firestoreInstance.collection("Artists").doc(uid).get().then((value) =>
return value.data(); // the brackets here aren't needed, so you can remove them
});
}
}
Then finally in home_view.dart
// insert await here:
Map<String, dynamic> profile = await
firebaseService.getProfile(auth.currentUser.uid);
If you plan to use the getProfile function I suggest you to use a FutureBuilder.
In you home_view.dart's build function write this:
return FutureBuilder(
future: firebaseService.getProfile(auth.currentUser.uid),
builder: (context, snapshot){
if (!snapshot.hasData){
return Center(child: CircularProgressIndicator(),);
}
final Map<String, dynamic> profile = snapshot.data.data();
return YourWidgets();
});
And now you don't need to write:
Map<String, dynamic> profile = await
firebaseService.getProfile(auth.currentUser.uid);

This is an async operation and you have to await for its value.
For reference, you can take a look here at documentation of how propper authentication and CRUD operations made in Firebase with flutter.

Related

flutter: type '(DocumentSnapshot<MyObject>) => MyObject' is not a subtype of type '(DocumentSnapshot<Object>) => MyObject'

I have difficulties to read (and show) data from firebase DB.
I get this warning:
type '(DocumentSnapshot) => GivitUser' is not a subtype of type '(DocumentSnapshot) => GivitUser'
I realy don't know what I'm doing wrong, I tried to convert from DocumentSnapshot to DocumentSnapshot but without success.
this is my database.dart file:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:givit_app/models/givit_user.dart';
class DatabaseService {
final String uid;
DatabaseService({#required this.uid});
final CollectionReference userCollection =
FirebaseFirestore.instance.collection('Users');
Future<void> updateGivitUserData(
String email, String fullName, String password, int phoneNumber) async {
return await userCollection.doc(uid).set({
'Email': email,
'Full Name': fullName,
'Password': password,
'Phone Number': phoneNumber,
});
}
Future<GivitUser> getGivitUser(String uid) async {
return await userCollection.doc(uid).get().then((user) => user.data());
}
GivitUser _userDataFromSnapshot(DocumentSnapshot<GivitUser> snapshot) {
return GivitUser(
uid: uid,
email: snapshot.data().email,
fullName: snapshot.data().fullName,
phoneNumber: snapshot.data().phoneNumber,
);
}
Stream<GivitUser> get userData {
return userCollection.doc(uid).snapshots().map(_userDataFromSnapshot);
}
}
and this is where I try to use the database.dart file and present the data:
Widget build(BuildContext context) {
GivitUser user = Provider.of<GivitUser>(context);
final DatabaseService db = DatabaseService(uid: user.uid);
return StreamBuilder<GivitUser>(
stream: db.userData,
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Loading();
}
print('after getter');
print(snapshot.data);
GivitUser givitUser = snapshot.data;
return Scaffold(
....
....
....
Thank in advance, I will appreciate any help :)
edit: a screenshot of the error:
ERROR SCREENSHOT
I had this exact problem, what helped me was trying to get the collection first and then working on this specific document, maybe try to change this code:
final DatabaseService db = DatabaseService(uid: user.uid);
return StreamBuilder<GivitUser>(
stream: db.userData,
to this code:
final DatabaseService db = DatabaseService(uid: user.uid);
return StreamBuilder<GivitUser>(
stream: db.userCollection.doc(db.uid).get(),
like this documents says https://firebase.flutter.dev/docs/firestore/usage/#one-time-read
and then you can get to the field like:
builder: (context, snapshot) {
/...
Map<String, dynamic> data = snapshot.data.data();
I hope it will help you too! :)
This line userCollection.doc(uid).snapshots() returns Stream<DocumentSnapshot> and not Stream<DocumentSnapshot<GivitUser>> as you declared in the _userDataFromSnapshot argument.
Reference
Stream<DocumentSnapshot> snapshots({bool
includeMetadataChanges = false})
You need to change this block of code below:
GivitUser _userDataFromSnapshot(DocumentSnapshot<GivitUser> snapshot) {
//...
}
to this:
GivitUser _userDataFromSnapshot(DocumentSnapshot snapshot) {
//...
}
snapshot.data() returns a Map<String, dynamic> object.
So you can get your data like you would from a Map object.
The _userDataFromSnapshot method can be updated to:
GivitUser _userDataFromSnapshot(DocumentSnapshot snapshot) {
var snapshotData = snapshot.data() as Map;
return GivitUser(
uid: uid,
email: snapshotData['Email'],
fullName: snapshotData['Full Name'],
phoneNumber: snapshotData['Phone Number'],
);
}

How to Get Array Field Values From Firestore

I have a Flutter app that uses Firestore to store user data. I need help with retrieving the values stored in the 'friends' array. The image below shows the structure of my Firestore. As you can see, the 'friends' field is an array with two values: '123456' and '789123'.
I want to store these values in my variable called friendsList and I try to do this in getFriendsList(). To test and see if the 'friends' array values were stored in the friendsList variable, I use a print statement at the end of getFriendsList() to print the value of friendsList. But when I check my Console, Instance of 'Future<dynamic>' is printed and not the values of the 'friends' field.
How can I assign the values of the 'friends' array field from Firestore into my friendsList variable?
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:mood/components/nav_drawer.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
FirebaseAuth auth = FirebaseAuth.instance;
User currentUser;
String currentUserUID;
Future<dynamic> friendsList;
class LandingScreen extends StatefulWidget {
static const String id = 'landing_screen';
#override
_LandingScreenState createState() => _LandingScreenState();
}
class _LandingScreenState extends State<LandingScreen> {
final _auth = FirebaseAuth.instance;
#override
void initState() {
super.initState();
getUserData();
}
void getUserData() {
getCurrentUser();
getCurrentUserUID();
getFriendsList();
}
void getCurrentUser() {
final currentUser = _auth.currentUser;
}
void getCurrentUserUID() {
currentUserUID = auth.currentUser.uid;
}
void getFriendsList() {
friendsList = FirebaseFirestore.instance
.collection("Users")
.doc(currentUserUID)
.get()
.then((value) {
return value.data()["friends"];
});
print(friendsList);
}
In the then callback, just assign your list to friendsList and change your friendsList to List<dynamic> type
FirebaseFirestore.instance
.collection("Users")
.doc(currentUserUID)
.get()
.then((value) {
friendsList = value.data()["friends"];
print(friendsList);
});
According to your comment for async await syntax,
final value = await FirebaseFirestore.instance
.collection("Users")
.doc(currentUserUID)
.get();
friendsList = value.data()["friends"];

How to use transaction with int data - Firestore Flutter

Upon creating an account the user's firestore creates a field that displays the current amount of plastics the user has. So far, I have a button that updates that amount using the user's text field input. I've heard of something called a transaction which apparently allows one to intsead add the input amount to the overall data for it to be displayed? How would I accomplish this in my case when the use inputs a new amount?
Code:
database.dart
Future<void> userSetup(String displayName) async {
int plastics = 0;
final CollectionReference users =
FirebaseFirestore.instance.collection('UserNames');
FirebaseAuth auth = FirebaseAuth.instance;
String uid = auth.currentUser.uid.toString();
users.doc(uid).set({'displayName': displayName, 'uid': uid});
//This is the field for plastics amount starting at 0.
users.doc(uid).update({'plastics': plastics});
return;
}
How I retrieve the amount data:
final firestore = FirebaseFirestore.instance;
FirebaseAuth auth = FirebaseAuth.instance;
Future<String> getPlasticNum() async {
final CollectionReference users = firestore.collection('UserNames');
final String uid = auth.currentUser.uid;
final result = await users.doc(uid).get();
return result.data()['plastics'].toString();
}
How I display it:
FutureBuilder(
future: getPlasticNum(),
builder: (_, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
}
return Text(snapshot.data,
style: TextStyle(color: Colors.amber[400], fontSize: 20));
},
),
Currently how the user can replace the amount, but not add to it(The problem)
OnPressed: () async {
try {
final String uid = auth.currentUser.uid;
FirebaseFirestore.instance
.collection('UserNames')
.doc(uid)
.update({
"plastics": int.parse(_plasticController.text),
});
Navigator.of(context).pop();
} catch (e) {}
},
I made a separate future to take care of adding:
final firestore = FirebaseFirestore.instance; //
FirebaseAuth auth = FirebaseAuth.instance;
Future<bool> addPlastic(String amount) async {
try {
String uid = auth.currentUser.uid;
var value = double.parse(amount);
DocumentReference documentReference =
FirebaseFirestore.instance.collection('UserNames').doc(uid);
FirebaseFirestore.instance.runTransaction((transaction) async {
DocumentSnapshot snapshot = await transaction.get(documentReference);
if (!snapshot.exists) {
documentReference.set({'plastics': value});
return true;
}
double newAmount = snapshot.data()['plastics'] + value;
transaction.update(documentReference, {'plastics': newAmount});
return true;
});
} catch (e) {
return false;
}
}
Then I just called it when the button was pressed
onPressed(){
addPlastics(_plasticController.text);
}

Flutter - Receive and then modify data from Stream

I'm attempting to do the following:
Listen to a Firestore stream so when a new document is added, the StreamBuilder will receive it, modify it, and then present it.
The "modification" takes the Stream data, which includes a Firestore UID, gets the data from Firestore with that UID, and then the StreamBuilder is populated with that data.
So the flow is: New document added -> Stream gets document -> Function gets UID from that document -> Function uses that UID to get more data from Firestore -> Function returns to populate StreamBuilder with that new data.
My current set-up is as follows -- which works, but the FutureBuilder is obviously making the Firestore call each time the widget is rebuilt, and nobody wants that.
Stream<QuerySnapshot> upperStream;
void initState() {
super.initState();
upperStream = aStream();
}
Stream<QuerySnapshot> aStream() {
return Firestore.instance
.collection('FirstLevel')
.document(/*ownUID (not related to stream)*/)
.collection('SecondLevel')
.snapshots();
}
Future<List> processStream(List streamData) async {
List futureData = List();
for (var doc in streamData) {
Map<String, dynamic> dataToReturn = Map<String, dynamic>();
DocumentSnapshot userDoc = await Firestore.instance
.collection('FirstLevel')
.document(/*OTHER USER'S UID FROM STREAM*/)
.get();
dataToReturn['i'] = userDoc['i'];
futureData.add(dataToReturn);
}
return futureData;
}
...
...
//The actual widget
Expanded(
child: StreamBuilder(
stream: upperStream,
builder: (context, snapshot) {
// Error/null handling
return FutureBuilder(
future: processStream(snapshot.data.documents),
builder: (context, futureSnap) {
// Error/null handling
return ListView.builder(
shrinkWrap: true,
itemCount: futureSnap.data.length,
scrollDirection: Axis.vertical,
itemBuilder: (context, index) {
//Continuing with populating
});
});
}),
),
What's the best way to handle a flow like this? Creating a method where the data from the Firestore stream is modified and then returned without needing ListView.builder at all?
Edit: I tried creating my own stream like this:
Stream<Map<String, dynamic>> aStream2() async* {
QuerySnapshot snap = await Firestore.instance
.collection(FirstLevel)
.document(/*OWN UID*/)
.collection(SecondLevel)
.getDocuments();
for (var doc in snap.documents) {
Map<String, dynamic> data = Map<String, dynamic>();
DocumentSnapshot userDoc = await Firestore.instance
.collection(FirstLevel)
.document(/*OTHER USER'S UID RECEIVED FROM STREAM*/)
.get();
data['i'] = userDoc['i'];
yield data;
}
}
However, the Stream is not triggered/updated when a new Document is added to the SecondLevel collection.
Alright I think I found the path to the solution. I get the data from the stream, modify it, and then yield it to the StreamBuilder within one method and no longer need the FutureBuilder. The key to this, as Christopher Moore mentioned in the comment, is await for. The stream method looks like this:
Stream<List> aStream() async* {
List dataToReturn = List();
Stream<QuerySnapshot> stream = Firestore.instance
.collection(LevelOne)
.document(OWN UID)
.collection(LevelTwo)
.snapshots();
await for (QuerySnapshot q in stream){
for (var doc in q.documents) {
Map<String, dynamic> dataMap= Map<String, dynamic>();
DocumentSnapshot userDoc = await Firestore.instance
.collection('UserData')
.document(doc['other user data var'])
.get();
dataMap['i'] = userDoc['i'];
//...//
dataToReturn.add(dataMap);
}
yield dataToReturn;
}
}
And then the StreamBuilder is populated with the modified data as I desired.
I found myself using this to implement a chat system using the Dash Chat package in my app. I think using the map function on a stream may be a little cleaner here is a sample:
Stream<List<ChatMessage>> getMessagesForConnection(
String connectionId) {
return _db
.collection('connections')
.doc(connectionId)
.collection('messages')
.snapshots()
.map<List<ChatMessage>>((event) {
List<ChatMessage> messages = [];
for (var doc in event.docs) {
try {
messages.add(ChatMessage.fromJson(doc.data()));
} catch (e, stacktrace) {
// do something with the error
}
}
return messages;
});}

Get all from a Firestore collection in Flutter

I set up Firestore in my project. I created new collection named categories. In this collection I created three documents with uniq id. Now I want to get this collection in my Flutter application so I created CollectionReference:
Firestore.instance.collection('categories')
but I don't know what next.
I am using this plugin firebase_firestore: 0.0.1+1
This is the easiest way to get all data from collection that I found working, without using deprecated methods.
CollectionReference _collectionRef =
FirebaseFirestore.instance.collection('collection');
Future<void> getData() async {
// Get docs from collection reference
QuerySnapshot querySnapshot = await _collectionRef.get();
// Get data from docs and convert map to List
final allData = querySnapshot.docs.map((doc) => doc.data()).toList();
print(allData);
}
Here is the code if you just want to read it once
QuerySnapshot querySnapshot = await Firestore.instance.collection("collection").getDocuments();
var list = querySnapshot.documents;
Using StreamBuilder
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("expenses").snapshots,
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (!snapshot.hasData) return new Text("There is no expense");
return new ListView(children: getExpenseItems(snapshot));
});
}
getExpenseItems(AsyncSnapshot<QuerySnapshot> snapshot) {
return snapshot.data.documents
.map((doc) => new ListTile(title: new Text(doc["name"]), subtitle: new Text(doc["amount"].toString())))
.toList();
}
}
I was able to figure out a solution:
Future getDocs() async {
QuerySnapshot querySnapshot = await Firestore.instance.collection("collection").getDocuments();
for (int i = 0; i < querySnapshot.documents.length; i++) {
var a = querySnapshot.documents[i];
print(a.documentID);
}
}
Call the getDocs() function, I used build function, and it printed all the document IDs in the console.
As of 2021, there have been some major changes in the cloud_firestore package. I was working with firestore on a project, and found that none of the old tutorials were working due to the API changes.
After going through documentation and a few other answers on Stack, here's the solution for the same.
The first thing that you need to do is create a reference for your collection.
CollectionReference _cat = FirebaseFirestore.instance.collection("categories");
Next step is to query the collection. For this, we will be using the get method on the collection reference object.
QuerySnapshot querySnapshot = await _cat.get()
Finally, we need to parse the query snapshot to read the data from each document within our collection. Here, we will parse each of the documents as maps (dictionaries) and push them to a list.
final _docData = querySnapshot.docs.map((doc) => doc.data()).toList();
The entire function will look something like this:
getDocumentData () async {
CollectionReference _cat = FirebaseFirestore.instance.collection("categories");
final _docData = querySnapshot.docs.map((doc) => doc.data()).toList();
// do any further processing as you want
}
QuerySnapshot snap = await
Firestore.instance.collection('collection').getDocuments();
snap.documents.forEach((document) {
print(document.documentID);
});
Update:
One time read of all data:
var collection = FirebaseFirestore.instance.collection('users');
var querySnapshot = await collection.get();
for (var doc in querySnapshot.docs) {
Map<String, dynamic> data = doc.data();
var fooValue = data['foo']; // <-- Retrieving the value.
}
Listening for all data:
var collection = FirebaseFirestore.instance.collection('users');
collection.snapshots().listen((querySnapshot) {
for (var doc in querySnapshot.docs) {
Map<String, dynamic> data = doc.data();
var fooValue = data['foo']; // <-- Retrieving the value.
}
});
For me it works on cloud_firestore version ^2.1.0
Here is the simple code to display each colection in JSON form. I hope this would help someone
FirebaseFirestore.instance.collection("categories").get().then(
(value) {
value.docs.forEach(
(element) {
print(element.data());
},
);
},
);
the easiest way to retrieve data from the firestore is:
void getData() async {
await for (var messages in _firestore.collection('collection').snapshots())
{
for (var message in messages.docs.toList()) {
print(message.data());
}
}
}
what If you store data in the docs Id ?
if the doc is EMPTY, it would be IMPOSSIBLE to get the id doc, its a bug, unless you set a field in a specific doc
enter image description here
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
final database1 = FirebaseFirestore.instance;
Future<QuerySnapshot> years = database1
.collection('years')
.get();
class ReadDataFromFirestore extends StatelessWidget {
#override
Widget build(BuildContext context) {
return FutureBuilder<QuerySnapshot>(
future: years,
builder: (context, snapshot) {
if (snapshot.hasData) {
final List<DocumentSnapshot> documents = snapshot.data.docs;
return ListView(
children: documents
.map((doc) => Card(
child: ListTile(
title: Text('doc.id: ${doc.id}'),
//subtitle: Text('category: ${doc['category']}'),
),
))
.toList());
} else if (snapshot.hasError) {
return Text(snapshot.error);
}
return CircularProgressIndicator();
}
);
}
}

Resources