I'm trying to use bloc with websockets in both directions (i.e. when a message is received on the websocket, an event is fired as well as when a state is emitted, a message is sent over the web socket). I'm still fairly new to flutter but have written similar style code (message queues and websockets) in other languages but and I'm really struggling to get my head around how to structure everything so it works in flutter.
I have a basic class that opens a websocket and waits for events
class WebsocketManager {
final BuildContext context;
late IOWebSocketChannel channel;
WebsocketManager(this.context);
void connect(){
channel = IOWebSocketChannel.connect(Uri.parse(wsBaseUrl));
channel.stream.listen(
(msg) {
//process msg
BlocProvider.of<SomeBloc>(context).add(MessageReceived(msg));
}
);
}
}
This works perfectly fine (although having to pass the BuildContext
in feels a bit wrong). The issue is with listening for new states. I assumed I would be able to do something like this
BlocListener<SomeBloc, SomeState>(
listener: (context, state) {
if(!sendMessage(SomeMessage()))
});
However this listener never fires. If I place that same code as a child of a Widget then it works fine so I assume a BlockListener
has to be a child of a widget.
My question is, is there a way of using BlocListener (or some alternative) without being a child of a widget?
Alternatively, is there a better way I could structure my code so it can have access to both the websocket and bloc?
Thanks
CodePudding user response:
First, the BlockListener
must be a child of the widget being provided with the bloc.
Yeah, I wouldn't pass the BuildContext into the WebsocketManager
. I'd flip it around a bit, to make it a bit cleaner.
How you do it will of course depend on how your UI should behave based on events and states. But for the sake of keeping my example simple, I have a suggestion below where a Widget (perhaps an entire route), is listening and updating based on the websocket messages. You can of course do it so that entire app is affected by the websocket, but that is "the next step".
The bloc should be the glue between your WebsocketManager
and the UI. So I'd suggest to let your Bloc be created (provided) to a Widget where appropriate. The bloc holds an instance of WebsocketManager
(perhaps a Singleton?). Have a method and corresponding state in the bloc that sets up the connection using WebsocketManager.connect()
. But instead of doing the ....add(MessageReceived(msg))
stuff in the listen callback, have a method (perhaps it is your connect()
method) that returns a Stream<type of msg>
and let the listen callback yield msg
. You can then in your bloc set up a StreamSubscription
for the WebsocketManager's stream, and then emit states and act based on what is received from the websocket.
I'd suggest that you also convert the msg
from the listen-callback to your own domain object and yield that in the stream, so that your bloc isn't strictly dependent on the msg type from the WebsocketManager.
This way your WebsocketManager
only do 'websocket-stuff' in the data layer, and you let your bloc do the logic in the application layer, which in terms will let your UI update based on what you bloc emits.