(Flutter) StreamBuilder returns only null - flutter

I am trying to create a "CategoryStream" to update the UI based on the users choice.
This is my stream:
import 'dart:async';
class CategoryStream {
StreamController<String> _categoryStreamController =
StreamController<String>();
closeStream() {
_categoryStreamController.close();
}
updateCategory(String category) {
print("Update category in stream was called");
print("the new category is: " + category);
_categoryStreamController.sink.add(category);
}
Stream<String> get stream => _categoryStreamController.stream;
}
And my StreamBuilder looks like this:
return StreamBuilder<String>(
stream: CategoryStream().stream,
builder: (context, snapshot) {
return Container(
color: Colors.white,
child: Center(
child: Text(snapshot.data.toString()),
),
);
},
);
So when the User choses a new category, i try to update the Stream like this:
CategoryStream().updateCategory(currentChosenCategory);
Whatever i do, the result is always null. Although the right category is displayed in the print() function...
What am i missing?
Maybe i need to work with a StreamProvider? Not a StreamBuilder? Because i am adding the data from a Parent-Widget to a Child-Widget of a Child-Widget..

By default, the value of a StreamController is null. You need to set at initialData or add data to the stream before you call snapshot.data in your StreamBuilder:
final CategoryStream _categoryStream = CategoryStream();
return StreamBuilder<String>(
stream: _categoryStream.stream,
initialData: "",
builder: (context, snapshot) {
return Container(
color: Colors.white,
child: Center(
child: Text(snapshot.data), //toString() is unnecessary, your data is already a string
),
);
},
);

Related

Firestore get length data disappear in flutter stream

I make a chat using firebase firestore.
So. I tried express not read count in the Chat list.
But, Initially the number appears, but it changes to null data.
I don't know Why data chage null data?
body: StreamBuilder(
stream: FirebaseFirestore.instance
.collection('chatRooms')
.where('emails', arrayContainsAny: [user?.email]).snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
var chatLists = snapshot.data?.docs;
if (snapshot.hasError) {
return Text('Something error...');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return Text('is Loading...');
}
return ListView.builder(
itemCount: chatLists?.length,
itemBuilder: (context, index) {
if (chatLists?[index]['currentMsg'] != null &&
chatLists?[index]['currentMsg'] != "") {
var list = List.from(chatLists?[index]['members']);
var member = '';
if (loginUser['userName'] != null) {
for (int i = 0; i < list.length; i++) {
if (list[i] != loginUser['userName']) {
member += list[i];
}
}
}
return ListTile(
title: Row(
children: [
Text(member),
const SizedBox(
width: 20.0,
),
ChatLength(docId: chatLists![index].id, uid: user!.uid),
],
),
subtitle: SizedBox(
height: 40.0,
child: Text(
chatLists[index]['currentMsg'],
overflow: TextOverflow.ellipsis,
)),
trailing: Text(
tmFormmater(chatLists?[index]['currentTm']),
style: const TextStyle(
color: Color(0xff999999),
),
),
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ChatScreen(
docId: chatLists![index].id,
title: member,
),
)),
);
} else {
return Container();
}
},
);
return Container();
},
),
class ChatLength extends StatelessWidget {
const ChatLength({super.key, required this.docId, required this.uid});
final String docId;
final String uid;
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: FirebaseFirestore.instance
.collection('message_read')
.doc(docId)
.collection('message')
.where('userId', isNotEqualTo: uid)
.where('read', isEqualTo: false)
.snapshots(),
builder: (context, snapshot) {
print(snapshot.data?.size);
if (snapshot.data?.size != null) {
return Container(
child: Text('${snapshot.data?.size}'),
);
} else {
return Container();
}
},
);
}
}
===== Debug console ====
flutter: 1
flutter: null // Data changes to null immediately
message_read collection structure
message_read -> documentId -> message -> documentId -> field(userId(chat writer), read)
I'm trying get a snapshot data from firestore.
And I put the imported data into the text.
But Data changes to null immediately.
Since you use snapshots() the code is not just reading the data once, but then continues listening for changes to the data. So the two values you see mean that the snapshots() stream was triggered twice, the first time with a single message - and the second time without any messages.
My educated guess is that your code changes the read value of the document after it gets it, since it has now displayed that message to the user. But doing so means the document no longer meets the criteria of your query, so the snapshots stream gets a new event without that message in it.
Consider using a different mechanism for the query, for example I usually use a timestamp to determine what messages to show. Step by step:
Ensure each message document has a timestamp field.
For each user store (either in the database or in local storage of the app) when they last started the app.
Then request from the database the messages since they last started the app.
Make sure to start the query before you update the timestamp value, otherwise you'll never get any results.

Flutter - How to Merge Two Stream in Flutter ? Why I'a getting Only Last Stream?

I want to Merge two Firestore Stream in a Futter. I have tried so many ways But in some cases, I got an error and others only return the last stream, not the merged Stream. This is my code
class Classes extends StatefulWidget {
#override
_ClassesState createState() => _ClassesState();
}
class _ClassesState extends State<Classes> {
String path0 = "class_schedule/Classes";
String path1 = "users/R2z6mt5/Classes";
#override
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder(
stream: Rx.merge([
Firestore.instance.collection(path1).snapshots(),
Firestore.instance.collection(path0).snapshots()
]),
builder: (context, snapshot) {
return !snapshot.hasData
? Text('PLease Wait')
: ListView.builder(
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index) {
DocumentSnapshot items = snapshot.data.documents[index];
return Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
color: Colors.yellow,
height: 50,
width: double.infinity,
child: Center(
child: Text(
items['Course'],
style: TextStyle(
fontSize: 24,
),
),
),
),
);
},
);
}),
);
}
}
Please Tell My How can I Solve this Issue?
Thanks in Advance
if I use StreamZip then I got "Class 'List' has no instance getter 'documents'. Receiver: Instance(length:2) of '_GrowableList' Tried calling: documents "
It is because when you merge with StreamZip, every stream event will be A list,
for example if 2 streams A and B which emit int, merging them with StreamZip will emit List<int> of length 2, at index 0 will be the event of Stream A and at index 1 will be the event of Stream B, So you need to access them like zippedStreamEvent[0].docs and zippedStreamEvent[1].docs
let me give you a code sample
StreamBuilder<List<QuerySnapshot>>(
stream: yourZippedStream,
builder: (_, snap) {
if(!snap.hasData){
return CircularProgressIndicator();
}
final allDocs = [...snap.data[0].docs, snap.data[1].docs];
return ListView.builder(itemBuilder: (_, i) {
final doc = allDocs[i];
return YourListWidget();
});
},
);
You can use this library to merge two streams together. See documentation for better understanding.
As per docs, StreamZip:
[E]mits lists of collected values from each input stream. The first list
contains the first value emitted by each stream, the second contains
the second value, and so on.
Hence, the type of snapshot.data in your code is List<QuerySnapshot>, which obviously doesn't have a documents getter. You would access the first QuerySnapshot like this: snapshot.data[0], then call documents on that. Similarly, the second would be snapshot.data[1], etc.

I am getting error while using stream builder in flutter

i am making a mobile app using flutter. And i am using stream builder for this screen. I am not getting the point where i am wrong in the code. Can you please help me in this. I am sharing code and screenshot for this particular row which is causing problem
var timeSelected = 'Click here';
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Text(
'Time Slot:',
style: TextStyle(color: Colors.white),
),
Spacer(),
GestureDetector(
onTap: () {
_asyncInputDialog(context);
//_displayDialog();
},
child: StreamBuilder(stream: cartManager.getTimeSlotSelected,
initialData: timeSelected,
builder: (context, AsyncSnapshot snapshot) {
if (snapshot.hasData){
timeShow(snapshot,);
}
else if (snapshot.hasError) {
return Text(snapshot.error.toString());
}
return Center(
child: Container(
child: Text('Select time slot'),
),
);
},)
),
],
),
This alert dialog will show when i click on the text of row:
_asyncInputDialog(
BuildContext context,
) {
return showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Center(child: Text('Available Time Slot')),
content: TEAlertDialogContent(),
actions: <Widget>[
new FlatButton(
child: new Text('CANCEL'),
onPressed: () {
Navigator.of(context).pop();
},
)
],
);
});
}
When i got the value from showdialog i will store the value in streamcontroller that is present in CartManager.
static StreamController<Timeslot> timeSlotController = BehaviorSubject();
timeSlotSelected(Timeslot time){
timeSlotController.sink.add(time);
}
get getTimeSlotSelected{
return timeSlotController.stream;
}
And we call the above method in stream property of streamcontroller and get the snapshot. This is the method which was called when our snapshot has data:
Widget timeShow(AsyncSnapshot<Timeslot> snapshot ) {
timeSelected = '${snapshot.data.firstTimeSlot}-${snapshot.data.secondTimeSlot}';
timeslotid = snapshot.data.id.toString();
return Text(timeSelected);
}
But i am getting error: type 'BehaviorSubject' is not a subtype of type 'Stream'
Please let me know where i am wrong. I had also shared a screen shot of screen showing this error too.
As your error states, you are trying to pass a type Timeslot to a Stream builder expecting a stream of type String. You must check which one is correct (String or Timeslot) and use the same type on both sides.
Apparently, your problem is in the timeSelected variable. Where is it defined? If this is a String, the Stream builder will infer that your stream is of type String, which is not true. You must set this variable as a Timeslot, since this is your stream type.
Also, you have an error in your code. You have to return a widget to be rendered if snapshot has data. Check the code below:
StreamBuilder(stream: cartManager.getTimeSlotSelected,
initialData: timeSelected,
builder: (context, AsyncSnapshot snapshot) {
if (snapshot.hasData){
return timeShow(snapshot,);
}
else if (snapshot.hasError) {
return Text(snapshot.error.toString());
}
return Center(
child: Container(
child: Text('Select time slot'),
),
);
},)

How to get data from the FutureProvider in flutter

I'm trying to implement local database support in my flutter app which is being managed using Provider, now I want to make the retrieving of data obey the state management pattern, but I've been failing to.
I've tried to make a traditional Provider to achieve this but the app got stuck in a loop of requests to the database, so after some search I found the FutureProvider, but I cant find how can I get a snapshot from the data being loaded
class _ReceiptsRouteState extends State<ReceiptsRoute> {
List<Receipt> receipts = [];
#override
Widget build(BuildContext context) {
return FutureProvider(
initialData: List(),
builder: (_){
return DBProvider().receipts().then((result) {
receipts = result;
});
},
child: Scaffold(
appBar: AppBar(
title: Text(AppLocalizations.of(context).history),
),
body: Container(
child: ListView.builder(
itemBuilder: (context, position) {
final item = receipts[position];
return ListTile(
title: Text(item.date),
);
},
),
),
),
);
}
}
now my app is running as I want but not as how it should run, I used FutureBuilder to get the data from the database directly but I know it should come through the provider so I want to make it right
FutureProvider exposes the result of the Future returned by builder to its descendants.
As such, using the following FutureProvider:
FutureProvider<int>(
initialData: 0,
builder: (_) => Future.value(42),
child: ...
)
it is possible to obtain the current value through:
Provider.of<int>(context)
or:
Consumer<int>(
builder: (context, value, __) {
return Text(value.toString());
}
);
In my example I used the create parameter of FutureProvider to request the API, then then I used Consumer to get the results of the API.
FutureProvider(
create: (_) => peopleService.getAllSurvivor(),
child: Consumer<List<Survivor>>(builder: (context, survivors, _) {
return survivors == null
? Center(child: CircularProgressIndicator())
: ListView.builder(
itemCount: survivors.length,
itemBuilder: (context, index) {
var survivor = survivors[index];
return ListTile(
title: Text(survivor.name),
subtitle: Text(survivor.gender),
leading: Icon(Icons.perm_identity),
);
},
);
})));

Flutter and Firestore, handle data from StreamBuilder

I'm right now trying to make a Quiz like application in flutter for learning this Framework.
I've implemented Firebase Firestore to manage the application data.
From this flutter plugin documentations I read about binding a CollectionReference to a ListView and it was pretty easy.
My problem is the following.
I've got some categories to display in the Home page, I want the user to be able to select which one he wants and then store this information in a List.
With this code I can display the list:
#override
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection('Categorie').snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) return new Text('Error: ${snapshot.error}');
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return new Center(
child: Column(
children: <Widget>[
SizedBox(
child: CircularProgressIndicator(),
height: 50.0,
width: 50.0,
),
Text('Dowload delle categorie...')
],
),
);
default:
_loadListOfCategories(snapshot);
return new ListView(
children:
snapshot.data.documents.map((DocumentSnapshot document) {
var nome = document['Nome'];
print('doc: $nome');
return new CategoryWidget(
id: document.documentID,
tap: onCategoryTap,
nome: document['Nome'],
count: document['Count'],
checked: false,
);
}).toList(),
);
}
},
);
}
CategoryWidget is just a simple stateless widget which act as a ListTile.
The result is the following:
Now, how can I save a List full of Category models, which one implementing a "checked/unchecked" property, and how can I keep this List updated?
I tried using "_loadListOfCategories()" method exacly inside the builder:
void _loadListOfCategories(AsyncSnapshot<QuerySnapshot> snapshot) {
var temporalList = new List<CategoryModel>();
for (DocumentSnapshot doc in snapshot.data.documents) {
temporalList.add(new CategoryModel(
id: doc.documentID,
count: doc['Count'],
nome: doc['Nome']));
}
setState(() {
_listOfCategories = temporalList;
});
}
But I couldn't call setState() here becouse I'm actually inside the building method.
use a map, your key will be 'document.documentID' and the value a boolean.
Map map = Map() // assuming this a string document.documentID
checked: map.get(document.documentID),
in your checkbox you call
setState()=>
map.put(document.documentID, !map.get(document.documentID));