Unable to create a broadcast stream in flutter - flutter

Note: Please read edit first.
I want to create a broadcast stream for providing to Stream Builder. And I also have to attach websocket url to it. Using StreamController docs, this is what i've implemented. I have read similar questions but couldn't figure out the problem.
final WebSocketChannel channel = IOWebSocketChannel.connect("ws://192.168.225.220:6969/test");
Class _Sample extends state<Sample>{
#override
void initState() {
super.initState();
mystream = channel.stream;
controller = StreamController<dynamic>.broadcast();
controller.addStream(mystream);
subscription = controller.stream.listen((data) => data, onDone: () => print("Task done") , onError: (error) => error);
}
///widget tree
child: StreamBuilder(
stream: mystream,
builder: (context, snapshot) {
if(snapshot.hasData && !snapshot.hasError){
return Text(snapshot.hasData.toString());
}
return Text("No Data");
}
),
///widget tree
}
This is the error I'm getting
Bad state: Stream has already been listened to.
EDIT:
I realized after #andras pointed out I'm listening to the stream multiple times. I have read the documentation many times and viewed implementations for Stream and Streambuilder but still I am not able to comprehend Streams. For my implementation, I want to send data from a client to server. And multiple clients should be able to listen to this data.Hence, I want to create a broadcast stream so that multiple listeners can listen to a broadcasting client on a websocket channel.
So far this is what I have understood,
I create a websocket channel, this channel provides a single subscription stream where I have provided a url for server communication.
final WebSocketChannel channel = IOWebSocketChannel.connect("ws://192.168.225.220:6969/test");
Now to create a broadcast stream there is a StreamController.broadcast constructor and to attach a source there is addStream() method
void initState(){
controller = StreamController<dynamic>.broadcast();
controller.addStream(channel.stream);
subscription = controller.stream.listen((data) => data, onDone: () => print("Task done") , onError: (error) => error);
}
So I'm listening on controller.stream, now I want to attach this stream to Streambuilder so I can update builder whenever there is data from stream.
I'm echoing the data to same client using
controller.sink.add(data)
Error i get when i try to use the stream.
The following StateError was thrown while handling a gesture:
Bad state: Cannot add new events while doing an addStream
I'm not sure what i'm doing wrong.

The error description seems pretty detailed.
You listen to the stream once in the initState and then StreamBuilder does also listen to the stream you pass into it.
Think about your code a bit and see if broadcast methods fits for your use case if you want to stick to multiple listeners.

Documentation says 'Events must not be added directly to this controller using add, addError, close or addStream, until the returned future is complete.'
https://api.dart.dev/stable/2.12.4/dart-async/StreamController/addStream.html
I think you should use listen to web sockets stream and add events manually instead of using addStream()

Related

flutter) Duplicate events occur in stream

I am developing an app by combining riverpod and stream. However, I receive the same event twice. To avoid duplicate reception, the stream is listened to in initState. However, a duplicate event occurred.
I checked it by taking breakpoints in debug mode, and I saw that two identical events were raised in streamController almost at the same time.
//This is the code that listens to the stream.
#override
void initState() {
super.initState();
ToiletListViewModel toiletListViewModel =
ref.read(toiletListViewModelProvider.notifier);
TextViewModel textViewModel = ref.read(textViewModelProvider.notifier);
textViewModel.setTexts();
toiletListViewModel.uiEventStream.listen((event) {
event.when(
onLoading: _onLoading,
onError: _onError,
onSuccess: _onSuccess,
);
});
toiletListViewModel.getToiletListLocal();
toiletListViewModel.getToiletListFromRemote();
}
//This is the code that sends an event to the stream
Future getToiletListFromRemote() async {
_uiEventController.add(const ToiletListUiEvent.onLoading());//This event occurs twice at a time.
try {
List<Toilet> results =
await getToiletListFromRemoteUseCase(toiletListPage);
state = [...state, ...results];
_uiEventController.add(const ToiletListUiEvent.onSuccess());
saveToiletList(state);
} catch (e) {
e as DioError;
_uiEventController.add(ToiletListUiEvent.onError(e.message));
}
return state;
}
If I make a mistake and fire the event twice, shouldn't success or fail occur twice as well as loading? In the code above, only the loading event is fired twice with no difference of 1ms.
What could be the cause? I don't know at all. Thank you for your help.
sorry. This was a stupid mistake. toiletListViewModel.getToiletListLocal();
I was calling the same event here.

Stream is not updating when push notification is come in flutter

My stream is not updating when I call the api while I get the push notification.
This is a stream and I declare like this,
var itemController = BehaviorSubject();
Stream get itemStream => itemController.stream;
I add data in stream using sink.add like,
modelData.itemController.sink.add(modelData);
I use stream like,
StreamBuilder(stream: item.itemStream,builder: (context, snapshot) {});
When I call api at that time these stream is not update and I am not getting any updated data from stream.

Flutter Bloc to Bloc Communication: How to receive data on initial listen to broadcast stream?

Problem Summary:
I'm trying to fetch a list from StateA of BlocA when I create a new bloc.
Simplified Background:
I have an overarching bloc (BlocA), that is always active in the context of this problem, and 2 screens with a corresponding bloc each (BlocB & BlocC) that gets created when routing to its associated screen and closes when routing away from its associated screen. Every time a new bloc is created it needs to fetch its data from the state of BlocA. The user might move back and forth between screens.
What I tried:
I created stream controllers in BlocA that streams relevant data to each of the blocs via a getter. At first, I tried a normal (single listner) stream which worked fine initially. However, when routing away and then back to the screen it throws an error when resubscribing to the same stream using the getter. I then instantiated the stream controller as a broadcast stream StreamController.broadcast(). The problem is then that, when subscribing to the stream, no data is passed on subscription to the stream like with a normal stream and when I try to implement an onListen callback in the broadcast constructor (to add an event to the sink) it gives me an error The instance member '_aStream' can't be accessed in an initializer. A similar error appears for state. See below:
... _aStream = StreamController.broadcast(onListen: () {return _aStream.add(state.TypeX)})
Simplified Example Code:
class BlocA extends Bloc<BlocAEvent, BlocAState> {
BlocA() : super(BlocAState()) {
on<EventA>(_onevent);
}
final StreamController<TypeX> _aStream = StreamController.broadcast();
Stream<TypeX> get aStream => _aStream.stream;
final StreamController<TypeY> _bStream = StreamController.broadcast();
Stream<TypeY> get bStream => _bStream.stream;
...
// sink.add()'s are implemented in events
}
class BlocB extends Bloc<BlocBEvent, BlocBState> {
BlocB({required this.blocA}) : super(BlocBState()) {
on<EventB>(_onEventB);
blocASubscription = blocA.aStream.listen((stream) {
if (stream != state.fieldX) {
add(EventB(fieldX: stream));
}
});
}
final BlocA blocA
late final StreamSubscription blocASubscription;
FutureOr<void> _onEventB(EventB event, Emitter<BlocBState> emit) {
emit(state.copyWith(fieldX: event.fieldX));
}
}
class BlocC extends Bloc<BlocCEvent, BlocCState> {
// similar to BlocB
}
You do not need a stream, because bloc underhood is on streams yet. You can sent everything what you want through events and states. Check the library of Angelov https://bloclibrary.dev/#/
I ended up staying with the stream controllers, as used in the example code, but created a new event for BlocA where it is triggered when the user changes between screens and sinks the appropriate state data into the stream. The event carried an index field to indicate the screen that was routed to. The event's index corresponds with the navBar index.
The event handling implementation looked like this:
FutureOr<void> _onScreenChanged(
ScreenChanged event,
Emitter<BlocAState> emit,
) async {
switch (event.index) {
case 0:
_aStream.sink.add(state.fieldX);
break;
case 1:
_bStream.sink.add(state.fieldY);
break;
default:
}
}

Observing streams using Cubit (BLoC library)?

TL;DR: Is there a way to listen to streams and emit state using Cubit instead of BLoC?
I'm using the BLoC library for Flutter and we use Cubits for our state management. Everything is working all right so far for interactions to get data or save data but now I need to work with streams. In my case that means watching snapshots from FirebaseFirestore.
I tried searching the internet if there are ways to observe streams for Cubit rather than using BLoC but most of the results point me to BLoC. I've worked with BLoC in another project so I know how to use it for observing streams but I wanted to use Cubit instead if there is a way.
Here is an example of the code I use to observe in FireStore:
#override
Stream<Either<Failure, List<MTalk>>> watchTalk() async* {
const path ='path/to/talks';
yield* firestore
.collection(path)
.snapshots()
.map(
(snap) => right<Failure, List<MTalk>>(
snap.docs
.map(
(documentSnapshot) => MTalk.fromFirestore(documentSnapshot))
.toList(),
),
)
.onErrorReturnWith((e) {
if (e is FirestoreException) {
return left(RetrieveFailure(message: e.message));
} else {
return left(UnknownFailure(message: e.toString()));
}
});
}
}
When using BLoC, you could simply use async* and yield to return the state whenever you return data from calling watchTalk() because mapEventToState() is also a Stream that yields State. In the case of Cubit, we use emit(MyState) to get state in the UI and the functions aren't of type Stream. What I wanna know is if we can use Cubit to work with streams.
In case anyone gets confused like I did it is pretty straightforward. You can call on listen on your cubit and emit state each time you get a value from the stream. Here's an example of my code listening for changes in network connectivity using DataConnectionChecker:
///Listens to the connectivity of
Future<void> listenToConnectivity() async {
if (_internetStream != null) {
await _internetStream.cancel();
}
_internetStream = repo.isConnectedToInternet().listen((result) {
result.fold(
(failure) => _processFailure(failure: failure),
(isConnected) {
if (isConnected) {
emit(const SessionState.connected());
} else {
emit(const SessionState.disconnected());
}
},
);
});
}

flutter: how to use 'await for' to wait for other BLoC event to finish

on screen init, I am loading my data via an externalData_bloc. On the same screen I am also rendering another widget controlled with internalData_bloc for user input which depends on the number of imported records (how many rows are needed). Of course, the rendering is faster than the data load, so I get null rows needed.
I found this nice question & answer, however, I do not get it to work. The solution says
Future loginProcess() async {
await for (var result in _userBloc.state) {
if (result.status == Status.finished) {
return;
}
}
}
I am within my repository. In here, I am also storing the external data in a variable. So within the repository class I have my function to get the number of records (properties are stored in the repository, and I want to return its length).
Future<int> getNoOfProperties(int problem) async {
LogicGraphsPStmntBloc bloc = LogicGraphsPStmntBloc();
Future propertiesLoad() async {
await for (var s in bloc) {
if (s == LogicGraphsPStmntLoadSuccess) {
return;
}
}
}
propertiesLoad();
return properties.length;
}
But no matter what I try to put in for await for (var result in XXXX) or if (s.YYY == ZZZ), it doesn't work. My understanding is, XXXX needs to be a stream which is why I used bloc = LogicGraphsPStmntBloc(). But I guess this creates another instance than the one used in my widgets. LogicGraphsPStmntBloc doesn't work either, nor do states. bloc at least doesn't throw an error already in the editor, but besides the instance issue, the if statement throws an error, where in cubit.dart StreamSubscription<State> listen(... is called with cancelOnError . Anyone having some enlightenment?
Bloc uses Stream and has a continuous flow of data. You might want to listen for changes in data instead of a finished task. Using a StreamBuilder is one way of doing this.
StreamBuilder<User>(
stream: bloc.userState, // Stream from bloc
builder: (context, AsyncSnapshot<State> snapshot) {
if (snapshot.hasData) {
// check if auth status is finished
}
}
)