Flutter Push Notification using SignalR - flutter

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

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.

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.

Related

Calendar clientViaUserConsent it gives me Authorization Error while creating event

clientViaUserConsent opens URL in browser but it said invalid request. this URL is generated internally from lib. I had double-checked my ClientId for both platforms but still face issues for getting AuthClient for create a calendar event.
I used the below packages to create events in the google calender.
googleapis: ^8.1.0
googleapis_auth: ^1.3.0
static final androidClientId = ClientId('xxxxxxxx.apps.googleusercontent.com');
static final iOSClientId = ClientId('xxxxxxx.apps.googleusercontent.com');
final _clientID = Platform.isAndroid ? EventProvider.androidClientId : EventProvider.iOSClientId;
final _scopes = [CalendarApi.calendarScope];
clientViaUserConsent(_clientID, _scopes, prompt).then((AuthClient client) {
var calendar = CalendarApi(client);
}
void prompt(String url) async {
print(" => $url");
if (await canLaunch(url)) {
await launch(URL);
} else {
throw 'Could not launch $url';
}
}
I am referring to this article for creating an event in google calendar.
https://blog.codemagic.io/google-meet-events-in-flutter/
https://medium.com/flutter-community/flutter-use-google-calendar-api-adding-the-events-to-calendar-3d8fcb008493
You are seeing that error because the app hasn't been verified. If you are the app developer I advise you to check the App Verification FAQ to learn more about the verification steps. If you aren't the developer, you could try to enable the less secure app access but please be mindful of the consequences:
Less secure apps can make it easier for hackers to get in to your account, so blocking sign-ins from these apps helps keep your account safe.

Flutter Signalr Listener is not connected in 2nd screen after migrated to null safety stable version

Chatting was working perfectly before migrating to null safety using signalr. But after migrating It is not working in chatting part.
Scenario is like there are 2 screens where I am using signalr.
1)Chatlist.
2)Chatting with person.
listener in Chatlist is perfect but in 2nd screen it is not working(Just worked when I installed and run for the 1st time). Weird issue.
All was working in old. I am using bloc for statemanagement and also migrated to yield to emit.
Piece of code is like:
void listenOnMessageReceived(
HubConnection hubConnection,
Function(Message? chatMessageReceive) onMessageReceived,
) {
final SocketResponseCallBack chatMessageReceived =
(response) => onMessageReceived(Message.fromJson(response));
final hubMethod = HubMethod(
CHAT_RECEIVED_MESSAGE_METHOD_NAME,
SignalRHelper.toSocketFunction(
CHAT_RECEIVED_MESSAGE_METHOD_NAME, chatMessageReceived));
bool exists = listenOnHubMethod.any((method) => method.methodName == CHAT_RECEIVED_MESSAGE_METHOD_NAME);
if(exists) {
listenOnHubMethod.removeWhere((element) =>
element.methodName == CHAT_RECEIVED_MESSAGE_METHOD_NAME);
SignalRHelper(hubConnection: hubConnection).on(
hubMethod.methodName,
hubMethod.methodFunction,
);
listenOnHubMethod.add(hubMethod);
}else{
SignalRHelper(hubConnection: hubConnection).on(
hubMethod.methodName,
hubMethod.methodFunction,
);
listenOnHubMethod.add(hubMethod);
}
}
I am having 2 types of above code in different screens. but it is working in only 1 screen and not listening in 2nd screen.
here is a piece of signalr listener code:
static MethodInvocationFunc toSocketFunction(
String methodName, SocketResponseCallBack responseCallBack) {
return (arguments) {
try {
if (arguments!.isEmpty) {
throw SocketEmptyResponseException(methodName);
}
final response = arguments.first;
responseCallBack(response);
} on FormatException {
throw SocketResponseException(methodName);
}
};
}
Is there any limitations in migration of stable version or anything else. Every help is appreciable.
Thank you.
Do not use signalR, on IOS it will be impossible to run listener on background or when app is closed and you will miss messages. Use FCM.

flutter global variable becomes null when app is in background

I have a messaging app that receives push notification from Firebase Cloud Messaging and when it does, it pushes it to a stream. In order to keep track of different streams, I have a streamtable which is a global variable. This is what I have
Map<String, StreamController<dynamic>> streamtable;
Future<dynamic> myBackgroundMessageHandler(Map<String, dynamic> message) async {
log('FCM onBackground: $message');
log('streamTable: $streamtable');
addToStream(message);
await showNotification(message);
return;
}
bool addToStream(Map<String, dynamic> message) {
var data;
if (Platform.isAndroid) {
data = message['data'];
} else if (Platform.isIOS) {
data = message;
}
bool status;
try {
switch (data['streamType']) {
case "Message":
streamtable['MessageStream'].add(message);
status = true;
break;
default:
log("streamType: ${data['streamType']}");
status = false;
break;
}
} catch (e) {
status = false;
log('addToStream: error($e)');
}
return status;
}
When the app is in the foreground, everything works great. However, when the app is in the background, streamtable becomes null, and nothing works.
First off, why is that happening? If I bring the app back to foreground, streamtable is not null.
How do I store data I receive when the app is in the background?
Mobile systems work differently than desktop systems. For desktop systems, "in the background" just means the UI is not on top, but the program is running. For mobile systems, "in the background" means the app is not used and as such can be killed by the operating system any time it sees fit.
Different systems have different callbacks to notify the app of impeding shutdown so it can write it's last data to a storage and restore from that state when it's called again.
The thought that your app holds global data and retains it in memory, since it's running, just "in the background" is not correct. It won't. It can be gone any time on mobile systems.
You will need to actually save the data you want to restore later, the "memory" only lasts as long as it's in the foreground or the operating system feels generous.
Seems like it's a bug in flutter: (https://github.com/FirebaseExtended/flutterfire/issues/1878)

Accepting payments in Flutter Web

I am creating an application in Flutter Web that needs to collect payment information to create subscription charges. My plan was to use Stripe to do this, however, after making all the necessary methods for Stripe, I found that Stripe only accepts card data through Stripe Elements. Upon further research, I saw that essentially all payment platforms do this to maintain PCI compliance.
Is there any method of embedding Stripe elements(or any equivalent) into my application or is there an easier method of accepting payments with Flutter Web?
There's an unofficial Flutter Stripe package that might be what you're after: https://pub.dev/packages/stripe_payment
There's a new package called stripe_sdk, that appears to have Web support. I haven't tried it yet, but it says Web support in the description and has a web demo aswell :)
Also the old packages for mobile won't work for web, because they rely on WebView, which is not supported (and wouldn't make much sense) on web.
In case you're using Firebase as a backend, there's a stripe payments extension you can install in Firebase which makes it easy. How it works is you add a checkout_session in to a user collection and keep listening on the document. Stripe extension will update the document with a unique payments url and we just open that URL in a new tab to make the payment in the tab. We're using it in our web app, and it's working.
Something like :
buyProduct(Product pd) async {
setState(() {
loadingPayment = true;
});
String userUid = FirebaseAuth.instance.currentUser!.uid;
var docRef = await FirebaseFirestore.instance
.collection('users')
.doc(userUid)
.collection('checkout_sessions')
.add({
'price': pd.priceId,
'quantity': pd.quantity,
'mode': 'payment',
'success_url': 'https://yourwebsite/purchase-complete',
'cancel_url': 'https://yourwebsite/payment-cancelled',
});
docRef.snapshots().listen(
(ds) async {
if (ds.exists) {
//check any error
var error;
try {
error = ds.get('error');
} catch (e) {
error = null;
}
if (error != null) {
print(error);
} else {
String url = ds.data()!.containsKey('url') ? ds.get('url') : '';
if (url != '') {
//open the url in a new tab
if (!isStripeUrlOpen) {
isStripeUrlOpen = true;
setState(
() {
loadingPayment = false;
},
);
launchUrl(Uri.parse(url));
}
}
}
}
}
},
);
}

Flutter: How to check if app is running on background from firebase messaging plugins onBackgroundMessage

I'm using firebase_messaging plugin to register a callback handler with onBackgroundMessage for my data-only payload of firebase messaging.
If the app is in foreground or in background, the normal way of operation is using sockets to get the data from network and show notification from the app.
But when the app is in killed state, I would like to show the notification by fetching the data from network.
But these operations conflicts when the app is in background as onBackgroundMessage is getting called in background also.
If I'm not wrong, the handler is running on a separate isolate and it has no access to the main contents.
So how can I differentiate the killed and background state of the app from this isolated function?
You can use IsolateNameServer to register a ReceiverPort from the foreground when it is running and remove it when the foreground is not running. Then on the background isolate check if it exists and if so redirect the FCM message through the port to the foreground for handling on foreground.
Something along the lines of this:
const FOREGROUND_ISOLATE_PORT_NAME = 'foreground_port';
class NotificationManager {
ReceivePort? _foregroundReceivePort;
StreamSubscription<RemoteMessage>? _fcmMessageSubscription;
init() async {
FirebaseMessaging.onBackgroundMessage(_fcmMessageHandlerBackground);
_fcmMessageSubscription = FirebaseMessaging.onMessage.listen(_fcmMessageHandlerForeground);
_foregroundReceivePort = ReceivePort();
IsolateNameServer.registerPortWithName(
_foregroundReceivePort!.sendPort,
FOREGROUND_ISOLATE_PORT_NAME,
);
_foregroundReceivePort!.listen((message) {
if (message is RemoteMessage) {
log('got fcm message for handling in foreground');
_fcmMessageHandlerForeground(message);
}
});
}
shutdown() async {
_fcmMessageSubscription?.cancel();
IsolateNameServer.removePortNameMapping(FOREGROUND_ISOLATE_PORT_NAME);
_foregroundReceivePort!.close();
_foregroundReceivePort = null;
}
}
With these two top level functions:
Future<void> _fcmMessageHandlerForeground(RemoteMessage message) async {
// ... handle message in foreground ...
}
Future<void> _fcmMessageHandlerBackground(RemoteMessage message) async {
final foreground = IsolateNameServer.lookupPortByName(FOREGROUND_ISOLATE_PORT_NAME);
if (foreground != null) {
log("redirecting FCM message to foreground");
foreground.send(message);
} else {
// ... handle message in background ...
}
}