Accessing all docs in subbcollecions - flutter

I'm having struggles for the last couple of days, so any help highly appreciates it.
I have an app where everyday users take photos of themself ( I set the date of that day as docId), then in UI, every day has a page ( a carousel) where users can swipe and see the photos belonging to every day.
I attached a screenshot of the Firstore database.
But having a problem reading images , tried every method.
P.s : When I set the DocId for instance: 2023-01-11 it works but it just show the photos of one day , I need to fetch all images from all days.
Method adding data to Firestore:
final photoToDb = db
.collection('photos')
.doc(DateFormat('yyyy-MM-dd').format(newDate))
.collection('Today Photos')
.withConverter(
fromFirestore: PhotoModel.fromFirestore,
toFirestore: ((PhotoModel photoModel, options) =>
photoModel.toFirestore()),
);
photoToDb.add(photo);
} catch (e) {
return ('errro');
}
}
Page where I'm trying to display images ,
lass SugarPhotoPage extends StatefulWidget {
const SugarPhotoPage({
super.key,
});
#override
State<SugarPhotoPage> createState() => _SugarPhotoPageState();
}
class _SugarPhotoPageState extends State<SugarPhotoPage> {
final Stream<QuerySnapshot> _photoStream = FirebaseFirestore.instance
.collection('photos')
.doc()
.collection('Today Photos')
.snapshots();
#override
void initState() {
print('${AppData.userSelectedData}');
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
),
body: StreamBuilder<QuerySnapshot>(
stream: _photoStream,
builder: (context, snapshot) {
if (snapshot.hasError) {
return const Text('Something went wrong');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return const Text("Loading");
}
if (snapshot.hasData) {
return SafeArea(
child: Center(
child: ListView(
children: snapshot.data!.docs
.map((DocumentSnapshot documentSnapshot) {
Map<String, dynamic> data =
documentSnapshot.data()! as Map<String, dynamic>;
return Container(
height: 200,
width: 100,
decoration: BoxDecoration(
image: DecorationImage(
image: NetworkImage('${data['ImgUrl']}'),
fit: BoxFit.contain,
),
),
);
}).toList(),
),
),
);
}
return const Text('Loading');
}),
);
}
}

You can't do that with this data structure. Firebase queries are shallow, meaning that you can't query a document together with the documents in sub collections.
In StreamBuilder you can get snapshots of either one specific document by setting :
FirebaseFirestore.instance
.collection(...)
.withConverter<...>(...)
.doc(...)
.snapshots()
or multiple documents:
FirebaseFirestore.instance
.collection(...)
.withConverter<...>(...)
.where(...)
.orderBy(...)
.limit(...)
.snapshots()
In both cases, you will get the data of one or more documents, but if you need the documents in a sub collection, you need to perform another query. For example if you have one document in doc variable, and you need data in its Today Photos sub collection, you need another stream:
doc.collection('Today Photos')
.withConverter<...>(...)
.snapshots()
So with the current data structure you can query into a StreamBuilder all documents in the user's photos collection, but the contents of Today Photos sub collection must be queried separately for each retrieved document of photos collection.
The other option is to change your data structure. You can add the daily photos to the photos collection, let Firebase assign an id to them and add the date as a field. This way you can have one stream for the photos, order them by date, add a limit etc.

Related

Why Won't This StreamBuilder Display Data without Restarting the App?

I'm trying to get a Flutter project to display a simple list of items returned from the Firebase Realtime Database. The code mostly works, but I have to restart the app each time I log out and log back in as a different user, which isn't what I'm looking for. I need the user's data to appear when they log in. I don't quite understand what all is happening here (I stumbled across a functional solution after several days of trial and error and googling), but I thought a Stream was more or less a 'live' stream of data from a particular source.
EDIT: kPAYEES_NODE is a constant stored elsewhere that resolves to 'users/uid/payees' in the RTDB:
import 'package:firebase_database/firebase_database.dart';
import 'auth_service.dart';
final DatabaseReference kUSER_NODE =
FirebaseDatabase.instance.ref('users/${AuthService.getUid()}');
final DatabaseReference kPAYEES_NODE = kUSER_NODE.child('payees');
Here's the code in question:
class DashboardPage extends StatelessWidget {
const DashboardPage({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(...),
body: StreamBuilder(
stream: kPAYEES_NODE.onValue,
builder: (context, snapshot) {
final payees = <Payee>[];
if (!snapshot.hasData) {
return Center(child: Column(children: const [Text('No Data')]));
} else {
final payeeData =
(snapshot.data!).snapshot.value as Map<Object?, dynamic>;
payeeData.forEach((key, value) {
final dataLast = Map<String, dynamic>.from(value);
final payee = Payee(
id: dataLast['id'],
name: dataLast['name'],
note: dataLast['note'],
);
payees.add(payee);
});
return ListView.builder(
shrinkWrap: true,
itemCount: payees.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text(
payees[index].name,
style: const TextStyle(color: Colors.white),
),
subtitle: Text(
payees[index].id,
style: const TextStyle(color: Colors.white),
),
);
});
}
},
),
floatingActionButton: (...),
);
}
}
That's because you only get the UID of the current once in your code, here:
FirebaseDatabase.instance.ref('users/${AuthService.getUid()}')
And by the time this line runs, the AuthService.getUid() has a single value. Instead, the code needs to be reevaluated any time a users signs in or out of the app.
In an app where the user can sign in and out, the UID values are a stream like the one exposed by FirebaseAuth.instance.authStateChanges() as shown in the Firebase documentation on getting the current user. You can wrap the stream that is returned by authStateChanges() in a StreamBuilder, and then create the database reference inside of that builder, to have it respond to changes in the authentication state.

Firestore how to fetch specific data with specific user id in Flutter

I have stream builder, and I fetch all the users. After that, using bloc (or any state management) I filter them. After filtering, I create a Set which has filtered user ids (I mean there is a set, and it has user ids).
Now, using with these uids I want to fetch filtered user datas. I did with FirebaseFirestore.instance.collection(...).doc(userId).get(), after that it gives Future<String?>. What should I do?
here is the codes:
class HomePageBody extends StatelessWidget {
HomePageBody({
Key? key,
required this.mapsState,
}) : super(key: key);
final MapsState mapsState;
final Set users = {};
#override
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: firestoreStream,
builder: (context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.waiting || snapshot.connectionState == ConnectionState.none) {
return const CustomProgressIndicator(
progressIndicatorColor: blackColor,
);
} else if (!snapshot.hasData) {
return const CustomProgressIndicator(
progressIndicatorColor: blackColor,
);
} else if (snapshot.hasData) {
final usersDatas = snapshot.data.docs;
for (var userDatas in usersDatas) {
if (userDatas["latitude"] == null || userDatas["longitude"] == null) {
} else {
users.add(userDatas);
}
}
context.read<MapsCubit>().filterUsersWithRespectToDistance(users: users);
final usersWithInTenKilometers = mapsState.usersWithInTenKilometers;
**// HERE WE HAVE FILTERED USERS, AND THIS SET HAS USER IDS.**
return ListView.builder(
padding: const EdgeInsets.only(top: 75),
itemCount: usersWithInTenKilometers.length,
itemBuilder: (context, index) {
final userId = usersWithInTenKilometers.elementAt(index);
final usersDatas = FirebaseFirestore.instance
.collection("users")
.doc(userId)
.get();
// I did like this, but it does not work.
return CustomListTile(
userImageUrl: "https://picsum.photos/200/300",
userStatus: "userStatus",
userName: "userName",
);
},
);
}
return const CustomProgressIndicator(
progressIndicatorColor: blackColor,
);
},
);
}
}
Consequently, I have a Set (or you can think like List), and it has user ids. Using these user ids, fetch user datas basically from the Firestore (email: ..., password: ... etc)
final userId = usersWithInTenKilometers.elementAt(index);
final users = FirebaseFirestore.instance
.collection("users")
.doc(userId)
.get()
.then((value) => value)
.then((value) => value.data());
return FutureBuilder(
future: users,
builder: (context, snapshot) {
if (snapshot.hasData) {
final convertUserDataToMap =
Map<String, dynamic>.from(snapshot.data as Map<dynamic, dynamic>);
final List userDataList = convertUserDataToMap.values.toList();
final userId = userDataList[0];
final userLong = userDataList[1];
....
I solved like this
Since you get back a Future<String?>, I'd typically first consider using a FutureBuilder to render that value.
If you have multiple values that each is loaded asynchronously separately (like is the case here with your multiple get() calls), I'd start with using a separate FutureBuilder for each Future. Only if I'd run into practical problems with that, would I start considering more complex options, such as Future.wait() to wait for all of them to complete before rendering any result.

A value of type 'StreamSubscription<DatabaseEvent>' can't be returned from the method 'getUsers' because it has a return type of 'Stream<List<User>>'

I am building a chat app with the tutorial I downloaded from github, but since it is made by firestore, and people suggests to user firebase RTDB, so now Im transforming all the related code, one problem I met is followings:
This is my code:
static Stream<List<User>> getUsers() {
return usersReference.onValue.listen((event){
final data = Map<String, dynamic>.from(event.snapshot.value);
final UserList = User.fromJson(data).toList();
return UserList;
});
}
I wan to use methode getUsers() for this following widget:
Widget build(BuildContext context) =>
Scaffold(
backgroundColor: Colors.blue,
body: SafeArea(
child: StreamBuilder<List<User>>(
stream: FirebaseApi.getUsers(),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Center(child: CircularProgressIndicator());
default:
if (snapshot.hasError) {
print(snapshot.error);
return buildText('Something Went Wrong Try later');
} else {
final users = snapshot.data;
if (users.isEmpty) {
return buildText('No Users Found');
} else
return Column(
children: [
ChatHeaderWidget(users: users),
ChatBodyWidget(users: users)
],
);
}
}
},
),
),
);
This is original code made for firestore, which I want to use my code to replace:
static Stream<List<User>> getUsers() => FirebaseFirestore.instance
.collection('users')
.orderBy(UserField.lastMessageTime, descending: true)
.snapshots()
.transform(Utils.transformer(User.fromJson));
So here comes error which makes me crying:
A value of type 'StreamSubscription<DatabaseEvent>' can't be returned from the method 'getUsers' because it has a return type of 'Stream<List<User>>'.
Plz, plz help me if you have any clue how to use firebase rtdb, thanks a lot, and btw why there is so many firestore tutorial for chat app which will be more expensive instead of rtdb.
Thanks a lot in advance and keep safe!
Updated after several experiment, Im not sure if following is correct solution:
Stream<List<User>> getUsers() {
getUserStream = usersReference.onValue.listen((event){
final data = Map<String, dynamic>.from(event.snapshot.value);
final userList = User.fromJson(data);
return userList;
});
}
for user.fromJson is followings code:
static User fromJson(Map<String, dynamic> json) => User(
idUser: json['idUser'],
name: json['name'],
urlAvatar: json['urlAvatar'],
lastMessageTime: Utils.toDateTime(json['lastMessageTime']),
);
So it means I transfer the data from Json to List, do I understand it correctly? Thanks for explaining, it is very kind of this community, Im just a software beginner but older than 35:)
updated after despairing experiment since above return an error:
This function has a return type of 'Stream<List<User>>', but doesn't end with a return statement.
I tried another solution which use another widget:
Widget build(BuildContext context) {
return FirebaseAnimatedList(
query: _usersReference.child("timestamp"),
sort: (a, b) => (b.key.compareTo(a.key)),
defaultChild: new CircularProgressIndicator(),
itemBuilder: (context, snapshot, animation, index) {
final data = Map<String, dynamic>.from(snapshot.value);
final List<User> users = data.entries.map((e) => e.value).toList();
return Column(
children: [
ChatHeaderWidget(users: users),
ChatBodyWidget(users: users)
],
);
});
}
so from my poor understanding query: _usersReference.child("timestamp"),will give me a map and I just need to convert to a List to ChatHeaderWidget(users: users), is it correct?
Sorry for my long question and diary, I can not test it now, since there are too many error yet.
Stream<List<User>> getUsers() {
getUserStream = usersReference.onValue.listen((event){
final data = Map<String, dynamic>.from(event.snapshot.value);
final userList = User.fromJson(data);
return userList;
});
}
There is no return value in this method. usersReference.onValue is a stream, you have to return with that. And for example you can use Stream.map() method to convert stream events to user list you can use in the StreamBuilder.
So one possible solution is the following:
Stream<List<User>> getUsers() =>
FirebaseDatabase.instance.ref().onValue.map((event) =>
event.snapshot.children
.map((e) => User.fromJson(e.value as Map<String, dynamic>))
.toList());
I imagined your data structure is something like this:
"users": {
"userId1": { /* userData */ },
"userId2": { /* userData */ },
"userId3": { /* userData */ }
}
Now you receive realtime database changes in your StreamBuilder. You have a list of users so I think your next step in your learning path to show these users on the screen. If you want to test with Column, you have to generate all children of it. For example you can use the map method on the user list too.
Column(children: userList.map((user) => ListTile(title: Text(user.name))).toList())
or another solution
Column(children: [
for (var user in users)
ListTile(title: Text(user.name))
])

Flutter Fire Store - Assigning individual document fields to variables

I'm attempting to show a user's profile image on their home page by pulling the user's 'imageUrl' from their Fire Store document. I already have the app setup to where the user can upload a new image which updates the 'imageUrl' in Fire Store, but I don't know how to have the 'imageUrl' as a variable so I can show it on the app screen.
I've been reading documentation online but It seems over simplified or out of date. I've tried using StreamBuilder, but it pulls the data from every user in the database instead of for a single user. I just need to know how to pull this one value and use it as a variable in my dart code using "getString()" with a document reference or the collection reference I already have, thank you.
class _UserPageState extends State<UserPage> {
User user = auth.currentUser!;
final CollectionReference collectionReference = FirebaseFirestore.instance.collection('users');
// Get profileImageUrl from users userDoc
String imageUrl = 'test'; // this should be the users imageUrl
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
'${user.email}'), // this is being pulled from authentication not firestore
),
body: Center(
child: Column(
children: [
// --------------------------- I tried using a stream builder here ---------------------
StreamBuilder(
stream: collectionReference.snapshots(),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return const Text(
'Something went wrong.'); // A: use incase the data does not load
}
final data = snapshot.requireData;
return ListView.builder(
shrinkWrap: true,
itemCount: data.size,
itemBuilder: (context, index) {
return Text(
// A: Stream builder will update with all of the users email addresses, I want this for one user exclusively
'My email is ${data.docs[index]['email']}');
},
collection('users')
.where("uid", isEqualTo: uid)
.snapshots(),
To filter the data in firestore collection use "where". Store the user uid in offline and query it by where using the stored uid
You can use the following function to get single data from stream.
Stream<UserModel> getSingleStreamData({String? uId}) {
return ref!.where(CommonKeys.id, isEqualTo: uId).snapshots().map((value) => value.docs.first.data());}

How to recover a specific data in the firestore?

I have this structure in the database:
I have a function that return the current user logged
FirebaseAuth auth = FirebaseAuth.instance;
auth.currentUser.uid;
And i want to retrieve "requisicoes" when "idUser" == auth.currentUser.uid;
Basically, the user retrieves the requests created by himself.
That's my StreamBuilder
final _controller = StreamController<QuerySnapshot>.broadcast();
FirebaseFirestore db = FirebaseFirestore.instance;
StreamBuilder<QuerySnapshot> addListenersRequests() {
final stream = db
.collection("requisicoes")
.where("usuario.idUser", isEqualTo: idUsuarioLogado)
.snapshots();
stream.listen((dados) {
_controller.add(dados);
});
return null;
}
Note: idUsuarioLogado is returning correctly currentUser
The problem is that I am getting everything in the "requisicoes" collection, when I create a new user, other users' requests appear on the screen
is there a logic problem in StreamBuilder?
As per you problem ,I think you are fetching the data of your current logged uid .But You want to retrieve the details of particulers users created(i,e documendId) . Then you have to fetch documentId first.Like
body: StreamBuilder(
stream:users.snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(),
);
}
if(!snapshot.hasData){
return Center(
child: CircularProgressIndicator(),
);
}
return ListView(
children: snapshot.data.documents.map((document) {
return Center(
child: Container(
width: MediaQuery.of(context).size.width/2.5,
height: MediaQuery.of(context).size.height/5,
child: Text(document.id),
),
);
}).toList());
}),
You have to fetch document.id. This is your particuler user instead of this.
FirebaseAuth auth = FirebaseAuth.instance;
auth.currentUser.uid;
Note: This will return you current logged userId.
For more details gothrough thislink.
I found the answer here Firestore is ignoring the where clause in the query in flutter
My example is the same as the link above, I have two functions, one addListenersRequests() and the other getCurrentUser(), these two functions I was calling in the initState method, the problem is that getCurrentUser is async, so addListenersRequests () was being executed first, before the value of the variable idUsuarioLogado is filled.
So I merged the two functions into one, so that getCurrentUser can be executed first
Final code:
Future<StreamBuilder<QuerySnapshot>> addListenersRequests() async{
await getDataUser();
final stream = db
.collection("requisicoes")
.where("usuario.idUser", isEqualTo: idUsuarioLogado)
.snapshots();
stream.listen((dados) {
_controller.add(dados);
});
return null;
}
Now it is working correctly.
The strange thing about this story is that the firebase executes the clause and retrieves everything in the collection even when the referenced variable is null