signalr_core icon indicating copy to clipboard operation
signalr_core copied to clipboard

Recommendations and best practices?

Open RCSandberg opened this issue 3 years ago • 2 comments

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

RCSandberg avatar May 04 '21 13:05 RCSandberg

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();
  }
}

janjoosse avatar May 22 '21 18:05 janjoosse

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!

RCSandberg avatar May 27 '21 08:05 RCSandberg