How to mix stream with Provider? - flutter

I am use Provider. I want mix different data source with stream.
Use case: Chat app where some message are from system (date/error message) but other are from database (Firestore).
For example for just get message from database I now use StreamBuilder:
StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection('message').snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
return new ListView.builder(
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index) {
return messageBuilder(snapshot.data.documents[index], xa);
});
But with StreamBuilder cannot mix data from other source.
I want inject message at messages[index] for different message type.
Possible solution is create separate messages List and feed into ListView.builder:
return new ListView.builder(
itemCount: messages.length,
itemBuilder: (context, index) {
final message = messages[index];
return messageBuilder(message, xa);
});
But how I can use Provider to mix stream from Firestore and also system message into messages List?
How I can bring together data source into final List messages?
Thanks for help!

Related

How to let Flutter stream in streambuilder get limited document from firebase and accept new document created at the same time?

I am building an instant messaging app with flutter and firebase. I have a view to display user messages with a StreamBuider. The stream of the StreamBuider is accepting data from firebase. I have limited the number of messages loaded at a time for lazy loading purposes. The current problem is that when the user creates a new messages
For example, the original view has 3 messages (m1,m2,m3). When the user writes a new message (m4), the view will display (m2,m3,m4). m1 has gone. What I want is to keep all 4 messages. Is there a way to limit the number of documents gotten while listening on new documents?
StreamBuilder<QuerySnapshot>(
stream: messageService.getMessagesByChatIdStream(chatId),
builder: (BuildContext context, snapshot) {
if (snapshot.hasData) {
return ListView.builder(
padding: EdgeInsets.all(10),
itemBuilder: (context, index) {
return buildMessageTile(index, snapshot.data?.docs[index]);
},
reverse: true,
itemCount: snapshot.data?.docs.length,
);
}
the getMessagesByChatIdStream() function is like this:
Stream<QuerySnapshot> getMessagesByChatIdStream(String chatId, {int limit = 5}) {
CollectionReference colRef =
firebaseFirestore.collection("messages").doc(chatId).collection(chatId);
return colRef
.limit(limit)
.orderBy('timestamp', descending: true)
.snapshots();
}

Fetching data properly with Futurebuilder and json response

How can I render my response in my text widget?
My json snapshot.data is as following:
"{\"0\":{\"Breed\":\"American Hairless Terrier\",\"count\":1},\"1\":{\"Breed\":\"Bolognese\",\"count\":2},\"2\":{\"Breed\":\"Cavalier King Charles Spaniel\",\"count\":12},\"3\":{\"Breed\":\"Central Asian Shepherd Dog\",\"count\":1},\"4\":{\"Breed\":\"Papillon\",\"count\":1}}"
I tried to display my data like this:
Text(snapshot.data[index.toString()]['Breed']),
but I am getting:
type 'String' is not a subtype of type 'int' of 'index'
try this, might not be perfect but i will give you some idea, the error is because you are assigning int value to Text widget
Text((snapshot.data[index].
['Breed']).toString());
if you want to show it in
futureBuilder and listview
here:
FutureBuilder(
future: FirebaseFirestore.
instance.
collection("groups").
doc(groupId).snapshots(),
//here your collection name
// and doc id
builder: (context, AsyncSnapshot<DocumentSnapshot> snapshot) {
if (!snapshot.hasData) {
return Text("Loading");
}
var userDocument = snapshot.data["Breeds"];
return ListView.builder(
itemCount: userDocument.length,
shrinkWrap: true,
itemBuilder: (context, index) {
return Text(userDocument[index]);
),
}
);
The indices 0,1,2,3.. are strings(wrapped with quotation marks). But you are providing int.
Try
Text(snapshot.data['Breed'][index.toString()])

StreamBuilder not refreshing after asyncMap future resolves

I'm using the following StreamBuilder in a Stateful widget:
StreamBuilder<List<int>>(
stream: widget.model.results(widget.type),
builder: (context, snapshot) {
if (!snapshot.hasData) return CircularProgressIndicator();
if (snapshot.hasError) return Text('Error');
final List<int> results = snapshot.data;
return ListView.builder(
itemCount: results.length,
itemBuilder: (context, index) {
return _buildListTile(results[index]);
});
})
And here's the bit where the Streams get built:
// inside the ViewModel
late final List<StreamController> _streamControllers = [
StreamController<List<int>>.broadcast(),
StreamController<List<int>>.broadcast(),
];
List<int> _results = [];
Stream<List<int>> results(int index) =>
_streamControllers[index]
.stream
.debounce(Duration(milliseconds: 500))
.asyncMap((filter) async {
final List<int> assets = await search(filter); // 👈 Future
return _results..addAll(assets);
});
The issue is that the UI doesn't get rebuilt after the search results are returned.
The debugger shows that the Future is getting resolved correctly, but that the UI doesn't get rebuilt once the result is returned (within asyncMap).
Am I using asyncMap correctly? Is there an alternative way to set this up that could potentially get it working?
EDIT: Showing the code that adds events to the stream
[0, 1].forEach((index) =>
textController.addListener(() =>
_streamControllers[index]
.sink
.add(textController[index].text));
U are using asyncMap correctly.
Your issue might be that you add events to stream before Streambuilder starts to listen to widget.model.results(widget.type) stream.
Either use:
BehaviorSubject
final List<BehaviorSubject> _streamControllers = [
BehaviorSubject<List<int>>(),
BehaviorSubject<List<int>>(),
];
or add events AFTER widgets are built (or when we start to listen to them)
How to use onListen callback to start producing events?
You are creating a new Stream every build therefore it will be always empty and won't update correctly. You have the same controller, but asyncMap is creating a new Stream under the hood. The docs:
Creates a new stream with each data event of this stream asynchronously mapped to a new event.
The fix would be to save the instance of the stream after asyncMap is used. This can be done multiple ways. One would be to make a late initialized field inside your State.
late Stream<List<int>> myStream = widget.model.results(widget.type);
and then use this instance in the StreamBuilder:
StreamBuilder<List<int>>(
stream: myStream,
builder: (context, snapshot) {
if (!snapshot.hasData) return CircularProgressIndicator();
if (snapshot.hasError) return Text('Error');
final List<int> results = snapshot.data;
return ListView.builder(
itemCount: results.length,
itemBuilder: (context, index) {
return _buildListTile(results[index]);
});
})
But you can also save the instance in initState or completely outside the widget and make Stream<List<int>> results(int index) return the saved instance or make it the list like this:
List<Stream<List<int>>> results = _streamControllers
.map((s) => s.stream.asyncMap((filter) async {
final List<int> assets = await search(); // 👈 Future
return _results..addAll(assets);
}))
.toList();

how to view List<Image>?

I'm converting the files I receive from my API as Images and add it to my List<Image> imageList;
How can I then show it using Listview.builder?
I tried using
ListView.builder(
itemCount: imageList.length,
itemBuilder: (BuildContext context, int index) {
return Image.memory(imageList[index].getBytes());
});
but it returns an error
Exception: Could not instantiate image codec.
Are there other ways? Thanks for those who can help.

AsyncSnapshot rejecting Type Annotation

I have a StreamBuilder that is taking data from my bloc component.
However it keeps rejecting my type annotation AsyncSnapshot<List<int>> snapshot and only accepts dynamic as a type AsyncSnapshot<List<dynamic>> snapshot. Yet in the examples i've viewed they do have type annotaions with no complaints.
Here is my stream creation.
Widget buildList(StoriesBloc bloc) {
return StreamBuilder(
stream: bloc.topIds,
builder: (BuildContext context, AsyncSnapshot<List<int>> snapshot) {
if (!snapshot.hasData) {
return Text("Still Waiting for Ids to fetch");
}
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, int index) {
return Text('${snapshot.data[index]}');
},
);
},
);
}
Here is the VSCode error generated.
What could i be doing wrong ?
Turns out my bloc.topIds result type was not of type List<int>.
Observable<List> get topIds => _topIds.stream;
So i simply changed it to fulfill the required type.
Observable<List<int>> get topIds => _topIds.stream;
And that fixed the issue. Hope it helps someone else.