My problem is regarding the push notification using Flutter and firebase_messaging plugin
Problem:
I have integrated firebase_messaging plugin to my flutter app for push notification.
I can guarantee that the setup is correct to the best of my knowledge as i am receiving push notification. The problem occurs when pushes are received only when my app is running in the background(like minimizing but available in the system memory). Pushes are not received when app is in foreground or is killed.
To provide a solution on what I have tried, I could not figure out actually needs to be done.
I have followed tutorials on this and applied every single step to overcome the problem but to no avail.
I am using nodeJS to handle the firebase-admin and serviceaccountkey file as I need device_tokens from my DB.
NodeJS
const firebase = require('firebase-admin');
const serviceAccount = require('../controller/firebase/serviceAccountKey.json');
firebase.initializeApp({
credential: firebase.credential.cert(serviceAccount)
});
//Function to actually implement the push
const pushNotificationInitiatorSubscription = (resultValue) => {
let devicesTokenString = resultValue[0]['device_token'];
const firebaseToken = devicesTokenString;
const payLoad = {
notification: {
title: 'New Subscription',
body: 'You have a new subscription to your material ' + resultValue[0]['course_name']
}
};
const option = {
priority: 'high'
};
firebase.messaging().sendToDevice(firebaseToken, payLoad, option).then(success => {
// console.log(success.results[0]['error']);
// console.log(success.results[1]['error']);
// console.log(success);
}).catch(err => {
console.log(err);
})
Flutter
import 'package:firebase_messaging/firebase_messaging.dart';
class FirebaseCloudMessage {
static FirebaseCloudMessage _instance = new FirebaseCloudMessage.internal();
FirebaseCloudMessage.internal();
factory FirebaseCloudMessage() => _instance;
final FirebaseMessaging _firebaseMessaging = new FirebaseMessaging();
configureFirebaseListeners() {
print('Here');
_firebaseMessaging.configure(
onMessage: (Map<String, dynamic> message) async {
print("Message $message");
// return message;
}, onLaunch: (Map<String, dynamic> message) async {
print("Message $message");
// return message;
}, onResume: (Map<String, dynamic> message) async {
print("Message $message");
// return message;
});
}
}
Help would be appreciated. Thanks
This is the default behavior of notifications received from the firebase notification service currently. You have to manually write code if you wanna show the notification when your app is in the foreground.
Here's a demo of showing notification in flutter using flutter_local_notifications package.
NOTE: This is a really basic example of showing notifications in flutter using flutter_local_notification package. There's a lot you can configure. For a detailed explanation, visit homepage of this package or read this really good medium article
Step 1: install flutter_local_notifications package in your pubspec.yaml
Step 2: initiate FlutterLocalNotifications in initState():
#override
void initState() {
super.initState();
var initializationSettingsAndroid =
new AndroidInitializationSettings('#mipmap/ic_launcher');
var initializationSettingsIOS = new IOSInitializationSettings();
var initializationSettings = new InitializationSettings(
initializationSettingsAndroid, initializationSettingsIOS);
flutterLocalNotificationsPlugin = new FlutterLocalNotificationsPlugin();
flutterLocalNotificationsPlugin.initialize(initializationSettings,
onSelectNotification: onSelectNotification);
}
Step 3: Create a function to handle click events on the notification. This function will be called when a user will tap on the notification.
Future<dynamic> onSelectNotification(String payload) async {
/*Do whatever you want to do on notification click. In this case, I'll show an alert dialog*/
showDialog(
context: context,
builder: (_) => AlertDialog(
title: Text(payload),
content: Text("Payload: $payload"),
),
);
}
Step 4: Write a function to show notification:
Future<void> _showNotification(
int notificationId,
String notificationTitle,
String notificationContent,
String payload, {
String channelId = '1234',
String channelTitle = 'Android Channel',
String channelDescription = 'Default Android Channel for notifications',
Priority notificationPriority = Priority.High,
Importance notificationImportance = Importance.Max,
}) async {
var androidPlatformChannelSpecifics = new AndroidNotificationDetails(
channelId,
channelTitle,
channelDescription,
playSound: false,
importance: notificationImportance,
priority: notificationPriority,
);
var iOSPlatformChannelSpecifics =
new IOSNotificationDetails(presentSound: false);
var platformChannelSpecifics = new NotificationDetails(
androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
await flutterLocalNotificationsPlugin.show(
notificationId,
notificationTitle,
notificationContent,
platformChannelSpecifics,
payload: payload,
);
}
Step 5: Call the _showNotification() function:
_firebaseMessaging.configure(
onMessage: (Map<String, dynamic> message) async {
//print("Message $message");
_showNotification(1234, "GET title FROM message OBJECT", "GET description FROM message OBJECT", "GET PAYLOAD FROM message OBJECT");
return;
}
}
After this, you will be able to show notification even when your app is in the foreground. Hopefully, this will be useful.
Related
I was trying to deploy a function to Firebase to send notifications to all admin accounts when a new user signs up to the app, this is my current code:
const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp();
exports.newDoctorNotification = functions.database.ref("/doctors/{pushId}")
.onCreate((snapshot, context) => {
const newDoctorID = context.params.pushId;
const notificationContent = {
notification: {
title: "New Doctor",
body: "A new doctor just signed up! uid: " + newDoctorID,
icon: "default",
sound: "default",
},
};
const adminTokensRef = functions.database.ref("device_tokens/admin");
const tokens = [];
adminTokensRef.once("value", (querySnapshot) => {
querySnapshot.forEach((adminToken) => {
tokens.push(adminToken.val());
});
});
if (tokens.length > 0) {
return admin.messaging().sendToDevice(tokens, notificationContent)
.then(function(result) {
console.log("Notification sent");
return null;
})
.catch(function(error) {
console.log("Notification failed ", error);
return null;
});
}
});
I have tried many variations such as the get() function and on(), but all give me the same error, I was trying to check the docs on this but they only talked about database triggers so I'm not sure if normal retrieval can work or not.
EDIT:
I updated my code to reach the database node through the snapshot given in the onCreate event, and now it works, although I am facing another problem now, if I push a new doctor under the node "doctors" it doesn't call the function.. but if I hit "test" in the Google Cloud console and it calls the function I get "null" in my snapshot.val() and "undefined" in the newDoctorID above, whereas the snapshot.key gives "doctors", why is it not calling the onCreate function?
Before, I was using azure-sb package to handle service bus message in NodeJS with below sample code:
let message = {
body: JSON.stringify(body),
customProperties: {
userId: userId
}
};
However, after changed to use package #azure/service-bus, I needed to change a little bit to get body in C# code as below:
let signMessage = {
body: body,
customProperties: { // tried to use userProperties but not okay
userId: userId
}
};
However, I still cannot get userProperties successfully in C# or ServiceBus Explorer.
Simple code:
const { ServiceBusClient } = require("#azure/service-bus");
const connectionString = "Endpoint=sb://bowman1012.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=xxxxxx"
const topicName = "test";
const messages = [
{ body: "Albert Einstein",
applicationProperties: {
userId: 'userId'
}
}
];
async function main() {
// create a Service Bus client using the connection string to the Service Bus namespace
const sbClient = new ServiceBusClient(connectionString);
// createSender() can also be used to create a sender for a queue.
const sender = sbClient.createSender(topicName);
try {
// Tries to send all messages in a single batch.
// Will fail if the messages cannot fit in a batch.
// await sender.sendMessages(messages);
// create a batch object
let batch = await sender.createMessageBatch();
for (let i = 0; i < messages.length; i++) {
// for each message in the arry
// try to add the message to the batch
if (!batch.tryAddMessage(messages[i])) {
// if it fails to add the message to the current batch
// send the current batch as it is full
await sender.sendMessages(batch);
// then, create a new batch
batch = await sender.createBatch();
// now, add the message failed to be added to the previous batch to this batch
if (!batch.tryAddMessage(messages[i])) {
// if it still can't be added to the batch, the message is probably too big to fit in a batch
throw new Error("Message too big to fit in a batch");
}
}
}
// Send the last created batch of messages to the topic
await sender.sendMessages(batch);
console.log(`Sent a batch of messages to the topic: ${topicName}`);
// Close the sender
await sender.close();
} finally {
await sbClient.close();
}
}
// call the main function
main().catch((err) => {
console.log("Error occurred: ", err);
process.exit(1);
});
It works fine on my side.
This is the API reference:
https://learn.microsoft.com/en-us/javascript/api/#azure/service-bus/servicebusmessage?view=azure-node-latest
I am developing a bot that sends users a message every hour on skype. It makes an API call and then sends a message containing the parsed message. I started with the welcomebot sample. I then used the switch statement to fire a setInterval()to send the message activity when the user sends a certain message but the message keeps sending even after I call clearinterval() I set to a different string. How can I make clearInterval() work? Or am I going about it the wrong way?
Index.js:
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
// Import required packages
const path = require('path');
const restify = require('restify');
const axios = require('axios');
// Import required bot services.
// See https://aka.ms/bot-services to learn more about the different parts of a bot.
const { BotFrameworkAdapter, UserState, ConversationState, MemoryStorage } = require('botbuilder');
const { WelcomeBot } = require('./bots/welcomeBot');
// Read botFilePath and botFileSecret from .env file
const ENV_FILE = path.join(__dirname, '.env');
require('dotenv').config({ path: ENV_FILE });
// Create bot adapter.
// See https://aka.ms/about-bot-adapter to learn more about bot adapter.
const adapter = new BotFrameworkAdapter({
appId: process.env.MicrosoftAppID,
appPassword: process.env.MicrosoftAppPassword
});
// Catch-all for errors.
adapter.onTurnError = async (context, error) => {
// This check writes out errors to console log .vs. app insights.
// NOTE: In production environment, you should consider logging this to Azure
// application insights.
console.error(`\n [onTurnError] unhandled error: ${ error }`);
// Send a trace activity, which will be displayed in Bot Framework Emulator
await context.sendTraceActivity(
'OnTurnError Trace',
`${ error }`,
'https://www.botframework.com/schemas/error',
'TurnError'
);
// Send a message to the user
await context.sendActivity('The bot encountered an error or bug.');
await context.sendActivity('To continue to run this bot, please fix the bot source code.');
};
// Define a state store for your bot. See https://aka.ms/about-bot-state to learn more about using MemoryStorage.
// A bot requires a state store to persist the dialog and user state between messages.
// For local development, in-memory storage is used.
// CAUTION: The Memory Storage used here is for local bot debugging only. When the bot
// is restarted, anything stored in memory will be gone.
const memoryStorage = new MemoryStorage();
const userState = new UserState(memoryStorage);
const conversationState = new ConversationState(memoryStorage);
// Create the main dialog.
const conversationReferences = {};
const bot = new WelcomeBot(userState, conversationReferences, conversationState);
// Create HTTP server
const server = restify.createServer();
server.listen(process.env.port || process.env.PORT || 3978, function() {
console.log(`\n${ server.name } listening to ${ server.url }`);
console.log('\nGet Bot Framework Emulator: https://aka.ms/botframework-emulator');
console.log('\nTo talk to your bot, open the emulator select "Open Bot"');
});
// Listen for incoming activities and route them to your bot main dialog.
server.post('/api/messages', (req, res) => {
adapter.processActivity(req, res, async (context) => {
// route to main dialog.
await bot.run(context);
});
});
const getBalance = async () => {
try {
return await axios.get('https://balance.com/getbalance', { //this returns a json object
headers: {
Authorization: xxxxx
}
});
} catch (error) {
console.error(error);
}
};
const retrieveBalance = async () => {
const balance = await getBalance();
if (balance.data.message) {
for (const conversationReference of Object.values(conversationReferences)) {
await adapter.continueConversation(conversationReference, async turnContext => {
// If you encounter permission-related errors when sending this message, see
// https://aka.ms/BotTrustServiceUrl
await turnContext.sendActivity(balance.data.message);
});
}
}
};
module.exports.retrieveBalance = retrieveBalance;
Bot.js:
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
// Import required Bot Framework classes.
const { ActionTypes, ActivityHandler, CardFactory, TurnContext } = require('botbuilder');
const balance = require('../index.js');
// Welcomed User property name
const CONVERSATION_DATA_PROPERTY = 'conversationData';
class WelcomeBot extends ActivityHandler {
/**
*
* #param {UserState} //state to persist boolean flag to indicate
* if the bot had already welcomed the user
*/
constructor(userState, conversationReferences, conversationState) {
super();
// Creates a new user property accessor.
// See https://aka.ms/about-bot-state-accessors to learn more about the bot state and state accessors.
this.userState = userState;
this.conversationReferences = conversationReferences;
this.conversationDataAccessor = conversationState.createProperty(CONVERSATION_DATA_PROPERTY);
// The state management objects for the conversation and user state.
this.conversationState = conversationState;
this.onConversationUpdate(async (context, next) => {
this.addConversationReference(context.activity);
await next();
});
this.onMessage(async (context, next) => {
this.addConversationReference(context.activity);
// Add message details to the conversation data.
// Display state data.
// Read UserState. If the 'DidBotWelcomedUser' does not exist (first time ever for a user)
// set the default to false.
// const didBotWelcomedUser = await this.welcomedUserProperty.get(context, false);
// Your bot should proactively send a welcome message to a personal chat the first time
// (and only the first time) a user initiates a personal chat with your bot.
// if (didBotWelcomedUser === false) {
// // The channel should send the user name in the 'From' object
// await context.sendActivity( 'This bot will send you your paystack balance every 5 minutes. To agree reply \'ok\'');
// // Set the flag indicating the bot handled the user's first message.
// await this.welcomedUserProperty.set(context, true);
// }
// This example uses an exact match on user's input utterance.
// Consider using LUIS or QnA for Natural Language Processing.
var text = context.activity.text.toLowerCase();
var myint;
switch (text) {
case 'ok':
myint = setInterval(async () => {
await balance.retrieveBalance();
}, 1000);
// eslint-disable-next-line template-curly-spacing
break;
case 'balance':
await balance.retrieveBalance();
break;
case 'no':
clearInterval(myint);
break;
case 'hello':
case 'hi':
await context.sendActivity(`You said "${ context.activity.text }"`);
break;
case 'info':
case 'help':
await this.sendIntroCard(context);
break;
default:
await context.sendActivity('This is a simple notification bot. ');
}
// By calling next() you ensure that the next BotHandler is run.
await next();
});
// Sends welcome messages to conversation members when they join the conversation.
// Messages are only sent to conversation members who aren't the bot.
this.onMembersAdded(async (context, next) => {
// Iterate over all new members added to the conversation
for (const idx in context.activity.membersAdded) {
// Greet anyone that was not the target (recipient) of this message.
// Since the bot is the recipient for events from the channel,
// context.activity.membersAdded === context.activity.recipient.Id indicates the
// bot was added to the conversation, and the opposite indicates this is a user.
if (context.activity.membersAdded[idx].id !== context.activity.recipient.id) {
await context.sendActivity('Welcome to the Nag Bot. If you want to get nagged with your balance type \'ok\', if you want your balance alone type \'balance. for info type \'info\'');
}
}
// By calling next() you ensure that the next BotHandler is run.
await next();
});
}
/**
* Override the ActivityHandler.run() method to save state changes after the bot logic completes.
*/
async run(context) {
await super.run(context);
// Save state changes
await this.userState.saveChanges(context);
await this.conversationState.saveChanges(context, false);
}
addConversationReference(activity) {
const conversationReference = TurnContext.getConversationReference(activity);
this.conversationReferences[conversationReference.conversation.id] = conversationReference;
}
async sendIntroCard(context) {
const card = CardFactory.heroCard(
'Welcome to Nag Bot balance checker!',
'Welcome to Paystack Nagbot.',
['https://aka.ms/bf-welcome-card-image'],
[
{
type: ActionTypes.OpenUrl,
title: 'Open your dashboard',
value: 'https://dashboard.paystack.com'
},
{
type: ActionTypes.OpenUrl,
title: 'Ask a question on twitter',
value: 'https://twitter.com/paystack'
},
{
type: ActionTypes.OpenUrl,
title: 'View docs',
value: 'https://developers.paystack.co/reference'
}
]
);
await context.sendActivity({ attachments: [card] });
}
}
module.exports.WelcomeBot = WelcomeBot;
I'm in the process of designing a chat bot and trying to find some Node.js sample code and/or documentation on how to implement the Azure Maps service as part of Bot Framework V4. There are many examples of how this is accomplished in V3, but there seems to be no examples of a V4 solution for Node.js. I'm looking to create a step in my botbuilder-dialog flow that would launch a simple "where do we ship it too" location dialog that would guide the user through the dialog and store the address results as part of that users profile. Any help or advice on this would be appreciated.
Yes, this is doable. I created a class (probably overkill, but oh well) in which I make my API call, with my supplied parameters, to get the map. I decided to use Azure Maps (vs Bing Maps) only because I was curious in how it differed. There isn't any reason you couldn't do this with Bing Maps, as well.
In the bot, I am using a component dialog because of how I have the rest of my bot designed. When the dialog ends, it will fall off the stack and return to the parent dialog.
In my scenario, the bot presents the user with a couple choices. "Send me a map" generates a map and sends it in an activity to the client/user. Anything else sends the user onward ending the dialog.
You will need to decide how you are getting the user's location. I developed this with Web Chat in mind, so I am getting the geolocation from the browser and returning it to the bot to be used when getMap() is called.
const { ActivityTypes, InputHints } = require('botbuilder');
const fetch = require('node-fetch');
class MapHelper {
async getMap(context, latitude, longitude) {
var requestOptions = {
method: 'GET',
headers: {
'Content-Type': 'application/json'
},
redirect: 'follow'
};
const result = await fetch(`https://atlas.microsoft.com/map/static/png?subscription-key=${ process.env.AZURE_MAPS_KEY }&api-version=1.0&layer=basic&zoom=13¢er=${ longitude },${ latitude }&language=en-US&pins=default|al.67|la12 3|lc000000||'You!'${ longitude } ${ latitude }&format=png`, requestOptions)
.then(response => response.arrayBuffer())
.then(async result => {
const bufferedData = Buffer.from(result, 'binary');
const base64 = bufferedData.toString('base64');
const reply = { type: ActivityTypes.Message };
const attachment = {
contentType: 'image/png',
contentUrl: `data:image/png;base64,${ base64 }`
};
reply.attachments = [attachment];
await context.sendActivity(reply, null, InputHints.IgnoringInput);
})
.catch(error => {
if (error) throw new Error(error);
});
return result;
};
};
module.exports.MapHelper = MapHelper;
const { ChoicePrompt, ChoiceFactory, ComponentDialog, ListStyle, WaterfallDialog } = require('botbuilder-dialogs');
const { MapHelper } = require('./mapHelper');
const CONFIRM_LOCALE_DIALOG = 'confirmLocaleDialog';
const CHOICE_PROMPT = 'confirmPrompt';
class ConfirmLocaleDialog extends ComponentDialog {
constructor() {
super(CONFIRM_LOCALE_DIALOG);
this.addDialog(new ChoicePrompt(CHOICE_PROMPT))
.addDialog(new WaterfallDialog(CONFIRM_LOCALE_DIALOG, [
this.askLocationStep.bind(this),
this.getMapStep.bind(this)
]));
this.initialDialogId = CONFIRM_LOCALE_DIALOG;
}
async askLocationStep(stepContext) {
const choices = ['Send me a map', "I'll have none of this nonsense!"];
return await stepContext.prompt(CHOICE_PROMPT, {
prompt: 'Good sir, may I pinpoint you on a map?',
choices: ChoiceFactory.toChoices(choices),
style: ListStyle.suggestedAction
});
}
async getMapStep(stepContext) {
const { context, context: { activity } } = stepContext;
const text = activity.text.toLowerCase();
if (text === 'send me a map') {
const { latitude, longitude } = activity.channelData;
const mapHelper = new MapHelper();
await mapHelper.getMap(context, latitude, longitude);
const message = 'Thanks for sharing!';
await stepContext.context.sendActivity(message);
return await stepContext.endDialog();
} else {
await stepContext.context.sendActivity('No map for you!');
return await stepContext.endDialog();
}
}
}
module.exports.ConfirmLocaleDialog = ConfirmLocaleDialog;
module.exports.CONFIRM_LOCALE_DIALOG = CONFIRM_LOCALE_DIALOG;
Hope of help!
---- EDIT ----
Per request, location data can be obtained from the browser using the below method. It is, of course, dependent on the user granting access to location data.
navigator.geolocation.getCurrentPosition( async (position) => {
const { latitude, longitude } = position.coords;
// Do something with the data;
console.log(latitude, longitude)
})
i am using nodejs v4 version of the botbuilder https://learn.microsoft.com/en-us/javascript/api/botbuilder/?view=botbuilder-ts-latest
My current code is picked from echo bot and looks like below
const { ActivityHandler } = require('botbuilder');
class ScanBuddyMsBot extends ActivityHandler {
constructor() {
super();
this.onMessage(async (context:any, next:any) => {
await context.sendActivity(`You said '${ context.activity.text }'`);
// By calling next() you ensure that the next BotHandler is run.
await next();
});
}
}
module.exports.ScanBuddyMsBot = ScanBuddyMsBot;
I am looking a way to fetch user email sending message to my bot. I can see in the context activity, conversation id and service url but not the email id.
in another variation of this i am using below way to get email id and not sure how to make below code work for above
var bot = new builder.UniversalBot(connector, async function(session) {
var teamId = session.message.address.conversation.id;
connector.fetchMembers(
session.message.address.serviceUrl,
teamId,
async (err, result) => {
if (err) {
session.send('We faced an error trying to process this information', err);
return
}
else {
const email = result[0].email
}
In Bot Builder v4, you can access that REST API using the getConversationMembers function:
/**
*
* #param {TurnContext} turnContext
*/
async testTeams(turnContext) {
const activity = turnContext.activity;
const connector = turnContext.adapter.createConnectorClient(activity.serviceUrl);
const response = await connector.conversations.getConversationMembers(activity.conversation.id);
const email = response[0].email;
await turnContext.sendActivity(email);
}
Please refer to the documentation and the samples to better understand how to use the v4 SDK.