Firestore collection chat has a field user_id it's type is reference and it refers to users collection document id. I want to use this reference to get user information from user collection like join in sql.
What I'm getting in my app.
{message: Hello Zia, user_id: DocumentReference(users/mfzfwKvXo5M5dFFuqKeM), event_id: DocumentReference(events/J3HTFRpL0HEbmMV2BSSb), time: Timestamp(seconds=1618466409, nanoseconds=0)}
My Code
ListView(
children: snapshot.data.docs.map((DocumentSnapshot document) {
print(document.data()['user_id']);
try {
print(document.data()['user_id'].toString());
} catch (e) {}
return new ListTile(
leading: FutureBuilder<DocumentSnapshot>(
future: user.doc(**document.data()['user_id']**).get(),
// document.data()['user_id]) it's a DocumentReference.
builder: (BuildContext context,
AsyncSnapshot<DocumentSnapshot> snapshot) {
if (snapshot.hasError) {
return Text("Something went wrong");
}
if (snapshot.connectionState == ConnectionState.done) {
Map<String, dynamic> data = snapshot.data.data();
return Text(
"Full Name: ${data['full_name']} ${data['last_name']}");
}
return Text("loading");
},
),
subtitle: new Text(document.data()['message']),
// subtitle: Text(document.data()['time']),
);
}).toList(),
);
document.data()['user_id]) type 'DocumentReference' is not a subtype of type 'String'
I got the solution I'm posting solution it may helps others
document.data()['user_id].path
path properties return the path of the document
Related
After migrating to null safety getting error The getter 'docs' isn't defined for the type 'AsyncSnapshot<Object?>'.
Try importing the library that defines 'docs', correcting the name to the name of an existing getter, or defining a getter or field named 'docs'.
Code snippet where error is
return FutureBuilder(
future: searchResultsFuture,
builder: (context, snapshot) {
if (!snapshot.hasData) {
return circularProgress();
}
List<UserResult> searchResults = [];
snapshot.docs.forEach((doc) { //have error here
User user = User.fromDocument(doc);
UserResult searchResult = UserResult(user);
searchResults.add(searchResult);
});
return ListView(
children: searchResults,
);
},
);
}
searchResultsFuture
handleSearch(String query) {
Future<QuerySnapshot> users =
usersRef.where("displayName", isGreaterThanOrEqualTo: query).get();
setState(() {
searchResultsFuture = users;
});
}
clearSearch() {
searchController.clear();
}
The snapshot in your code is an AsyncSnapshot, which indeed doesn't have a docs child. To get the docs from Firestore, you need to use:
snapshot.data.docs
Also see the FlutterFire documentation on listening for realtime data, which contains an example showing this - and my answer here explaining all snapshot types: What is the difference between existing types of snapshots in Firebase?
change like this:
return FutureBuilder(
future: searchResultsFuture,
builder: (context, **AsyncSnapshot** snapshot) {
if (!snapshot.hasData) {
return circularProgress();
}
List<UserResult> searchResults = [];
**snapshot.data!.docs.forEach((doc) {**
User user = User.fromDocument(doc);
UserResult searchResult = UserResult(user);
searchResults.add(searchResult);
});
return ListView(
children: searchResults,
);
},
);
}
usually, it takes a few ms for data to retrieve so I tried this to
make sure my operations are performed after data is retrieved
return StreamBuilder<QuerySnapshot>(
stream: Collectionreference
.snapshots(),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> activitySnapshot) {
if (activitySnapshot.hasError) {
return Center(
child: Text('Something went wrong'),
);
}
if (activitySnapshot.connectionState == ConnectionState.waiting) {
return Center(
child: SpinKitWave(
color: constants.AppMainColor,
itemCount: 5,
size: 40.0,
)));
}
if (!activitySnapshot.hasData || activitySnapshot.data.docs.isEmpty) {
return Center(
child: Text('Nothing to Display here'),
);
}
if (activitySnapshot.hasData) {
activitySnapshot.data.docs.forEach(doc=>{
print(doc);
})
}
}
});
I got a collection called "inventar" which contains a doc with an auto generated value which contains a single map I want to iterate about.
Note that the keys are going to vary, because the user is going to specify it.
How can I iterate over this map so that I can output the key and value in my table cells listed below?
new StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance
.collection("inventar")
.where("verfallsdatum")
.snapshots(),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return Text('Something went wrong');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return Text("Loading");
}
return new Table(
children: [
new TableRow(children: [
new TableCell(child: new Text("Produkt")),
new TableCell(child: new Text("Verfallsdatum")),
]),
// how to iterate here?
new TableRow(
children: [
new TableCell(
child: new Text("key"),
),
new TableCell(
child: new Text("value"),
),
]
)
]);
},
)
Edit:
I am trying to get this data out of my database since alomost one month! Which major mistake or misunderstanding do I have, that I am unable to query single document which contains a map and output it as table? Is this task so awefuly rough to perform or am I just dumb? :D
Here is the recent attampt I did, but it says there is no method "forEach" for the type "DocumentSnapshot" even though I think I say this attampt in pretty much every tutorial. But mine aint work!
var products = await db.collection("inventar").doc("vqQXArtqnFyAlkPC1PHr").get().then((snapshot) => {
snapshot.forEach((doc) => {
})
});
return StreamBuilder(
stream: FirebaseFirestore.instance
.collection("inventar")
.where("verfallsdatum")
.snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return Text('Something went wrong');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return Text("Loading");
}
return ListView(
children: snapshot.data.docs.map((documentSnapshot) {
var _data = documentSnapshot.data();
return _data.map((key, value) {
return new Table(children: [
new TableRow(children: [
new TableCell(child: new Text("Produkt")),
new TableCell(child: new Text("Verfallsdatum")),
]),
// how to iterate here?
new TableRow(children: [
new TableCell(
child: new Text(key),
),
new TableCell(
child: new Text(value),
),
])
]);
}).toList();
}).toList(),
);
},
);
I am using a Stream Provider to access Firestore data and pass it around my app. The problem I am facing starts when I first run the app. Everything starts as normal but as I navigate to the screen where I am using the Stream values in a list view, I initially get an error before the UI rebuilds and the list items appear after a split second. This is the error I get:
════════ Exception caught by widgets library ═══════════════════════════════════
The following NoSuchMethodError was thrown building OurInboxPage(dirty, dependencies: [_InheritedProviderScope<List<InboxItem>>]):
The getter 'length' was called on null.
Receiver: null
Tried calling: length
I'm guessing this has something to do with the load time to access the values and add them to the screen? How can I load all stream values when the app starts up to avoid this?
Here is my Stream code:
Stream<List<InboxItem>> get inboxitems {
return orderCollection
.where("sendTo", isEqualTo: FirebaseAuth.instance.currentUser.email)
.snapshots()
.map(
(QuerySnapshot querySnapshot) => querySnapshot.docs
.map(
(document) => InboxItem.fromFirestore(document),
)
.toList(),
);
}
I then add this to my list of Providers:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(
MultiProvider(
providers: [
StreamProvider<List<InboxItem>>.value(value: OurDatabase().inboxitems),
],
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Consumer<OurUser>(
builder: (_, user, __) {
return MaterialApp(
title: 'My App',
theme: OurTheme().buildTheme(),
home: HomepageNavigator(),
);
},
);
}
}
And finally the page I want to display the stream items:
class OurInboxPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
List<InboxItem> inboxList = Provider.of<List<InboxItem>>(context);
return Scaffold(
body: Center(
child: ListView.builder(
itemCount: inboxList.length,
itemBuilder: (context, index) {
final InboxItem document = inboxList[index];
return Card(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(document.event),
Icon(Icons.arrow_forward_ios)
],
),
);
},
),
),
);
}
}
Thanks
Yeah its trying to build before the data is populated, hence the null error.
Wrap your ListView.builder in a StreamBuilder and having it show a loading indicator if there's no data.
StreamBuilder<List<InboxItem>>(
stream: // your stream here
builder: (context, snapshot) {
if (snapshot.hasData) {
return // your ListView here
} else {
return CircularProgressIndicator();
}
},
);
I'm assuming your not using the latest version of provider because the latest version requires StreamProvider to set initialData.
If you really want to use StreamProvider and don't want a null value, just set its initialData property.
FROM:
StreamProvider<List<InboxItem>>.value(value: OurDatabase().inboxitems),
TO:
StreamProvider<List<InboxItem>>.value(
value: OurDatabase().inboxitems,
initialData: <InboxItem>[], // <<<<< THIS ONE
),
If you want to display some progress indicator while getter function inboxitems is executed initially. You don't need to modify the StreamProvider, and just add a null checking in your OurInboxPage widget.
class OurInboxPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
final List<InboxItem>? inboxList =
Provider.of<List<InboxItem>?>(context, listen: false);
return Scaffold(
body: inboxList == null
? const CircularProgressIndicator()
: ListView.builder(
itemCount: inboxList.length,
itemBuilder: (_, __) => Container(
height: 100,
color: Colors.red,
),
),
);
}
}
There are 2 ways to solve the issue.
Use the progress bar while the data is loading.
StreamBuilder<int>(
stream: getStream(),
builder: (_, snapshot) {
if (snapshot.hasError) {
return Text('${snapshot.error}');
} else if (snapshot.hasData) {
return Text('${snapshot.data}');
}
return Center(child: CircularProgressIndicator()); // <-- Use Progress bar
},
)
Provide dummy data initially.
StreamBuilder<int>(
initialData: 0, // <-- Give dummy data
stream: getStream(),
builder: (_, snapshot) {
if (snapshot.hasError) return Text('${snapshot.error}');
return Text('${snapshot.data}');
},
)
Here, getStream() return Stream<int>.
I am new to flutter and I am trying to integrate a firebase backend to store my data. I am trying to establish a stream using firebase but when I try to create a listview with the stream I get the following message:
The method 'collection' was called on null.
Receiver: null
Tried calling: collection("betslips")
Here is my code:
class Database {
final FirebaseFirestore firestore;
Database(this.firestore);
Stream<List<BetSlipModel>> streamBetSlip({String uid}) {
try {
print(firestore.collection("betslips"));
return firestore
.collection("betslips")
.snapshots()
.map((query) {
List<BetSlipModel> retVal;
for(final DocumentSnapshot doc in query.docs) {
retVal.add(BetSlipModel.fromDocumentSnapshot(documentSnapshot: doc));
}
return retVal;
});
} catch(e) {
rethrow;
}
}
}
I then try and access the values here:
body: Expanded(
child: StreamBuilder(
stream: Database(widget.firestore)
.streamBetSlip(uid: widget.auth.currentUser.uid),
builder: (BuildContext context,
AsyncSnapshot<List<BetSlipModel>> snapshot) {
if (snapshot.connectionState == ConnectionState.active) {
if (snapshot.data.isEmpty) {
return const Center(
child: Text("Empty"),
);
}
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (_, index) {
return BetSlipCard(
firestore: widget.firestore,
uid: widget.auth.currentUser.uid,
betslip: snapshot.data[index],
);
},
);
} else {
return const Center(
child: Text("loading..."),
);
}
},
),
),
Any ideas? Thanks
The method 'collection' was called on null.
Receiver: null
Tried calling: collection("betslips")
means that firestore variable is not referencing anything, check the below on how to solve it:
You are creating an instance of the class here:
stream: Database(widget.firestore)
widget is an instance variable of the class State, therefore inside the State class initialize firestore:
final FirebaseFirestore firestore = FirebaseFirestore.instance;
I ma getting a null return on a stream query. The funny thing is that data came through but on processing it to use it in the app it gets lost somewhere.I know i probably made a silly mistake somewhere yet i've been looking at this issue for three days now. Please help.
Here is the stream
Stream <SellerProfile> get sellerProfile {
return sellerProfileCollection.document(uid).snapshots()
.map(yieldSellerProfile);
}
SellerProfile yieldSellerProfile(DocumentSnapshot snapshot) {
print(snapshot.data['shopName']);
return SellerProfile(
shopName: snapshot.data['shopName'] ?? '',
phone: snapshot.data['phone']??'',
credit: snapshot.data['credit'] ?? '',
posts: snapshot.data['posts'] ?? '',
sales: snapshot.data['sales'] ?? '',
avatarUrl: snapshot.data['avatarUrl'] ?? '',
location:snapshot.data['location'] ?? '',
rating: snapshot.data['rating'] ?? '',
joinedDate: snapshot.data['joinedDate'] ?? '',
);
}
My idea is that after querying the stream sellerProfile i want to map it into a custom model to use it in the app.
As in the code, i print the snapshot.data['shopName'] before it is processed and i get the output
I/flutter ( 1008): Soko
which means the data arrives from firestore but as i try to access the data on my frontend i receive a null
Here is the frontend
Widget build(BuildContext context) {
final AuthService _auth = AuthService();
final user = Provider.of<User>(context);
return StreamBuilder<SellerProfile>(
stream: SellerDatabaseService(uid: user.uid).sellerProfile,
builder: (context, snapshot) {
SellerProfile profile=snapshot.data;
print(profile);
return Scaffold(
backgroundColor: Colors.white,
appBar: header(context,strTitle: "Profile"),
body: SingleChildScrollView(),
);
}
);
}
and here is the output when i try to print the profile
I/flutter ( 1008): null
Where am i going wrong? Thanks in advance!
I go the issue. I was trying to build the stream with stream builder instead of returning it from a provider.
So i changed this...
Widget build(BuildContext context) {
final AuthService _auth = AuthService();
final user = Provider.of<User>(context);
return StreamBuilder<SellerProfile>(
stream: SellerDatabaseService(uid: user.uid).sellerProfile,
builder: (context, snapshot) {
SellerProfile profile=snapshot.data;
print(profile);
return Scaffold(
backgroundColor: Colors.white,
appBar: header(context,strTitle: "Profile"),
body: SingleChildScrollView(),
);
}
);
}
To this...
return StreamProvider<BuyerProfile>.value(
value: BuyerDatabaseService(uid: user.uid).buyerProfile,
builder: (context, snapshot) {
BuyerProfile profile=Provider.of<BuyerProfile>(context);
if(profile!=null){
return Scaffold(...
You're not checking if the snapshot has data yet. Add a check for this with the hasData property of the AsyncSnapshot:
return StreamBuilder<SellerProfile>(
stream: SellerDatabaseService(uid: user.uid).sellerProfile,
builder: (context, snapshot) {
if(snapshot.hasError) {
return Text(snapshot.error.toString());
}
if(!snapshot.hasData) {//Check if the snapshot actually has data
return CircularProgressIndicator();
}
SellerProfile profile=snapshot.data;
print(profile);
return Scaffold(
backgroundColor: Colors.white,
appBar: header(context,strTitle: "Profile"),
body: SingleChildScrollView(),
);
}
);
Ideally you should also check if it hasError as well, and if you want more granular control over what to show, you could use the connectionState.