How to access query from itemBuilder? - firebase

I am make random chat app use FirestoreAnimatedList (FirebaseAnimatedList for Firestore).
I want show user avatar only one time if same user send many message so not have same avatar show many time.
My solution is use index for check if last message send by same user.
So I need check like :
if (snapshot[index - 1][‘user’] == user) {
return true;
}
But my snapshot is DocumentSnapshot so cannot call [index - 1].
Maybe I must get list from query?
How to access so can call index on list and query like this?
Thanks!
Here example:
body: FirestoreAnimatedList(
query: firestore.collection('messages').snapshots(),
itemBuilder: (
BuildContext context,
DocumentSnapshot snapshot,
Animation<double> animation,
int index,
) {
return FadeTransition(
opacity: animation,
child: MessageItem(
index: index,
document: snapshot,
onTap: _removeMessage,
),
);
},

You can just store the users in a Map.
In your widget's class, you will have to create a variable to hold the map:
final Map<int, dynamic> users = {};
Then, you can populate the map in the itemBuilder callback:
itemBuilder: (
BuildContext context,
DocumentSnapshot snapshot,
Animation<double> animation,
int index,
) {
users[index] = snapshot['user']; // added line
return FadeTransition(
opacity: animation,
child: MessageItem(
index: index,
document: snapshot,
onTap: _removeMessage,
),
);
},
Now, you can do what you wanted:
if (users[index - 1] == user) {
return true;
}

Related

Flutter Firestore - get only 10 docs with their id

here I'm trying to get the top 10 names only from my firebase firestore,
and I searched on how I do it with the listview that I have, but I get to nowhere.
so I thought about getting the id of my documents instead.
In my firestore I gave the top 10 documents IDs from 1 to 10, now I'm stuck and I have no idea how to do it. please help.
static int id = 1;
StreamBuilder<QuerySnapshot>(
stream: fireStore.collection(path).snapshots(),
builder: (context, snapshot) {
if(!snapshot.hasData){
return Center(
child: CircularProgressIndicator(
backgroundColor: Colors.blueAccent,
),
);
}
List<InfoBox> ancients = [];
try {
final collection = snapshot.data!.docs;
for (var collect in collection) {
final name = collect['Name'];
final image = collect['Image'];
final collectionWidget = InfoBox(
ancientName: name,
ancientImage: image
);
ancients.add(collectionWidget);
}
}catch(e){
print('problems in stream builder \n error : $e');
}
return Expanded(
child:ListView(
children: ancients,
)
);
},
);
You are probably searching for limit() function. According to the documentation:
To limit the number of documents returned from a query, use the limit method on a collection reference
You can implement it like this:
fireStore.collection(path).limit(10).snapshots();
Change your ListView to this List if else everything is ok and u are getting data in list then this will work.
ListView.builder(
itemCount: ancients.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
leading: new Image.network(ancients[index].ancientImage),
title: new Text(ancients[index].ancientName),
)
},
)

How can i convert a array from firestore in a list to use in a dropdown menu in Flutter?

I'm new to flutter and i want to fetch a array from firestore and then add all items to a List Schools variable.
I have structure in firebstore as follows:
ListOfSchool -> List -> An array with name of schools
So far i have tried this:
CollectionReference SchoolList = FirebaseFirestore.instance.collection('ListSchools');
List<dynamic> Schools = [];
_fetchSchoolList() async{
DocumentSnapshot result = await SchoolList.doc('List').get().then((value) {
Schools.add(value);
});
}
But this is giving me a error:
The body might complete normally, causing 'null' to be returned, but the return type is a potentially non-nullable type.
Try adding either a return or a throw statement at the end.
I think you should make a function which fetch datas from the firebase server, like this.
void fetchFirebaseData() async {
final firestoreInstance = FirebaseFirestore.instance;
firestoreInstance.collection("ListSchools").doc('List').get().then((value) {
//do something
});
}
You mentioned that you want to use these datas in a dropdown menu/dropdown list. When you want to do something like this, you should use futurebuilder because fecthing datas takes some time. I believe this is why you get null value. By the way, this is why fetchFirebaseData function is an async function.
FutureBuilder<String>(
future: fetchFirebaseData(),
builder: (context, snapshot) {
List<String> schools = snapshot.data ?? new List<String>();
if (snapshot.hasError) print(snapshot.error);
return snapshot.hasData
? DropdownButton<String>(
items: schools.map((String value) {
return DropdownMenuItem<String>(
value: value,
child: new Text(value),
);
}).toList(),
onChanged: (_) {},
),
: Center(child: CircularProgressIndicator());
}),

how to reduce cloud firestore reads?

i have a search bar that receives a query string from the user, if the query string is empty, like i just initiated the search operation, it fetches all documents in posts collection...if user starts typing inside the search bar...it takes the typed text and compare it to a field inside each document in firestore collection and if it matches...its retrieves matched documents and it and previews them in screen, if the user deleted his typed query string...so the query string is empty again...so it fetches all documents again...i use provider package to conduct the query string to database fetch function like so:
Provider:
import 'package:flutter/foundation.dart';
class QueryStringProvider extends ChangeNotifier {
String _queryString = '';
String getQueryString() => _queryString;
updateQueryString(String userQueryString) {
_queryString = userQueryString;
notifyListeners();
}
}
the database function:
Stream<QuerySnapshot> filterAllPosts(String query) {
return query.isEmpty
? _postsCollection.snapshots()
: _postsCollection
.where('postSubtitles', arrayContains: query)
.snapshots();
}
the following is a stream builder wrapped inside a Consumer<QueryStringProvider>, the Streambuilder receives all data retrieved from database fetch function, and populate the data according to my preferred layout structure:
#override
Widget build(BuildContext context) {
return Container(
child: Consumer<QueryStringProvider>(
builder: (context, data, _) {
return StreamBuilder<QuerySnapshot>(
// stream: DatabaseService().listenToAllGigs(),
stream: DatabaseService().filterAllGigs(data.getQueryString()),
builder: (context, snapshot) {
return !snapshot.hasData
? Center(child: Text(''))
: snapshot.data.documents.length > 0
? ListView.builder(
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index) {
DocumentSnapshot data =
snapshot.data.documents[index];
Map getDocData = data.data;
return GestureDetector(
child: PostItem(
appointed: getDocData['appointed'],
appointedUserFullName:
getDocData['appointedUserFullName'],
postId: getDocData['gigId'],
postTitle: getDocData['postTitle'],
postSubtitles: getDocData['postSubtitles'],
postBody: getDocData['postBody'],
// onDeleteItem: () => model.deleteGig(index),
),
);
})
: Center(
child: Text(
'No Posts matching your criteria',
style: TextStyle(fontSize: 16),
));
},
);
},
),
);
}
i have three questions about how to reduce cloud reads with the above code:
1) is there any problems that i fetch all documents when i initiate the search operation and the query string is empty?
2)when users starts typing inside the search bar...does this mean that all the fetched data get lost..and we are making new reads again with filtering?...or it just filters what i've already fetched from initiating the search operation from the start?
3) when user deletes the query string and cancels filtering and and query string is now empty...does this mean that i will make a new full read to the cloud collection again...so everytime the users types or deletes the query string i iterate through all the collection again?*
Execuse me if the question is too long...but it is much closer to theoretical concepts rather than code...
any clarifications would be much appreciated...
many thanks.
What I suggest is querying the result only if the user has entered some specific number of characters and all of them are valid.
Like, in my case, I ensure that the user has entered at least 3 characters and all of them are alphabets or numbers.

State updated in Flutter UI not reflected in Firebase Firestore realtime

Here I have a stream of objects of which I'm returning from Firestore and being displayed in my UI. My StreamBuilder is properly returning the data from my database and is displaying my objects correctly in the UI, however I'm trying to accomplish a two way binding here, when my PlannerItemContainer is tapped and the value of the boolean isChecked changes, it updates in both the UI and in Firestore.
Now when I update my boolean's value, the change is reflected in real time in the UI as expected but when I attempt to make changes to the boolean from my UI, it's new value is not reflected in Firestore.
My diagnosis is that because PlannerItemContainer maintains its own state, my StreamBuilder isn't notified whenever a change is made.
My two questions are: 1.) Am I on the right track with my thinking and 2.) If I am is there a notifier function that I need to pass up from my PlannerItemContainer to the StreamBuilder that will notify it that there has been a change made to the PlannerItemContainer so that the realtime update is made to Firestore??
StreamBuilder(
stream: userProvider.watchHealthPlannerItems(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
plannerController.add(snapshot.data);
return Timetable<HealthPlannerItem>(
eventBuilder: (HealthPlannerItem item) {
return Row(
children: [
Expanded(
child: PlannerItemContainer(item),
),
],
);
},
);
},
);
Stream passed to StreamBuilder:
Stream<List<HealthPlannerItem>> watchHealthPlannerItems() {
return FirebaseFirestore.instance
.collection('organizations')
.doc(this.organizationId)
.collection('apps')
.doc(this.appId)
.collection('clients')
.doc(this.user.id)
.collection('healthplanner')
.snapshots()
.map((snapshot) {
return snapshot.docs.map((doc) {
Map<String, dynamic> healthPlannerData = doc.data();
HealthPlannerItem healthPlannerItem =
HealthPlannerItem.fromJson(healthPlannerData);
return healthPlannerItem;
}).toList();
});
}
PlannerItemContainer widget:
class PlannerItemContainer extends StatefulWidget {
final HealthPlannerItem item;
PlannerItemContainer(this.item);
#override
_PlannerItemContainerState createState() => _PlannerItemContainerState();
}
class _PlannerItemContainerState extends State<PlannerItemContainer> {
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
setState(() => widget.item.isChecked = !widget.item.isChecked);
},
child: widget.item.isChecked
? _buildCheckedItemContainer(widget.item)
: _buildUncheckedItemContainer(widget.item),
);
}
}
I'm not following everything completely but a quick scan shows me you're trying to change an immutable (final) variable "item" in your setState. you can move item into your state object and it can be mutable in there. Hopefully that gets you unstuck.

In Dart/Flutter, how can I find out if there is no Collection in the Firestore database?

I'd like to check if a collection (the users collection) exists in the Firestore database or not. But I cannot find any means of doing that. If the collection exists, I would like to stream its documents, otherwise an empty stream as you see in the following method
- Is there any way to find a collection exists without getting its snapshots?
- Why break; or yield* Stream.empty() hangs the stream, like an endless stream!
Stream<userInfo> getCurrentUserInfos() async* {
final String usersCollectionPath = "users";
Stream<QuerySnapshot> snapshots = Firestore.instance.collection(usersCollectionPath).snapshots();
snapshots.isEmpty.then((hasNoUserCollection) {
// Although I don't have 'users' collection
// in my database but I never reach this point!
// i.e. hasNoUserCollection is always FALSE!
if(hasNoUserCollection) {
print("users collection doesn't exist, create it!");
// next line (i.e. `break;`) hangs the tool!
// And sometimes hangs the VSCode's IDE as well, if I have a breakpoint on it!!!
break;
// I tried yielding an empty stream instead of break, again another hang!
// yield* Stream<userInfo>.empty();
} else {
// previous stream is dead, so create it again!
snapshots = Firestore.instance.collection(usersCollectionPath ).snapshots();
await for (QuerySnapshot snap in snapshots) {
for (DocumentSnapshot userDoc in snap.documents) {
yield (new userInfo.fromQuerySnapshot(userDoc));
}
}
});
}
Now even a try-catch block cannot catch what's gone wrong, when the stream is empty!
try{
getCurrentUserInfos().last.then((userInfolastOne) {
print("last one: $lastOne.name");
});
// the following line (i.e. await ...) at least doesn't hang and
// `catch` block is able to catch the error `Bad state: No element`,
// when the stream is empty
//
// userInfo lastOne = await stream.last;
} catch (ex) {
print ("ex: $ex");
}
There is no API to detect if a collection exists. In fact: a collection in Firestore only exists if there are documents in it.
The cheapest check I can think of is doing a query for a single document, and then checking if that query has any results.
Okay, maybe I figured it out
final snapshot = await Firestore.instance.collection(collectionName).getDocuments();
if (snapshot.documents.length == 0) {
//Doesn't exist
}
This worked for me
As stated by #Frank, a collection in Firestore gets deleted if no Documents exist in it.
However, I understand that there might be cases where you want to keep a history of the collection modification/ creation events, or let's say for some reason prevent Collections from being deleted.
Or for example, you want to know when a collection was created in the first place. Normally, if the Documents are deleted, and then the Collection gets created again, you will not know the initial creation date.
A workaround I can think of is the following:
Initialize each collection you want with a Document that will be specifically for keeping generic info about that collection.
For example:
This way, even if all other Documents in the Collection are deleted, you'll still keep the Collection in addition to some info that might be handy if In the future you need to get some history info about the Collection.
So to know if a Collection exists of no, you can run a query that checks for a field in Info Documents (eg CollectionInfo.exists) to know which Collections have been already created.
This is a sample from one of my projects. You can use snapshot.data!.docs.isEmpty to check if a collection has data or not.
StreamBuilder(
stream: _billGroupStream,
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return const Center(
child: Text('Something went wrong'),
);
} else if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: Column(
children: const [
LinearProgressIndicator(),
Text('Loading data, please wait...'),
],
),
);
} else if (!snapshot.hasData) {
return const Center(child: CircularProgressIndicator());
} else if (snapshot.data!.docs.isEmpty) {
return const Center(
child: Text(
'Huh!! Looks like you have no transactions yet!' ,
textAlign: TextAlign.center,
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18),
),
);
} else if (snapshot.connectionState == ConnectionState.active) {
final List<DocumentSnapshot> docs = snapshot.data!.docs;
return ListView.builder(
shrinkWrap: true,
restorationId: 'billModeList',
itemCount: snapshot.data!.docs.length,
itemBuilder: (context, index) {
///This document snapshot is used for determining the unique id for update and delete methods
final DocumentSnapshot doc = docs[index];
//final DocumentSnapshot doc = snapshot.data!.docs[index];
///This [BillModel] converted data is used to build widgets
final BillModel billModel = doc.data()! as BillModel;
return Dismissible(
onDismissed: (direction) {
_remoteStorageService.deleteItemFromGroup(
widget.uri, doc.id);
setState(() {
docs.removeAt(index);
});
},
background: Container(
color: Colors.red,
child: const Icon(Icons.delete_forever_sharp),
),
key: Key(doc.id),
child: Card(
elevation: 3,
shadowColor: Colors.teal.withOpacity(.5),
child: ListTile(
leading:
const CircleAvatar(child: Icon(Icons.attach_money)),
title: Text(billModel.name),
subtitle: Text(billModel.category ?? ''),
trailing: Text(billModel.amount.toString()),
),
),
);
},
);
}
return const CircularProgressIndicator();
},
),

Resources