Problem: Both of my streams from the code below do not update my UI automatically.
So the new data is only fetched and displayed when I do a hot reload or a hot restart. I am trying to fetch the most recent messages from each chat room and display them to the user.
Question: How can I change my code to make the streams work properly? Or is there maybe a better solution to what I am doing below?
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:blabber_tech/services/auth.dart';
import 'package:blabber_tech/services/chat_services.dart';
class MyChatsScreen2 extends StatelessWidget {
static const String id = "mychats2_screen";
// get current user id
String? userId = AuthService().getUserId();
// Stream of all rooms of current user
Stream getRoomsStream() async* {
// get rooms of current user
QuerySnapshot roomsSnapshot = await FirebaseFirestore.instance
.collection("rooms")
.where("userId1", isEqualTo: userId)
.get();
// get rooms of current user
QuerySnapshot roomsSnapshot2 = await FirebaseFirestore.instance
.collection("rooms")
.where("userId2", isEqualTo: userId)
.get();
// add rooms of current user to rooms list
List<QueryDocumentSnapshot> rooms = roomsSnapshot.docs;
// add rooms of current user to rooms list
List<QueryDocumentSnapshot> rooms2 = roomsSnapshot2.docs;
// add rooms of current user to rooms list
rooms.addAll(rooms2);
// sort rooms list by when last message was sent
// rooms.sort(
// (a, b) => b["lastMessageSentAt"].compareTo(a["lastMessageSentAt"]));
yield rooms;
}
// Stream to get last message of each room
Stream getLastMessageStream(String roomId) async* {
try {
// get last message of room
QuerySnapshot lastMessageSnapshot = await FirebaseFirestore.instance
.collection("rooms")
.doc(roomId)
.collection("messages")
.orderBy("createdAt", descending: true)
.limit(1)
.get();
// get last message of room
List lastMessage = lastMessageSnapshot.docs;
// return last message of room
yield lastMessage;
} catch (error) {
print(error);
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
// create listview of all chats of current user and show last message and other user name and photo
child: StreamBuilder(
stream: getRoomsStream(),
builder: (context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
return StreamBuilder(
stream: getLastMessageStream(snapshot.data[index].id),
builder: (context, AsyncSnapshot<dynamic> snapshot2) {
if (snapshot2.hasData) {
return ListTile(
leading: CircleAvatar(
//backgroundImage: NetworkImage(
//snapshot.data[index]["userPhotoUrl"]),
),
//title: Text(snapshot.data[index]["userName"]),
subtitle: Text(snapshot2.data[0]["message"]),
);
} else {
return Container();
}
},
);
},
);
} else {
return Container();
}
},
),
),
);
}
}
Since you're using get() when the widget is created, the data is only loaded from the database once when the widget is created. If you want to get the new data whenever it is updated, use a snapshot() listener - which returns a stream which gets an initial event with the initial data, and a new event whenever the data is updated.
To wire the Stream up in your build method, you'll want to use a StreamBuilder as shown in the Firebase documentation on listening for realtime updates in Flutter.
Related
I wrote a code like this:
StreamBuilder(
stream: _firestore.collection("Products").where("Name", isGreaterThanOrEqualTo: SearchText.toLowerCase()).snapshots(),
builder: (BuildContext context, snapshot) {
if (snapshot.data == null) {
return const Text("No data");
}
return ListView.builder(
itemCount: snapshot.data.docs.length,
itemBuilder: (context, index) {
return Card(
child: ListTile(
title: Text(snapshot.data.docs[index].data()["Name"]),
),
);
},
);
}
),
I want to shrink the data from Firestore with .toLowerCase() or otherwise. In order to make a search system, I need to shrink the incoming data. How can I do that?
Thanks for help.
I don't understand what you mean by shrink. You mentioned toLowerCase() so this is what I think the problem is
You have a stream of product names from firestore and you want to be able to make them searchable. The user search query text might be lowercase so you want to run your search on the products from firestore(lowercased)
One way to do this is to modify the stream of products that you are getting from your firestore . You can run this on dartpad.dev/
Here is a simple example with a fake list of products. I have illustrated how to use something called a streamTransformer
// A mock list of products
final List<String> productList = [
"Airpods",
"Wallet",
"Glasses",
"Gatorade",
"Medicine"
];
// A stream that exposes the product list
Stream<String> productStream() async* {
for(var product in productList){
yield product;
}
}
void main() {
// Use a stream transformer to transform or modify the stream
StreamTransformer<String, dynamic> lowerCaser = StreamTransformer.fromHandlers(handleData: (data,sink)=> sink.add(data.toString().toLowerCase()));
// Transform the stream with the .transform function
productStream().transform(lowerCaser).listen(
(product)=>print(product)
);
}
I am trying to show data from the text file as per the data stored in shared preference i have another screen to save data in the text file i have a stream builder earlier it was future builder So i am trying to refresh the screen when coming back from second screen i tried to call a method when pop the method is getting called in the viewmodel calss of provider but the streambuilder is not getting updated
this is the code
to fetch data
Future<List<String>> fetchdata() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
String? category = prefs.getString('category');
if (category != null) {
lines = await locator<JsonAPI>().fetchquotes(category);
} else {
lines = await locator<JsonAPI>().fetchquotes('quotes');
}
// data = lines as Future<List<String>>;
notifyListeners();
return lines;
}
stream builder
var quotesdata = Provider.of<HomeViewModel>(context, listen: false);
StreamBuilder(
stream: quotesdata.fetchdata().asStream(),
builder: (context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
List<String> lines = quotesdata.lines;
// List<String>? lines = snapshot.data as List<String>?;
return ScreenShotWidget(
homeViewModel: quotesdata,
list: lines,
);
} else {
return Container();
}
}),
method that i call when pop
function(data) {
category = data.toString();
fetchdata();
notifyListeners();
setState() {}
}
any idea how to update the screen
Every time your widget rebuilds, you get a new stream. This is a mistake. You should obtain the stream only once (for example, in initState)
#override
void initState() {
_stream = quotesdata.fetchdata().asStream();
}
and use that stream variable with StreamBuilder
StreamBuilder(
stream: _stream,
Later, when you want to update the stream, you can do
setState(() {
_stream = quotesdata.fetchdata().asStream();
})
to change the stream and force a refresh.
Please go over your code and change all such usages
StreamBuilder(
stream: quotesdata.fetchdata().asStream(),
to this kind of usage.
StreamBuilder(
stream: _stream,
Otherwise you may get a high backend bill someday. Right now every screen refresh does a new query to the backend.
void _myMatches() {
if (SignUp.userUid != null) {
FirebaseFirestore.instance
.collection("posts")
.where(
'owner id',
isEqualTo: SignUp.userUid,
)
.where("User Id", isNotEqualTo: [])
.where("rental status", isEqualTo: false)
.get()
.then((value) {
value.docs.forEach((result) {
print(result.data());
});
});
} else {
FirebaseFirestore.instance
.collection("posts")
.where(
'owner id',
isEqualTo: Loginpage.userUid,
)
.where("User Id", isNotEqualTo: [])
.where("rental status", isEqualTo: false)
.get()
.then((value) {
value.docs.forEach((result) {
print(result.data());
});
});
}
}
}
Hi, I am using flutter and firestore to write a program. My function that reads the data is as follows:(mentioned above)
which i call when a specific button is pressed. This leads to the data being read from firestore to be printed on the console. What do I do to display it on my emulator. How do I wrap this data in a widget so I can display it on the screen on whichever page i want?
The key is to use a FutureBuilder to render UI after you get the data, and show loading before that. Then inside builder of FutureBuilder, use ListView and ListTile(or anything you like) to render list items.
A minimum example might looks like this:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MaterialApp(
home: App(),
));
}
class App extends StatelessWidget {
Future<QuerySnapshot<Map<String, dynamic>>> getData() {
// Handle any data retrieval logic you want
return FirebaseFirestore.instance.collection('posts').get();
}
#override
Widget build(BuildContext context) {
return FutureBuilder<QuerySnapshot<Map<String, dynamic>>>(
// plug your future snapshot here
future: getData(),
builder: (context, snapshot) {
// Check loading
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
}
// Check error
final queryData = snapshot.data;
if (snapshot.hasError || queryData == null) {
return Icon(Icons.error);
}
return Scaffold(
// Use ListView.builder to render only visible items
body: ListView.builder(
itemCount: queryData.docs.length,
itemBuilder: (context, index) {
// Get data inside docs
final docData = queryData.docs[index].data();
return ListTile(
title: docData['title'],
subtitle: docData['subtitle'],
);
},
),
);
});
}
}
I'm trying to use where in a Firebase firestore query in my flutter application but it is showing all the data in the collection without filtering it ., here is my code :
Widget buildingMessages() {
print('message room id $roomID'); //The correct id is being printed here
var theMessages = FirebaseFirestore.instance.collection('messages');
theMessages.where('room_id',isEqualTo: roomID).orderBy('created', descending: true);
return StreamBuilder<QuerySnapshot>(
stream: theMessages.snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return Text('Something went wrong');
}
return new ListView(
children: snapshot.data.docs.map((DocumentSnapshot document) {
//....
The problem is in stream: theMessages.snapshots(). You are referencing the theMessages. and you are not using your where clause. extends it with your where clause. like
stream: theMessages.snapshots().where(
'room_id',isEqualTo: roomID).orderBy('created', descending: true);
Edit: Or initialize it as
var theMessages = FirebaseFirestore.instance.collection('messages').
where('room_id',isEqualTo: roomID).orderBy('created', descending: true);
I can show my firebase data to a ListView.builder(). But I can't understand how can I show my data to a ResponsiveGridList().
Here is the code of MyListView.dart file where I can load firebase data
Future productData;
Future getData() async {
var firestore = Firestore.instance;
QuerySnapshot qn = await firestore.collection("product").getDocuments();
return qn.documents;
}
#override
void initState() {
productData = getData();
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: productData,
builder: ( _ , snapshot){
if(snapshot.connectionState == ConnectionState.waiting){
return Text("Loading...");
}else{
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index){
return ListTile( title: Text(snapshot.data[index].data["title"]),);
});
}
},
);
}
I want to use responsiveGridList() in the place of ListView.builder().
Here is the ResponsiveGridList()
List<String> list = ["title 1", "title 2", "title 3"];
ResponsiveGridList(
scroll: false,
desiredItemWidth: 200,
minSpacing: 0,
children: list.map((i) {
return Container(child: Text(i),);
}).toList()
);
I have tried to load data to my RespnsiveGridlist but can't.
Is there any option to add data to a list and then I can show the list of data into a listView.
The ResponsiveGridList() requires a list of data.