Flutter Push Notification using SignalR

343

Solution 1

SignalR problems

SignalR for Flutter uses web sockets and SSE to receive messages from the SignalR service. If the app was terminated because the user restarted their phone or the OS shut down the app to save battery, these push notifications would not be received by the app.

To overcome this, app developers (and SignalR) have to use FCM on Android, and APNs on iOS (or FCM which will also use APNs on iOS). All other approaches will be more limited because the operating systems do not allow users to keep background processes running the entire time. This was actually allowed years ago, but the operating systems have made these changes to save the user battery - they enforce that all apps go through the same push notification medium - FCM on Android, APNs on iOS.

SignalR for Flutter uses neither FCM nor APNs. At it's current state, SignalR is not well suited for Android or iOS - take a look at the comments with people struggling with similar problems to you on How to use signalr in Android.

Alternative solution

The simplest / easiest way to get started is to use Firebase Cloud Messaging.

  • On Android, it will be used directly to send messages to devices, and
  • on iOS, FCM will use APNs to reach devices reliably

Caveat: On Android, there is a more complicated alternative called unifiedpush, but the limitations include showing a notification to the user at all times to handle background notifications.

My analysis: This is all done based on my quick investigation by reading the pubspec.yaml, the GitHub issues on the original repo, the SignalR documentation, and some experience implementing Push Notifications for Flutter.

Disclosure: I just released a push notification library 2 days ago called push which would be well suited to these types of Push Notification packages making the transformation to using FCM on Android and APNs on iOS. However, as an app developer, in most cases, you should use firebase_messaging, not push.

Solution 2

I worked with SignalR but on native Platform(IOS & Android), I made stock app and get realtime price. When app go to background, I will disconnect with SignalR server after 5 second, and when app go to foreground again, I check if app's current state not connect to server SignalR, I'll connect again. I think it not good if your app still connect and receiver data from signalR server in background state.

Share:
343
Mehrzad Mohammadi
Author by

Mehrzad Mohammadi

Software student / Developer

Updated on January 03, 2023

Comments

  • Mehrzad Mohammadi
    Mehrzad Mohammadi over 1 year

    I'm using SignalR for push notifications on my Flutter app and that works ok. I get the message from the backend and show notification using flutter_local_notifications. The problem is that the SignalR service would shut down after some time. How can I make my app stay on in the background? and even start on reboot?

    Here's my code:

    import 'dart:async';
    import 'dart:convert';
    import 'package:flutter/material.dart';
    import 'package:isolate_test/Model/UserMessageModel.dart';
    import 'package:signalr_core/signalr_core.dart';
    import 'EndPointService.dart';
    import 'NotificationService.dart';
    
    class SignalRProvider {
      static String appName = "NOTIFICATION";
      static String? userName = "";
      static String deviceName = "android_app";
      static List<UserMessageModel> messages = <UserMessageModel>[];
    
      HubConnection connection = HubConnectionBuilder()
          .withUrl(
              'my_url',
              HttpConnectionOptions(
                logging: (level, message) => print(message),
              ))
          .withAutomaticReconnect()
          .withHubProtocol(JsonHubProtocol())
          .build();
      Function(bool update)? onMessagesUpdateCallback;
      SignalRProvider({
        this.onMessagesUpdateCallback,
      });
    
      setUsername(String username) {
        userName = username;
      }
    
      Future initSignalR(BuildContext context) async {
        WidgetsFlutterBinding.ensureInitialized();
        await NotificationService().init();
        connection.on('SignalRUserReceiveMessage', (message) async {
          var data = message!.first;
          if (data != null) {
            UserMessageModel msg = UserMessageModel.fromJson(data);
            messages.add(msg);
            msg.showNotification();
          }
          if (onMessagesUpdateCallback != null) {
            onMessagesUpdateCallback!(true);
          }
        });
    
        connection.on('SignalRMonitoringMessage', (message) async {
          var data = message!.first;
          if (data != null) {
            UserMessageModel msg = UserMessageModel.fromJson(data);
            messages.add(msg);
            msg.showNotification();
          }
          if (onMessagesUpdateCallback != null) {
            onMessagesUpdateCallback!(true);
          }
        });
    
        connection.on("SignalRReceiveConnectedMessage", (message) async {
          await connection.send(methodName: 'SignalRInit', args: [
            userName,
            appName,
            connection.connectionId,
          ]);
        });
        connection.on("SignalRReceiveDisconnectedMessage", (message) async {
          if (connection.state == HubConnectionState.disconnected) {
            connection.start();
          }
        });
        await connection.start();
      }
    
      List<UserMessageModel> getMessages() {
        return messages;
      }
    
      Future deleteMessage(UserMessageModel _msg) async {
        if (_msg == null) return;
        var response =
            await EndPointService().SetupApi("Message", "", []).httpDelete(
          HeaderEnum.BasicHeaderEnum,
          ResponseEnum.ResponseModelEnum,
          jsonEncode(_msg),
        );
      }
    
      addOrUpdateMessage(UserMessageModel _msg) {
        if (_msg == null) return;
        if (messages != null) {
          var found =
              messages.firstWhere((e) => e.user == _msg.user && e.id == _msg.id);
          var index =
              messages.indexWhere((e) => e.user == _msg.user && e.id == _msg.id);
          if (found != null) {
            messages[index] = _msg;
          } else {
            messages.add(_msg);
          }
          if (onMessagesUpdateCallback != null) {
            onMessagesUpdateCallback!(true);
          }
        }
      }
    
      setMessagesUpdateCallback(Function(bool update) func) {
        onMessagesUpdateCallback = func;
      }
    }
    
  • Mehrzad Mohammadi
    Mehrzad Mohammadi over 2 years
    Thank You for this useful information. Can you please give me an explanation on how to use push library? I couldn't run the example code.
  • Ben Butterworth
    Ben Butterworth over 2 years
    Don't use the push package I created, you should be using firebase_messaging instead. push is a special package you should use if firebase_messaging doesn't suit your needs - specifically, if you want to avoid FCM on iOS. That was the intention of this package. Firebase Messaging has more thorough guides and suit the general use case, much better.
  • Ben Butterworth
    Ben Butterworth over 2 years
    However, I am now updating the push repo to help users try the example app more easily. Thanks for your feedback :)
  • Mehrzad Mohammadi
    Mehrzad Mohammadi over 2 years
    I would use Firebase but the main problem with that is I am worried about it getting limited and asking for an account upgrade. any idea on that?
  • Ben Butterworth
    Ben Butterworth over 2 years
    There are no limitations or cost for FCM usage.
  • Mehrzad Mohammadi
    Mehrzad Mohammadi over 2 years
    Yes, but the push notification service is on the Beta version. There is a probability that it will not be free in the future, right?
  • Ben Butterworth
    Ben Butterworth over 2 years
    There is no indication it will be a charged service in the future, and it's not in beta, it's been around for a long time.