Hello I'm building chat app with firestore database but I have a problem,
when I send a new message and copy it it will copy the above message (old message) and when I refresh page I can copy the new message normally
I know the problem is the stream sorted the messages but not the new messages, so how I refresh the stream every new message sent?
I can fix that problem with setState but I am using Getx.
Can anyone help me with that? or advice I want only refersh the stream.
when I remove descending from stream everything work fine but I need to sort messages.
** sorry code look weird because i shorted it too much
Stream<QuerySnapshot<Map<String, dynamic>>>? snap()=> _fireStore
.collection('chatRooms')
.doc(chatRoom.chatRoomId)
.collection('messages')
.orderBy('createdon', descending: true)
.snapshots();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: StreamBuilder(
stream: snap(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.active) {
if (snapshot.hasData) {
QuerySnapshot dataSnapshot =
snapshot.data as QuerySnapshot;
return ListView.builder(
reverse: true,
itemCount: dataSnapshot.docs.length,
itemBuilder: (context, index) {
MessageModel currentMessage =
MessageModel.fromMap(dataSnapshot.docs[index]
.data() as Map<String, dynamic>);
return Column(
children: [
GestureDetector(
child: Text('${currentMessage.text}'),
onLongPress: () {
showModalBottomSheet(
context: context,
builder: (_) {
return Wrap(
children: [
ListTile(
title: Text(
'Copy message'),
leading: Icon(
Icons.copy),
onTap: () {
Clipboard.setData(
new ClipboardData(
text: currentMessage.text));
Get.back();
print('Copied');
},
)
],
});
} else if (snapshot.hasError) {
....
You can try this.
QuerySnapshot dataSnapshot = snapshot.data as QuerySnapshot;
final sortedMessages= dataSnapshot.docs.sort((a, b)=> a['createdon'].compareTo(b['createdon']));
And in item builder,
itemBuilder: (context, index) {
MessageModel currentMessage =
MessageModel.fromMap(list[sortedMessages]
.data() as Map<String, dynamic>);//Create item from new list
Related
I'm attempting display data from two diffrent collections within firestore , I treied to nest both streambuilds so i can particulary display the data as one stream , however I keep on getting the error bad state field doesnt exist with doc snapshot how can i fixing thus error , or is there another much more effective method i can use to display data from two diffrent collections in one class?
below is screenshot of the data(s) i want to display:
class OrderStream extends StatelessWidget {
static const String route = "/Order";
final CollectionReference meal =
FirebaseFirestore.instance.collection("menu");
final CollectionReference profile =
FirebaseFirestore.instance.collection("users");
OrderStream({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder(
stream: profile.snapshots(),
builder: (context, AsyncSnapshot<QuerySnapshot> streamSnapshot) {
return StreamBuilder(
stream: meal.snapshots(),
builder:
(context, AsyncSnapshot<QuerySnapshot> streamSnapshot) {
if (!streamSnapshot.hasData) {
return const SizedBox(
height: 250,
child: Center(
child: CircularProgressIndicator(),
),
);
} else {
return ListView.builder(
itemCount: streamSnapshot.data!.docs.length,
itemBuilder: (context, index) {
final DocumentSnapshot documentSnapshot =
streamSnapshot.data!.docs[index];
return Column(
children: [
Text( documentSnapshot['price'],)
Text( documentSnapshot['name'],)
]
),
),
}
This is probably happening due to similar name for both snapshots.
The best way to check this is by renaming the snapshot for individual Streambuilder().
StreamBuilder(
stream: profile.snapshots(),
builder: (context, AsyncSnapshot<QuerySnapshot> profileStreamSnapshot) {
return StreamBuilder(
stream: meal.snapshots(),
builder:
(context, AsyncSnapshot<QuerySnapshot> mealStreamSnapshot) {
if (!streamSnapshot.hasData) {
//modified (renamed snapshot variable) code here
}
You can merge those two streams into 1 using library like rxdart which has combineLatest2 method although you can also use something like StreamZip to get the same effect.
I have used rxdart combineLatest2 as follows:
import 'package:rxdart/rxdart.dart';//import ⇐
class MyHomePage extends StatelessWidget {
final CollectionReference profile =
FirebaseFirestore.instance.collection("users");
final CollectionReference meal =
FirebaseFirestore.instance.collection("menu");
MyHomePage({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder(
stream: Rx.combineLatest2(profile.snapshots(), meal.snapshots(),
(QuerySnapshot profileSnapshot, QuerySnapshot mealSnapshot) {
return [...profileSnapshot.docs, ...mealSnapshot.docs];
}),
builder: (context, AsyncSnapshot<List<DocumentSnapshot>> snapshot) {
if (!snapshot.hasData) {
return const SizedBox(
height: 250,
child: Center(
child: CircularProgressIndicator(),
),
);
} else {
return ListView.builder(
itemCount: snapshot.data!.length,
itemBuilder: (context, index) {
final DocumentSnapshot documentSnapshot =
snapshot.data![index];
final Map<String, dynamic> data =
documentSnapshot.data() as Map<String, dynamic>;
if (data.containsKey("price") && data.containsKey("name")) {
return Column(
children: [Text(data["price"]), Text(data["name"])],
);
} else {
return Container();
}
},
);
}
}),
);
}
}
You can also use Stream.merge() as follows:
final Stream<QuerySnapshot> mealsStream = meal.snapshots();
final Stream<QuerySnapshot> profilesStream = profile.snapshots();
//.. All that Scaffold stuff
stream: Stream.merge([mealsStream, profilesStream]),
I am trying to fetch user chats from firebase firestore but all I can see on the chat screen is a circular progress indicator, Can anyone help me with that plz?
Future<Stream<QuerySnapshot>> getChatRoomMessages(chatRoomId) async {
return FireStore.collection("ChatRooms")
.doc(chatRoomId)
.collection("chat")
.orderBy("ts", descending: true)
.snapshots();
}
Widget chatMessages() {
return StreamBuilder<QuerySnapshot>(
stream: messageStream,
builder: (context, snapshot) {
return snapshot.hasData
? ListView.builder(
itemCount: snapshot.data?.docs.length,
itemBuilder: (context, index) {
DocumentSnapshot ds =
snapshot.data?.docs[index] as DocumentSnapshot<Object?>;
return Text(ds["message"]);
})
: Center(child: CircularProgressIndicator());
},
);
}
getandSetMessages() async {
messageStream = await getChatRoomMessages(chatRoomID);
setState(() {});
}
#override
void initState() {
getandSetMessages();
super.initState();
}
This is where am implementing it in scaffold body ->
body: Stack(
children: <Widget>[
chatMessages(),
Align(
alignment: Alignment.bottomLeft,
child: Container(
I have a collection called Stuff that holds a title. Think it like a Twitter post.
{
'stuffID': string
'title': string
'details': string
}
And I have a favorites collection, that hold who favorite the which post. A user can favorite multiple stuff.
{
'userID': string
'stuffID': string
}
From second collection, I want to get all stuffID's that current user favorite. And I want to use those to get rest of the information from first collection. In summary, I want to list all stuff's that user favorite. Like a bookmark list.
I thought I must use two StreamBuilder for achieving this. But I couldn't make it work.
Here is what I manage to do:
#override
Widget build(BuildContext context) {
final FirebaseAuth auth = FirebaseAuth.instance;
final User? user = auth.currentUser;
final userID = user!.uid;
var resultStream = FirebaseFirestore.instance
.collection('favorites')
.where("userID", whereIn: [userID]).snapshots();
return StreamBuilder<QuerySnapshot>(
stream: resultStream,
builder:
(BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot1) {
if (snapshot1.hasError) {
return Text('Something is wrong.');
}
if (snapshot1.connectionState == ConnectionState.waiting) {
return Text("Loading");
}
snapshot1.data!.docs.map((DocumentSnapshot document1) {
Map<String, dynamic> data1 =
document1.data()! as Map<String, dynamic>;
print(data1['stuffID']);
Query _stuffStream = FirebaseFirestore.instance
.collection('Stuffs')
.where('stuffID', isEqualTo: data1['stuffID']);
return StreamBuilder<QuerySnapshot>(
stream: _stuffStream.snapshots(),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot2) {
if (snapshot2.hasError) {
return Text('Something is wrong.');
}
if (snapshot2.connectionState == ConnectionState.waiting) {
return Text("Loading");
}
return ListView(
//showing the data
children:
snapshot2.data!.docs.map((DocumentSnapshot document) {
Map<String, dynamic> data =
document.data()! as Map<String, dynamic>;
String stuffID = data['stuffID'];
return ListTile(
title: Text(data['title']),
subtitle: Text(data['details']),
);
}).toList(),
);
});
});
return const Center(child: CircularProgressIndicator());
});
}
When I use this code, app stucks at loading screen:
I'm trying to work it for two days but all my attempts have failed. Can you please help?
I did more and more research day after day and found the answer. I created the code based on here. Note that I changed the design of my collection in Firebase. But it completely unrelated to rest of code. I just wanted to go with more efficient way to store the data.
In summary, I'm fetching the stuffID from favorites collection. And using that stuffID to get stuffs.
#override
Widget build(BuildContext context) {
final FirebaseAuth auth = FirebaseAuth.instance;
final User? user = auth.currentUser;
final userID = user!.uid;
return StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance
.collection('favorites')
.doc(userID)
.collection(userID)
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) return const Text("Loadsing...");
return Column(children: [
ListView.builder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemCount: snapshot.data!.docs.length,
itemBuilder: (context, index) {
//return buildListItem(context, snapshot.data.documents[index]);
return ListView(
scrollDirection: Axis.vertical,
shrinkWrap: true,
children: <Widget>[
StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance
.collection('Stuffs')
.where('stuffID',
isEqualTo: snapshot.data!.docs[index]
['stuffID']) //seçilen döküman
.snapshots(),
builder: (context, snap) {
if (!snap.hasData) return const Text("Loading...");
return ListTile(
leading: CircleAvatar(
backgroundImage: NetworkImage(
snap.data!.docs[index]['stuffImage']),
),
title: Text(snap.data!.docs[index]['title']),
subtitle: Column(
children: <Widget>[
Text(snap.data!.docs[index]['details']),
],
),
);
}),
],
);
}),
]);
},
);
}
When I update the database in console or app, the changes are not synced in the app. I thought it's because of orderBy and indexing settings, but commenting them didn't help. What's wrong with my code? How to set up auto-sync?
#override
Widget build(BuildContext context) {
return Scaffold(
drawer: DrawerMenu(),
appBar: AppBar(
title: Text('Заявки'),
),
body: StreamBuilder<QuerySnapshot>(
stream: Firestore.instance
.collection('orders')
.where('companyId', isEqualTo: globals.companyId)
// .orderBy('dateAdded', descending: true)
// .orderBy('statusId')
.snapshots(),
builder: (context, snapshot) {
if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
}
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return LoadingScreen();
default:
return ListView.builder(
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index) => GestureDetector(
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Items(
order: snapshot.data.documents[index],
),
)),
child: OrderCard(
order: snapshot.data.documents[index],
)));
}
},
),
);
}
Its because you are just getting the snapshot as a stream once, but to get the actual changes when you modify in console or app you need to make a subscription to the stream. You need to .listen the stream of data so that you get notified when something change.
Also you may want to separate the logic of firebase its easier.
For example:
Create a Stream Controller:
final StreamController<DataType> _streamController = StreamController<DataType>();
Listen to firebase data and add it to stream:
Firestore.instance
.collection('orders')
.where('companyId', isEqualTo: globals.companyId)
.snapshots().listen((DocumentSnapshot snapShot){
_streamController.add(snapShot.documents);
});
Now you can listen to the stream from stream controller:
_streamController.stream;
It turned out that it's necessary to add custom indexes in firestore console when a query consists of .where().orderBy and so on.
I have a page which displays 2 elements, both of them are different StreamBuilder but the second one depends on the first one.
To make it more clear I display this:
Firebase documents (list)
Firebase user
If we sign out both StreamBuilder disappear. That's fine, but my problem comes when I need to select a document from the list:
return ListTile(
leading: FlutterLogo(size: 40.0),
title: Text(set["title"]),
selected: _selected[index],
trailing: Badge(
badgeColor: Colors.grey,
shape: BadgeShape.circle,
toAnimate: true,
onTap: () => setState(() => _selected[index] = !_selected[index]),
);
Everytime I do the SetState() I refresh the first StreamBuilder (not sure why) and with this the second one.
This is the list widget:
Widget _mySetsLists(BuildContext context) {
List<bool> _selected = List.generate(20, (i) => false);
return StreamBuilder(
stream: FirebaseAuth.instance.onAuthStateChanged,
builder: (context, snapshot) {
FirebaseUser user = snapshot.data;
if (snapshot.hasData) {
return StreamBuilder(
stream: Firestore.instance
.collection('users')
.document(user.uid)
.collection('sets')
.snapshots(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
return new ListView.builder(
shrinkWrap: true,
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index) {
DocumentSnapshot set = snapshot.data.documents[index];
return ListTile(
leading: FlutterLogo(size: 40.0),
title: Text(set["title"]),
selected: _selected[index],
onTap: () => setState(() => _selected[index] = !_selected[index]),
);
},
);
} else {
return Center(
child: new CircularProgressIndicator(),
);
}
},
);
} else {
return Text("loadin");
}
},
);
}
}
And this is the user profile:
class UserProfileState extends State<UserProfile> {
#override
Widget build(BuildContext context) {
return SliverList(
delegate: SliverChildListDelegate(
[
_mySetsLists(context),
Divider(),
StreamBuilder<FirebaseUser>(
stream: FirebaseAuth.instance.onAuthStateChanged,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.active) {
FirebaseUser user = snapshot.data;
if (user == null) {
return Text('not logged in');
}
return ListTile(
leading: CircleAvatar(
backgroundImage: NetworkImage(
user.photoUrl,
),
),
title: Text(user.displayName),
subtitle: Text(user.email),
trailing: new IconButton(
icon: new Icon(Icons.exit_to_app),
highlightColor: Colors.pink,
onPressed: () {
authService.signOut();
}),
);
} else {
return Text("loading profile"); // <---- THIS IS WHAT I SEE
}
},
),
],
),
);
}
I also went through the same difficulty, but this is the trick i used
var itemsData = List<dynamic>();
var _documents = List<DocumentSnapshot>();
#override
void initState() {
// TODO: implement initState
super.initState();
getData();
}
getData(){
Firestore.instance
.collection('users')
.document(currentUser.uid)
.collection('set')
.getDocuments()
.then((value) {
value.documents.forEach((result) {
setState(() {
_documents.add(result);
itemsData.add(result.data);
});
});
});
}
replace your listview builder will be like this
ListView.builder(
shrinkWrap: true,
itemCount: _documents.length,
itemBuilder: (context, index) {
return ListTile(
title:Text(itemsData[index]['name'])
)
})
Hope it helps!!
If you pretend to use setstat a lot using the stream you can download the data locally. So every reload will not download data again, but just show the local data.
First step: declare the variable that will store data locally.
QuerySnapshot? querySnapshotGlobal;
Then where you read the streamData, first check if the local data you just declared is empty:
//check if its empty
if(querySnapshotGlobal==null)
//as its empty, we will download it from firestore
StreamBuilder<QuerySnapshot>(
stream: _queryAlunos.snapshots(),
builder: (context, stream){
if (stream.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
}
else if (stream.hasError) {
return Center(child: Text(stream.error.toString()));
}
else if(stream.connectionState == ConnectionState.active){
//QuerySnapshot? querySnapshot = stream.data;
//instead of save data here, lets save it in the variable we declared
querySnapshotGlobal = stream.data;
return querySnapshotGlobal!.size == 0
? Center(child: Text('Sem alunos nesta turma'),)
: Expanded(
child: ListView.builder(
itemCount: querySnapshotGlobal!.size,
itemBuilder: (context, index){
Map<String, dynamic> map = querySnapshotGlobal!.docs[index].data();
//let it build
return _listDeAlunoswid(map, querySnapshotGlobal!.docs[index].id);
},
),
);
}
return CircularProgressIndicator();
},
)
else
//now, if you call setstate, as the variable with the data is not empty, will call it from here e instead of download it again from firestore, will load the local data
Expanded(
child: ListView.builder(
itemCount: querySnapshotGlobal!.size,
itemBuilder: (context, index){
Map<String, dynamic> map = querySnapshotGlobal!.docs[index].data();
return _listDeAlunoswid(map, querySnapshotGlobal!.docs[index].id);
},
),
),
Hope it helps you save some money!