signalr_core
signalr_core copied to clipboard
Recommendations and best practices?
Hi,
Awesome job providing this!
This is obviously not an bug/issue, and can of course be answered with the phrase "it depends..." :) But I'm wondering if you have any implementation best practices on the Flutter side of things working with this? Signal-R is new to me so I'm not sure about common patterns.
For instance, is it best practice to keep a connection to the server open for the entire app and use that as a (lazy)singleton? How and when do you close the connection? Do you e.g. usually close the connection when the app is minimized? Or do you setup a new HubConnection for each route in the app when needed and close each connection "as soon as possible"?
In my case, and I guess this is a common vanilla case:
- I will only use one hub
- Different routes in my app will listen to different events
- The events will typically just trigger a page refresh or trigger a page transition based on a user's previous action
If you have any open source code that I could get inspired by I would happily check it out.
If this is too vague, feel free to close this :)
BR, Robert
I am not a pro on SignalR either, so take it with a pinch of salt, but this is how I use the package:
- I use one HubConnection per the lifetime of the app: after login I establish the connection and when the user logs out/ the app is terminated/ goes into background, I close the connection again
- I use the package for two use cases: 1) chat functionality, and 2) notifications for events like when another user is online
- The app is mobile only, so that makes things a lot more simpler I guess (no need for multiple connections across different clients for a single user)
- On establishing the connection, I also register the different callbacks for handling the different type of messages I send from server to client, on stopping the connection I do the opposite and remove the callbacks
Here's the code for my HubConnectionService which handles (drumroll) the connection and handling the different messages. Probably I will rewrite the stuff a little bit and instead of letting the HubConnectionService
calling methods on ChatService
and EventService
, I will then expose the content of the messages to services/widgets interested in them through Streams
.
Feel free to comment/question/suggest.
class HubConnectionService {
final _storageService = locator.get<StorageService>();
final _chatService = locator.get<ChatService>();
final _eventService = locator.get<EventService>();
late HubConnection _connection;
StreamController<HubConnectionState> _connectionStarted = StreamController<HubConnectionState>.broadcast();
Stream<HubConnectionState> get connectionStarted => _connectionStarted.stream;
Future<void> init() async {
_connection = HubConnectionBuilder()
.withUrl('${ApiRoutes.apiBaseUrl}${ApiRoutes.negotiate}',
HttpConnectionOptions(accessTokenFactory: () => _storageService.getAccessToken()))
.withAutomaticReconnect()
.build();
await _startConnection();
if (_connection.state == HubConnectionState.connected) {
_connectionStarted.sink.add(HubConnectionState.connected);
}
}
Future<void> closeConnection() async {
if (_connection.state == HubConnectionState.connected) {
_connection.off(HubClient.receiveConversation);
_connection.off(HubClient.receiveMessage);
_connection.off(HubClient.receiveEvent);
_connection.stop();
}
}
Future<void> restartConnection() async {
await _startConnection();
}
Future<void> _startConnection() async {
if (_connection.state == HubConnectionState.disconnected) {
await _connection.start();
_connection.on(HubClient.receiveConversation, (args) => _chatService.onConversation(args!));
_connection.on(HubClient.receiveMessage, (args) => _chatService.onMessage(args!));
_connection.on(HubClient.receiveEvent, (args) => _eventService.onEvent(args!));
}
}
void dispose() {
_connectionStarted.close();
}
}
Sweet! Many thanks for your reply!
tl;dr; Okey, yeah I kind of went for the same pattern. In fact, extremely close with regards to implementation as well :) I established the hubconnection when it is first required then keep it for use in the entire app until the user close/minimize the app.
In my case I join different groups and register different callbacks on different routes and on different widgets on same route, and then when the routes/widgets are disposed I leave said groups and de-register the callbacks.
I think the only real difference is when I register the callbacks, and how I close my connections. I have to specify callback using the method parameter when I close a connection because I have several callbacks methods listening to the same signal r method, so I'm extremely glad that is implemented :)
@override
void membershipApproved(CallbackFunc callback) {
connection.on(HubMethod.membershipApproved, callback);
}
@override
void closeMembershipApproved(CallbackFunc callback) {
connection.off(HubMethod.membershipApproved, method: callback);
}
Yet again, great that you provided this!