Flutter FCM: Storing Notification Data while the app is in the Background/Closed - android

I am trying to store data received via FCM inside a class in order to navigate to a specific tab of my app after a user clicks on that notification.
My problem is that, as far as I could find on the web, the MainActivity is stopped when the app is not in the Foreground, and so, when I try and retrieve this data, I am not getting the updated variables. I have checked this using print statements throughout the app.
In order to store the information and use it when the app is brought back up, do I need to create a local database, or is there another way around this problem?
PS: I have a Stream that receives information that the user has clicked on the notification, and it updates the main page, but I cannot retrieve anything else from it, as it itself doesn't receive the json.
Thank you. Also, this was my first question posted here, be gentle if I didn't follow the protocol by the letter.
Sample code below.
Initialization:
FirebaseOption options = FirebaseOptions(
apiKey: 'XXXXXXXXXXXXXXXXXXXXXXXXXXx',
appId: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
messagingSenderId: 'XXXXXXXXXXXXXX',
projectId: 'XXXXXXXXXXXXx',
);
Future<void> backgroundHandler(RemoteMessage message) async {
await Firebase.initializeApp(
options: options,
);
var decoded = await NotificationModelPusherAG.fromJson(message.data);
var encodedMessage = await json.decode(decoded.message);
var decodedMessage = await PusherMessage.fromJson(encodedMessage);
notifications.type = message.data;
FirebaseNotifications.showNotification(decodedMessage.title, decodedMessage.description, message.data);
}
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
final String INSTANCE_ID = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
await PusherBeams.start(INSTANCE_ID);
await Firebase.initializeApp(
options: options,
);
/// Run this funk when a notification is received and app is in BG
FirebaseMessaging.onBackgroundMessage(backgroundHandler);
runApp(MyApp());
}
The class in which the data is stored:
class Notifications {
var tab = 0;
int get returnTab => return tab;
final _notificationUpdateController = BehaviorSubject();
Stream get update => _notificationUpdateController.stream;
shouldUpdate(add) {
_notificationUpdateController.sink.add(add);
}
void set type(messageData) {
if (messageData['type'] == 'xxxxxxx') {
this.tab = 1;
}
}
var notifications = Notifications();
The widget that should update:
StreamBuilder<Object>(
stream: notifications.update,
builder: (context, snapshot) {
if (updateMulti == true) {
print(notifications.returnTab); /// Here it is '0'
return multiScreen;
} else {
return multiScreen;
}
}
);
And the function that updates it:
flutterNotificationPlugin.initialize(
initSettings,
onSelectNotification: onSelectNotification
);
static Future onSelectNotification(String payload) {
print(payload); /// The payload is always null for some reason
print(notifications.returnTab); /// Here it shows '1' as it should
updateMulti = true;
notifications.shouldUpdate(true);
}
I kind of shortened the code a bit, if I missed something important do tell me, and I shall update accordingly.
Thank you again.

Related

How to disconnect from multiple connected ble devices in Flutter app?

I am currently connected to multiple ble devices. Now, I'd like to disconnect from them but I can't.
Here's the code for connecting:
for (String deviceId in deviceIdList) {
try {
await Provider.of<BleDeviceConnector>(context, listen: false)
.connectAndLoad(
deviceId: deviceId, custom: custom, index: index);
} catch (e) {
print(e);
}
}
// In separate file...
class BleDeviceConnector extends ReactiveState<ConnectionStateUpdate> {
BleDeviceConnector({
required FlutterReactiveBle ble,
required Function(String message) logMessage,
}) : _ble = ble,
_logMessage = logMessage;
final FlutterReactiveBle _ble;
final void Function(String message) _logMessage;
#override
Stream<ConnectionStateUpdate> get state => _deviceConnectionController.stream;
final _deviceConnectionController = StreamController<ConnectionStateUpdate>();
late StreamSubscription<ConnectionStateUpdate> _connection;
Future<void> connectAndLoad(
{required String deviceId,
required CustomModel custom,
required int index}) async {
_connection = _ble.connectToDevice(id: deviceId).listen((update) async {
_deviceConnectionController.add(update);
if (update.connectionState == DeviceConnectionState.connected) {
await custom.loadCustomStuff();
}
}, onError: (e) {
print('Connecting to device $deviceId resulted in error $e');
});
}
Future<void> disconnect(String deviceId) async {
try {
print('disconnecting from device: $deviceId');
_logMessage('disconnecting from device: $deviceId');
await _connection.cancel();
} on Exception catch (e, _) {
print("Error disconnecting from a device: $e");
_logMessage("Error disconnecting from a device: $e");
} finally {
// Since [_connection] subscription is terminated, the "disconnected" state cannot be received and propagated
_deviceConnectionController.add(
ConnectionStateUpdate(
deviceId: deviceId,
connectionState: DeviceConnectionState.disconnected,
failure: null,
),
);
print("Disconnected from $deviceId");
}
}
Future<void> dispose() async {
await _deviceConnectionController.close();
}
This results in connecting to all the devices in deviceList, and loading info.
However, when I then try to disconnect from all of connected devices, it only successfully disconnects from the last one that was written to.
Here's what I'm trying:
for (String deviceId in deviceIdList) {
await Provider.of<BleDeviceConnector>(context, listen: false).disconnect(deviceId);
}
When I reached out to the package author (package: flutter_reactive_ble), he said:
"you can create a class that will hold all subscriptions and then you can loop through them and cancel them one by one in order to disconnect"
When I ran this by the discord, the thought was that I should make a list instead of a class (which seemed more along the lines of what I was thinking). Either way, I tried adding the _connection StreamSubscription to a list, but it doesn't even end up getting added to the list (for some reason).
They also mentioned to try and make another _connection variable since that appears to be the only one.
My understanding was that I would get a new instance every time I make a call to **connectAndLoad(...).
Any help would REALLY be appreciated.
Thanks!

How to share my location from flutter app to whatsapp number?

I have a function that shared data to whatsapp number, but when sending a google location it`s not working.
when printing the URL that I get at this function:
https://www.google.com/maps/search/?api=1&query=9.5191017,8.1450133
but I see that in WhatsApp look like:
https://www.google.com/maps/search/?api=1
my code :
_launchURL() async {
var phonenumber = numberphone.substring(1,10);
var url = 'https://wa.me/+972$phonenumber?text=$textToSend';
print(url);
if (await canLaunch(url)) {
await launch(url);
} else {
throw 'Could not launch $url';
}
}
my code that called the _launchURL()
onPressed: () async{
Position position = await getCurrentPosition(desiredAccuracy: LocationAccuracy.high);
textToSend='https://www.google.com/maps/search/?api=1&query=${position.latitude},${position.longitude}'.toString() ;
_launchURL();
}
any one can help me how to share location in whatsapp app using flutter ?
Thank you
I solved this issue using the code below
final maps = Uri.https("google.com", "/maps/search/", { "api=1&query": "$lat,$lng" });

Firebase Functions receive Object instead of String

Bear with me. I've spent a month just PHRASING this question: I've been using Firebase Database and Firebase functions for about a year. I've gotten it to work... but only if I sent the text of the message as a STRING. The problem is that now I wish to receive an OBJECT instead but I'm unsure of how to do this in FireBaseMessage.
My previous structure:
messages
T9Vh5cvUcbqC8IEZowBpJC3
ZWfn7876876ZGJeSNBbCpPmkm1
message
"messages": {
".read": true,
"$receiverUid": {
"$senderUid": {
"$message": {
".read": true,
".write": "auth.uid === $senderUid"
And my function for the listener was this:
exports.sendMessage = functions.database.ref('/messages/{receiverUid}/{senderUid}/{message}')
This is problematic... for a variety of reasons. Namely if the old message was "Hey" and then that same person just writes "Hey" again... then the original gets overwritten.
So my NEW structure is more like this:
messages
-LkVcYqJoEroWpkXZnqr
body: "genius grant"
createdat: 1563915599253
name: "hatemustdie"
receiverUid: "TW8289372984KJjkhdsjkhad"
senderUid: "yBNbs9823789KJkjahsdjkas"
Which is written as:
mDatabase.child("messages").push().setValue(message);
...and I'm just unsure about how to write out that function.
I mean... IDEALLY... it would be something like:
exports.sendMessage = functions.database.ref('/messages/{receiverUid}/{senderUid}/{msgID}/{msgOBJECT}')
...but I'm just not sure how Firebase functions is reading this new structure.
Now I'm pushing to the database like so:
mDatabase.child("messages").child(guid).child(user_Id).push().setValue(msgObject).addOnSuccessListener(this, new OnSuccessListener<Void>() {
#Override
public void onSuccess(#NonNull Void T) {
Log.d("MessageActivity", "Message Sent");
Basically I would just like to receive the message object... with everything in it... when it arrives from the notification... and be able to easily parse the body, date, userids, etc.
Can someone explain the correct way to go about this?
UPATE By request here's the complete cloud function:
exports.sendMessage = functions.database.ref('/messages/{receiverUid}/{senderUid}/{msgId}/{message}')
.onWrite(async (change, context) => {
const message = context.params.message;
// const messageId = context.params.messageId;
const receiverUid = context.params.receiverUid;
const senderUid = context.params.senderUid;
// If un-follow we exit the function.
if (!change.after.val()) {
return console.log('Sender ', senderUid, 'receiver ', receiverUid, 'message ', message);
}
console.log('We have a new message: ', message, 'for: ', receiverUid);
// Get the list of device notification tokens.
const getDeviceTokensPromise = admin.database()
.ref(`/users/${receiverUid}/notificationTokens`).once('value');
// Get the follower profile.
const getSenderProfilePromise = admin.auth().getUser(senderUid);
// The snapshot to the user's tokens.
let tokensSnapshot;
// The array containing all the user's tokens.
let tokens;
const results = await Promise.all([getDeviceTokensPromise, getSenderProfilePromise]);
tokensSnapshot = results[0];
const sender = results[1];
// Check if there are any device tokens.
if (!tokensSnapshot.hasChildren()) {
return console.log('There are no notification tokens to send to.');
}
console.log('There are', tokensSnapshot.numChildren(), 'tokens to send notifications to.');
console.log('Fetched sender profile', sender);
// console.log('David you're looking for the following UID:', followerUid);
// Notification details.
const payload = {
notification: {
title: `${sender.displayName} sent you a message.`,
body: message,
tag: senderUid
},
// 'data': { 'fuid': followerUid }
data: {
type: 'message',
name: sender.displayName
}
};
console.log('David you are looking for the following message:', message);
// Listing all tokens as an array.
tokens = Object.keys(tokensSnapshot.val());
// Send notifications to all tokens.
const response = await admin.messaging().sendToDevice(tokens, payload);
// For each message check if there was an error.
const tokensToRemove = [];
response.results.forEach((result, index) => {
const error = result.error;
if (error) {
console.error('Failure sending notification to', tokens[index], error);
// Cleanup the tokens who are not registered anymore.
if (error.code === 'messaging/invalid-registration-token' ||
error.code === 'messaging/registration-token-not-registered') {
tokensToRemove.push(tokensSnapshot.ref.child(tokens[index]).remove());
}
}
});
return Promise.all(tokensToRemove);
});
Since you now store the sender and receiver's UIDs inside the message, the declaration of your Cloud Function will need to change.
Instead of this:
exports.sendMessage = functions.database.ref('/messages/{receiverUid}/{senderUid}/{msgId}/{message}').onWrite(async (change, context) => {
You'll need to trigger on:
exports.sendMessage = functions.database.ref('/messages/{messageId}').onWrite(async (change, context) => {
So with this change your code will trigger on each message that is written /messages.
Now you "just" need to get the sender and receiver's UID. And since you no longer can get them from the context, you will instead get them from the change. Specifically change.after contains the data snapshot as it exists in the database after the write has completed. So (as long as you're not deleting the data), you can get the UIDs with:
const receiverUid = change.after.val().receiverUid;
const senderUid = change.after.val().senderUid;
And you'll also get the actual message from there of course:
const message = change.after.val().message;
And just in case you need the message ID (the -L... key that it was written under in the database):
const messageId = change.after.val().messageId;
You need a trigger on just the messageId:
exports.sendMessage = functions.database.ref('/messages/{messageId}').onWrite((change, context) => {
const changedData = change.after.val(); // This will have the complete changed data
const message = change.after.val().message; // This will contain the message value
......
});
Elaborating on Frank's answer:
You can't get the data from context like const message = context.params.message;because those parameters don't exists anymore on the context.

parsing error unexpected token mailtransport

In my android app when user enters wrong PASSWORD more times then i want send email to user using firebase functions with download link of picture captured so i created function below.
So i push email and download link to firebase and when data gets added following function gets triggered but whenever im trying to deploy this function cli giving me error that mailtransport is unexpected..
exports.sendMails = functions.database.ref('/failedAttemps/{email2}/{attemptsid}')
.onWrite((data, context) =>
{
const email2 = context.params.email2;
const attemptsid = context.params.attemptsid;
//const sender_id = context.params.sender_id;
//const mees = context.params.message_not;
// contentss = mees;
const gmailEmail = functions.config().gmail.email;
const gmailPassword = functions.config().gmail.password;
const mailTransport = nodemailer.createTransport({
service: 'gmail',
auth: {
user: "******#gmail.com",
pass: "****#****",
},
});
const email = admin.database().ref(`/Notification/${email2}/${attemptsid}/email`);
email.once("value", function(snapshot){
emails = snapshot.val();
});
const naam = admin.database().ref(`/Notification/${email2}/${attemptsid}/dlink`);
naam.once("value", function(snapshot){
dlinks = snapshot.val();
});
// console.log('message :' , contentss);
const mailOptions = {
from: '"LetsTalk" <noreply#firebase.com>',
to: emails,
};
// Building Email message.
mailOptions.subject = 'Someone tried to login to you account';
mailOptions.text = `${dlink}Thanks you for subscribing to our newsletter. You will receive our next weekly newsletter.`;
try {
await mailTransport.sendMail(mailOptions);
// console.log(`New ${subscribed ? '' : 'un'}subscription confirmation email sent to:`, val.email);
} catch(error) {
console.error('There was an error while sending the email:', error);
}
return null;
});
Every time i try to deploy function on firebase.This error pops upenter image description here
The problem seems to be you are using await without first indicating it is an async function.
Try replacing your first lines with:
exports.sendMails = functions.database.ref('/failedAttemps/{email2}/{attemptsid}')
.onWrite(async (data, context) =>
{

Firebase Cloud Function sends multiple of the same notification to the same devices

I have a firebase cloud function that is supposed to send a notification to the 5 closest phones when a new "emergency" is added to the database. The code I wrote does send a notification to the 5 closest phones, but It sends that same notification over and over again. It gets even worse when my users log on or off because then it sends even more. I'm confused why my cloud function doesn't just operate once and then terminate.
Here is the code for my cloud function:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.sendPushNotificationAdded = functions.database.ref('/emergencies/{id}').onCreate(event => {
return admin.database().ref('/tokens').on('value', function(snapshot) {
var efarArray = snapshotToArray(snapshot, event.data.child('latitude').val(), event.data.child('longitude').val());
efarArray.sort(function(a, b) {
return a.distance - b.distance;
});
var payload = {
notification: {
title: "NEW EMERGANCY!",
body: "Message from Patient: " + event.data.child('other_info').val(),
//badge: '1',
sound: 'default',
}
};
var options = {
priority: "high",
timeToLive: 0
};
tokens_to_send_to = [];
if(efarArray.length >= 5){
//only send to the 5 closest efars
for (var i = 4; i >= 0; i--) {
tokens_to_send_to.push(efarArray[i].token);
}
}else{
for (var i = efarArray.length - 1; i >= 0; i--) {
tokens_to_send_to.push(efarArray[i].token);
}
}
//TODO: send a messaged back to patient if no efars respond or are found?
return admin.messaging().sendToDevice(tokens_to_send_to, payload, options).then(response => {
});
});
});
//code for function below from https://ilikekillnerds.com/2017/05/convert-firebase-database-snapshotcollection-array-javascript/
function snapshotToArray(snapshot, incoming_latitude, incoming_longitude) {
var returnArr = [];
snapshot.forEach(function(childSnapshot) {
var distance_to_efar = distance(childSnapshot.child('latitude').val(), childSnapshot.child('longitude').val(), incoming_latitude, incoming_longitude);
var item = {
latitude: childSnapshot.child('latitude').val(),
longitude: childSnapshot.child('longitude').val(),
token: childSnapshot.key,
distance: distance_to_efar
};
returnArr.push(item);
});
return returnArr;
};
If more clarification or code is needed just let me know. I've been stuck on this forever...
Don't use on() with Cloud Functions. That's almost never the right thing to use, since it adds a listener that could be invoked any number of times as the database changes. Use once() to get a single snapshot and act on that.
Also, you must return a promise from the function that resolves when all the asynchronous work in that function is complete. on() doesn't return a promise, so your function isn't doing that as well.
You might want to study some of the official sample code and follow the patterns established there.

Categories

Resources