I'm trying to track the sms delivery status of the messages I send using the bot framework. I'm using Twilio, and sending proactive messages. Right now I'm trying to do so with twilio status callbacks
This is similar to this question, I tried that approach but I couldn't get it to work. I've added my url on the TwiML app and it is not firing. I have double and triple checked, and I suspect this url is somehow ignored or not going through with my current set up. I don't get any callbacks on the proactive message nor on the replies the bot sends to the user. However the flow works fine and I can reply and get proper responses from the bot. Edit: calling this "approach 1"
approach 2: I've also tried this doing some light modifications on Twilio adapter, to be able to add my callback just before create message. (I changed it so it uses a customized client wrapper that adds my callback url when creating the twilio resource) This does work, partially: when I reply a message from my bot, I get the status callbacks. But as the proactive message is sent using the default adapter, I don't get a callback on the initial message.
approach 3: Finally, I also tried using the TwilioAdapter when sending the proactive message but for some reason as soon as I send an activity, the TurnContext is disposed, so I can't save the state or do any subsequent actions. This leads me to believe twilio adapter is not intended to be used this way (can't be used on proactive messages), but I'm willing to explore this path if necessary.
Here is the modified Twilio Adapter:
public class TwilioAdapterWithErrorHandler : TwilioAdapter
{
private const string TwilioNumberKey = "TwilioNumber";
private const string TwilioAccountSidKey = "TwilioAccountSid";
private const string TwilioAuthTokenKey = "TwilioAuthToken";
private const string TwilioValidationUrlKey = "TwilioValidationUrl";
public TwilioAdapterWithErrorHandler(IConfiguration configuration, ILogger<TwilioAdapter> logger, TwilioAdapterOptions adapterOptions = null)
: base(
new TwilioClientWrapperWithCallback(new TwilioClientWrapperOptions(configuration[TwilioNumberKey], configuration[TwilioAccountSidKey], configuration[TwilioAuthTokenKey], new Uri(configuration[TwilioValidationUrlKey]))), adapterOptions, logger)
{
OnTurnError = async (turnContext, exception) =>
{
// Log any leaked exception from the application.
logger.LogError(exception, $"[OnTurnError] unhandled error : {exception.Message}");
Task[] tasks = {
// Send a message to the user
turnContext.SendActivityAsync("We're sorry but this bot encountered an error when processing your answer."),
// Send a trace activity, which will be displayed in the Bot Framework Emulator
turnContext.TraceActivityAsync("OnTurnError Trace", exception.Message, "https://www.botframework.com/schemas/error", "TurnError")
};
Task all = Task.WhenAll(tasks); //task with the long running tasks
await Task.WhenAny(all, Task.Delay(5000)); //wait with a timeout
};
}
}
Modified client Wrapper:
public class TwilioClientWrapperWithCallback : TwilioClientWrapper
{
public TwilioClientWrapperWithCallback(TwilioClientWrapperOptions options) : base(options) { }
public async override Task<string> SendMessageAsync(TwilioMessageOptions messageOptions, CancellationToken cancellationToken)
{
var createMessageOptions = new CreateMessageOptions(messageOptions.To)
{
ApplicationSid = messageOptions.ApplicationSid,
MediaUrl = messageOptions.MediaUrl,
Body = messageOptions.Body,
From = messageOptions.From,
};
createMessageOptions.StatusCallback = new System.Uri("https://myApp.ngrok.io/api/TwilioSms/SmsStatusUpdated");
var messageResource = await MessageResource.CreateAsync(createMessageOptions).ConfigureAwait(false);
return messageResource.Sid;
}
}
Finally, here's my summarized code that sends the proactive message:
[HttpPost("StartConversationWithSuperBill/{superBillId:long}")]
[HttpPost("StartConversationWithSuperBill/{superBillId:long}/Campaign/{campaignId:int}")]
public async Task<IActionResult> StartConversation(long superBillId, int? campaignId)
{
ConversationReference conversationReference = this.GetConversationReference("+17545517768");
//Start a new conversation.
await ((BotAdapter)_adapter).ContinueConversationAsync(_appId, conversationReference, async (turnContext, token) =>
{
await turnContext.SendActivityAsync("proactive message 1");
//this code was edited for brevity. Here I would start a new dialog that would cascade into another, but the end result is the same, as soon as a message is sent, the turn context is disposed.
await turnContext.SendActivityAsync("proactive message 2"); //throws ObjectDisposedException
}, default(CancellationToken));
var result = new { status = "Initialized fine!" };
return new JsonResult(result);
}
private ConversationReference GetConversationReference(string targetNumber)
{
string fromNumber = "+18632704234";
return new ConversationReference
{
User = new ChannelAccount { Id = targetNumber, Role = "user" },
Bot = new ChannelAccount { Id = fromNumber, Role = "bot" },
Conversation = new ConversationAccount { Id = targetNumber },
//ChannelId = "sms",
ChannelId = "twilio-sms", //appparently when using twilio adapter we need to set this. if using TwiML app and not using Twilio Adapter, use the above. Otherwise the frameworks interprets answers from SMS as new conversations instead.
ServiceUrl = "https://sms.botframework.com/",
};
}
(I can see that I could just call create conversation reference and do two callbacks, one for each message, but in my actual code I'm creating a dialog that sends one message and then invokes another dialog that starts another message)
Edit 2:
Some clarifications:
On approach 2, I'm using two adapters, as suggested by code sample and documentation on using twilio adapter. The controller that starts the proactive message uses an instance of a default adapter (similar to this one), and TwilioController (the one that gets the twilio incoming messages) uses TwilioAdapterWithErrorHandler.
On approach 3, I excluded the default adapter, and both controllers use TwilioAdapterWithErrorHandler.
Edit 3:
Here's a small repo with the issue.
I found a fix for this problem, around approach 3, by changing the overload I use for ContinueConversation. Replace this :
//Start a new conversation.
await ((BotAdapter)_adapter).ContinueConversationAsync(_appId, conversationReference, async (turnContext, token) =>
With this:
//Start a new conversation.
var twilioAdapter = (TwilioAdapterWithErrorHandler)_adapter;
await twilioAdapter.ContinueConversationAsync(_appId, conversationReference, async (context, token) =>
This way, the context is not disposed, an I can use the twilio adapter for the proactive message and have status callbacks on all messages.
Okay, here's the thing, my colleagues have registered our backend app to Firebase Console. I'm using code bellow to send any notification to the FCM and consequently to the end user on iOS, but I'm failing to do so!
#RequestMapping(value = "/admin/invoice/message", method = RequestMethod.GET)
#ResponseBody
public void getMessage() {
try {
org.springframework.core.io.Resource resource = new ClassPathResource("name_of_the_json_file_below.json");
InputStream dbAsStream = resource.getInputStream(); // when spring boot project running as a jar
FirebaseOptions options = new FirebaseOptions.Builder().setCredentials(GoogleCredentials.fromStream(dbAsStream))
.setDatabaseUrl("https://project_id_property.firebaseio.com").build();
#SuppressWarnings("unused")
FirebaseApp firebaseApp = null;
List<FirebaseApp> firebaseApps = FirebaseApp.getApps();
if (firebaseApps != null && !firebaseApps.isEmpty()) {
for (FirebaseApp app : firebaseApps) {
if (app.getName().equals(FirebaseApp.DEFAULT_APP_NAME)) {
firebaseApp = app;
}
}
} else {
firebaseApp = FirebaseApp.initializeApp(options);
}
Notification notification = new Notification("title", "body");
Message message = Message.builder().setNotification(notification).putData("title", "title").putData("body", "body")
.putData("type", "type").putData("sound", "default").putData("priority", "high").setToken("valid_push_token").build();
if (firebaseApp != null) System.out.println("aaa"); //aaa
String responseMessage = FirebaseMessaging.getInstance().send(message);
System.out.println(responseMessage); //this prints -> projects/project_id/messages/0:1607085339665987%fbe62b14fbe62b14
return;
}catch (Exception e) {
e.printStackTrace();
}
return;
}
Within resource folder inside Spring Boot project there is a json file, presumably gotten at the time of registering out app with FCM.
This file looks like this:
{
"type": "service_account",
"project_id": "project_id",
"private_key_id": "some string with private key id",
"private_key": "some long string with private key",
"client_email": "firebase-adminsdk-k202i#project_id.iam.gserviceaccount.com",
"client_id": "some client id",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-9cgqi%40project_id_string_property.iam.gserviceaccount.com"
}
As soon as some user from iOS mobile app login in, User table gets updated with new valid push token which is part of simple login request. That push token is gotten before the actual login request is sent to our backend app. Namely, request from user's mobile app is made to FCM and a new push token is pulled. This is usual scenario I guess.
For testing I just call local service (Swagger) localhost8080:appName/admin/invoice/message.
The mobile app is not getting any notification!
The problem is that all other services with very huge requests bodies, and bunch of check-ups are able to send notifications. Unfortunately, I cannot invoke them, bunch of cases and conditions need to overlap in some way to be able to reach the lines of code above for sending push notifications!
How to send notifications locally to an end User, assuming all configuration is correct?
This is the function that is both sending sound to iOS and Android.
private Message getMessage(String token, String title, String body, String type, Map<String, String> data) throws FirebaseMessagingException {
AndroidNotification androidNofi = AndroidNotification.builder()
.setSound("default")
.build();
Aps aps = Aps.builder()
.setSound("default")
.build();
ApnsConfig apnsConfig = ApnsConfig.builder()
.setAps(aps)
.build();
AndroidConfig androidConfig = AndroidConfig.builder()
.setNotification(androidNofi)
.build();
Notification notification = new Notification(title, body);
Builder builder = Message.builder()
.setNotification(notification)
.setApnsConfig(apnsConfig)
.setAndroidConfig(androidConfig)
.putData("title", title)
.putData("body", body)
.putData("type", type)
.putData("priority", "high")
.setToken(token);
if (data != null) builder.putAllData(data);
Message message = builder.build();
return message;
}
Can anyone help me with Azure notification hub, how to set up device installation form c# code. I have problem with the Installation object. How to set it to pass it as parameter to CreateOrUpdateInstallation method of hub client instance. It's not clear to me.
I have a hub on azure that works with device registration like charm in local, but uploaded on azure are not working. Now I wanna try with istalation.
thnx
update: after 4 days, I figured out, that you can't send notification to yourself. Azure somehow knows that you are sending notification to yours phone, and that's why my welcome message never delivered to my phone.
update: this is how now I install the device i my backend code:
[HttpGet]
[Route("api/push/test-installation")]
public async Task<IActionResult> NotificationInstalationTest()
{
string connectionString = "{{my connection string}}";
string hubName = "{{my hub name}}";
string token = "{{tokne}}";
NotificationHubClient hubClient = NotificationHubClient.CreateClientFromConnectionString(connectionString, hubName);
string notificationText = $"Test message for Azure delivery for Atila at: {DateTime.Now.ToShortTimeString()}";
var alert = new JObject
(
new JProperty("aps", new JObject(new JProperty("alert", notificationText))),
new JProperty("inAppMessage", notificationText)
).ToString(Newtonsoft.Json.Formatting.None);
IList<string> tags = new List<string>();
tags.Add("email");
IDictionary<string, string> pushVariables = new Dictionary<string, string>();
pushVariables.Add( "email", "atila#panonicit.com" );
Installation installation = new Installation();
installation.InstallationId = Guid.NewGuid().ToString();
installation.Platform = NotificationPlatform.Apns;
installation.PushChannel = token;
installation.Tags = tags;
installation.PushVariables = pushVariables;
await hubClient.CreateOrUpdateInstallationAsync(installation);
NotificationOutcome result = await hubClient.SendAppleNativeNotificationAsync(alert);
return Ok("Success");
}
Now when I hit this endpoint with Postman it works, if the same endpoint call comes from iOS it not works!
thnx
How to set it to pass it as parameter to CreateOrUpdateInstallation method of hub client instance. It's not clear to me.
Based on my understanding, you are registering the notification hub from your backend using the installation model. For your WebAPI project, assuming your method for create/update an installation as follows:
InstallationController.cs
//PUT api/installation
public async Task<HttpResponseMessage> Put(DeviceInstallation deviceUpdate)
{
Installation installation = new Installation();
installation.InstallationId = deviceUpdate.InstallationId;
//TODO:
await hub.CreateOrUpdateInstallationAsync(installation);
return Request.CreateResponse(HttpStatusCode.OK);
}
For your mobile client, you could refer to the following method:
private async Task<HttpStatusCode> CreateOrUpdateInstallationAsync(DeviceInstallation deviceInstallation)
{
using (var httpClient = new HttpClient())
{
//TODO: set your authorization header
//httpClient.DefaultRequestHeaders.Authorization
var putUri =$"{your-backend-endpoint}/api/installation";
string json = JsonConvert.SerializeObject(deviceInstallation);
var response = await httpClient.PutAsync(putUri, new StringContent(json, Encoding.UTF8, "application/json"));
return response.StatusCode;
}
}
Moreover, for more details you could refer to Registration management and here for building backend with the registration model to build your backend using the installation model.
i've written an Azure function and connected the output to a notification-hub to send push notifications using APNS. It works fine as long as i send the notification to all registered devices, but i don't know how to use tags in order to address a specific user. If i try to use a tag, i get an error message saying "Exception while executing function: Functions.SendSinglePushNotification. Microsoft.Azure.WebJobs.Host: Error while handling parameter notification after function returned:. Microsoft.Azure.NotificationHubs: notification.Tag property should be null."
Here's my code so far:
#r "Microsoft.Azure.NotificationHubs"
#r "Newtonsoft.Json"
using System;
using Microsoft.Azure.NotificationHubs;
using Newtonsoft.Json;using
Microsoft.Azure.WebJobs.Host.Bindings.Runtime;
public static void Run(HttpRequestMessage req, TraceWriter log,Binder
binder, out AppleNotification notification)
{
string user = "Test";
string tagExpression = "Test";
string userTag = req.GetQueryNameValuePairs()
.FirstOrDefault(q => string.Compare(q.Key, "userid", true) == 0)
.Value;
string apnsNotificationPayload = "{\"aps\": {\"alert\": \"Test: (" + user + ")\" }}";
notification = new AppleNotification(apnsNotificationPayload);
}
I was trying to use notification = new
AppleNotification(apnsNotificationPayload,tagExpression);
but that does not work. How can i achieve that?
Thanks a lot and best regards
I had similar issue. Eventually, what worked for me was constructing Notification client manually. I am developing functions in Visual Studio, so my code is slightly different from yours.
[FunctionName("MyFunction")]
public static async Task Run([ServiceBusTrigger("queuename", AccessRights.Listen, Connection =
"<connection-settings-name>")] string myQueueItem, TraceWriter log)
{
log.Info($"C# Queue trigger function processed: {myQueueItem}");
var notificationHubSas = "<DefaultFullSharedAccessSignature from Azure portal>";
var notificationHubName = "myhub";
var nhClient = NotificationHubClient.CreateClientFromConnectionString(notificationHubSas, notificationHubName);
var tags = "";
await nhClient.SendTemplateNotificationAsync(<notification payload>, tags);
}
After searching the docs I could not find any info on how to send device to device messages using FCM without the use of an external server.
For example, if I was creating a chat application I would need to send push notifications to users about unread messages since they won't be online all the time and I can't have a persistent service in the background that would always be connected to the real time database because that would be too resource heavy.
So how would I send a push notification to a user "A" when a certain user "B" sends him/her a chat message? Do I need an external server for this or can it be done with just Firebase servers?
UPDATE: It is now possible to use firebase cloud functions as the server for handling push notifications. Check out their documentation here
============
According to the docs you must implement a server for handling push notifications in device to device communication.
Before you can write client apps that use Firebase Cloud Messaging, you must have an app server that meets the following criteria:
...
You'll need to decide which FCM connection server protocol(s) you want to use to enable your app server to interact with FCM connection servers. Note that if you want to use upstream messaging from your client applications, you must use XMPP. For a more detailed discussion of this, see Choosing an FCM Connection Server Protocol.
If you only need to send basic notifications to your users from the server. You can use their serverless solution, Firebase Notifications.
See a comparison here between FCM and Firebase Notifications:
https://firebase.google.com/support/faq/#messaging-difference
Making a HTTP POST request with the link https://fcm.googleapis.com/fcm/send with required header and data helped me. In the below code snippet
Constants.LEGACY_SERVER_KEY is a local class variable, you can find this at your Firebase Project Settings->Cloud Messaging->Legacy Server key. You need to pass device registration token i.e. regToken in below code snippet referenced HERE.
At last you need okhttp library dependency in order to get this snippet work.
public static final MediaType JSON
= MediaType.parse("application/json; charset=utf-8");
private void sendNotification(final String regToken) {
new AsyncTask<Void,Void,Void>(){
#Override
protected Void doInBackground(Void... params) {
try {
OkHttpClient client = new OkHttpClient();
JSONObject json=new JSONObject();
JSONObject dataJson=new JSONObject();
dataJson.put("body","Hi this is sent from device to device");
dataJson.put("title","dummy title");
json.put("notification",dataJson);
json.put("to",regToken);
RequestBody body = RequestBody.create(JSON, json.toString());
Request request = new Request.Builder()
.header("Authorization","key="+Constants.LEGACY_SERVER_KEY)
.url("https://fcm.googleapis.com/fcm/send")
.post(body)
.build();
Response response = client.newCall(request).execute();
String finalResponse = response.body().string();
}catch (Exception e){
//Log.d(TAG,e+"");
}
return null;
}
}.execute();
}
further if you want to send message to a particular topic, replace regToken in json like this
json.put("to","/topics/foo-bar")
and don't forget to add INTERNET permission in your AndroidManifest.xml.
IMPORTANT : - Using above code means your server key resides in the client application. That is dangerous as someone can dig into your application and get the server key to send malicious notifications to your users.
You can do it using Volly Jsonobject request....
follow this Steps first:
1 copy legacy server key and store it as Legacy_SERVER_KEY
Legacy Server key
you can see in picture how to get
2 You need Volley dependency
compile 'com.mcxiaoke.volley:library:1.0.19'
Code for send Push:-
private void sendFCMPush() {
String Legacy_SERVER_KEY = YOUR_Legacy_SERVER_KEY;
String msg = "this is test message,.,,.,.";
String title = "my title";
String token = FCM_RECEIVER_TOKEN;
JSONObject obj = null;
JSONObject objData = null;
JSONObject dataobjData = null;
try {
obj = new JSONObject();
objData = new JSONObject();
objData.put("body", msg);
objData.put("title", title);
objData.put("sound", "default");
objData.put("icon", "icon_name"); // icon_name image must be there in drawable
objData.put("tag", token);
objData.put("priority", "high");
dataobjData = new JSONObject();
dataobjData.put("text", msg);
dataobjData.put("title", title);
obj.put("to", token);
//obj.put("priority", "high");
obj.put("notification", objData);
obj.put("data", dataobjData);
Log.e("!_#rj#_##_PASS:>", obj.toString());
} catch (JSONException e) {
e.printStackTrace();
}
JsonObjectRequest jsObjRequest = new JsonObjectRequest(Request.Method.POST, Constants.FCM_PUSH_URL, obj,
new Response.Listener<JSONObject>() {
#Override
public void onResponse(JSONObject response) {
Log.e("!_##_SUCESS", response + "");
}
},
new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
Log.e("!_##_Errors--", error + "");
}
}) {
#Override
public Map<String, String> getHeaders() throws AuthFailureError {
Map<String, String> params = new HashMap<String, String>();
params.put("Authorization", "key=" + Legacy_SERVER_KEY);
params.put("Content-Type", "application/json");
return params;
}
};
RequestQueue requestQueue = Volley.newRequestQueue(this);
int socketTimeout = 1000 * 60;// 60 seconds
RetryPolicy policy = new DefaultRetryPolicy(socketTimeout, DefaultRetryPolicy.DEFAULT_MAX_RETRIES, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT);
jsObjRequest.setRetryPolicy(policy);
requestQueue.add(jsObjRequest);
}
Just Call sendFCMPush();
1) subscribe an identical topic name, for example:
ClientA.subcribe("to/topic_users_channel")
ClientB.subcribe("to/topic_users_channel")
2) send messages inside the application
GoogleFirebase : How-to send topic messages
Yes, it's possible to do it without any server. You can create a device group client side and then you exchange messages in the group. However there are limitations:
You have to use the same Google account on the devices
You can't send high priority messages
Reference: Firebase doc See the section "Managing device groups on Android client apps"
Google Cloud Functions make it now possible send push notifications from device-to-device without an app server.
I have made cloud function which is trigger when new message is added in database
It is node.js code
'use strict';
const functions = require('firebase-functions');
const admin = require('firebase-admin'); admin.initializeApp();
exports.sendNotification = functions.database.ref('/conversations/{chatLocation}/{messageLocation}')
.onCreate((snapshot, context) => {
// Grab the current value of what was written to the Realtime Database.
const original = snapshot.val();
const toIDUser = original.toID;
const isGroupChat = original.isGroupChat;
if (isGroupChat) {
const tokenss = admin.database().ref(`/users/${toIDUser}/tokens`).once('value').then(function(snapshot) {
// Handle Promise
const tokenOfGroup = snapshot.val()
// get tokens from the database at particular location get values
const valuess = Object.keys(tokenOfGroup).map(k => tokenOfGroup[k]);
//console.log(' ____________ddd((999999ddd_________________ ' + valuess );
const payload = {
notification: {
title: original.senderName + " :- ",
body: original.content
}
};
return admin.messaging().sendToDevice(valuess, payload);
}, function(error) {
console.error(error);
});
return ;
} else {
// get token from the database at particular location
const tokenss = admin.database().ref(`/users/${toIDUser}/credentials`).once('value').then(function(snapshot) {
// Handle Promise
// The Promise was "fulfilled" (it succeeded).
const credentials = snapshot.val()
// console.log('snapshot ......snapshot.val().name****^^^^^^^^^^^^kensPromise****** :- ', credentials.name);
//console.log('snapshot.....****snapshot.val().token****^^^^^^^^^^^^kensPromise****** :- ', credentials.token);
const deviceToken = credentials.token;
const payload = {
notification: {
title: original.senderName + " :- ",
body: original.content
}
};
return admin.messaging().sendToDevice(deviceToken, payload);
}, function(error) {
console.error(error);
});
}
return ;
});
Google Cloud Functions make it now possible send push notifications from device-to-device without an app server.
From the relevant page on Google Cloud Functions:
Developers can use Cloud Functions to keep users engaged and up to
date with relevant information about an app. Consider, for example, an
app that allows users to follow one another's activities in the app.
In such an app, a function triggered by Realtime Database writes to
store new followers could create Firebase Cloud Messaging (FCM)
notifications to let the appropriate users know that they have gained
new followers.
Example:
The function triggers on writes to the Realtime Database path where followers are stored.
The function composes a message to send via FCM.
FCM sends the notification message to the user's device.
Here is a demo project for sending device-to-device push notifications with Firebase and Google Cloud Functions.
You can use firebase realtime database to do so. You can create data structure for storing chats and add observers for the conversation threads for both users. It still does device - server - device architecture, but in this case there is no additional server on the developers' part. This uses the firebase servers. You can check out a tutorial here (ignore the UI part, although, that is also a good starting point for chat UI frameworks).
Firebase Realtime Chat
If you have fcm(gcm) token of the device to whom you want to send notification. It's just a post request to send the notification.
https://github.com/prashanthd/google-services/blob/master/android/gcm/gcmsender/src/main/java/gcm/play/android/samples/com/gcmsender/GcmSender.java
In my case I use retrofit with this class Message:
public class Message {
private String to;
private String collapseKey;
private Notification notification;
private Data data;
public Message(String to, String collapseKey, Notification notification, Data data) {
this.to = to;
this.collapseKey = collapseKey;
this.notification = notification;
this.data = data;
}
}
Data
public class Data {
private String body;
private String title;
private String key1;
private String key2;
public Data(String body, String title, String key1, String key2) {
this.body = body;
this.title = title;
this.key1 = key1;
this.key2 = key2;
}
}
Notification
public class Notification {
private String body;
private String title;
public Notification(String body, String title) {
this.body = body;
this.title = title;
}
}
this the call
private void sentToNotification() {
String to = "YOUR_TOKEN";
String collapseKey = "";
Notification notification = new Notification("Hello bro", "title23");
Data data = new Data("Hello2", "title2", "key1", "key2");
Message notificationTask = new Message(to, collapseKey, notification, data);
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://fcm.googleapis.com/")//url of FCM message server
.addConverterFactory(GsonConverterFactory.create())//use for convert JSON file into object
.build();
ServiceAPI api = new retrofit.create(ServiceAPI.class);
Call<Message> call = api .sendMessage("key=YOUR_KEY", notificationTask);
call.enqueue(new Callback<Message>() {
#Override
public void onResponse(Call<Message> call, retrofit2.Response<Message> response) {
Log.d("TAG", response.body().toString());
}
#Override
public void onFailure(Call<Message> call, Throwable t) {
Log.e("TAG", t.getMessage());
}
});
}
our ServiceAPi
public interface ServiceAPI {
#POST("/fcm/send")
Call<Message> sendMessage(#Header("Authorization") String token, #Body Message message);
}
You can use Retrofit. Subscribe devices to topic news. Send notification from one device to other.
public void onClick(View view) {
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
httpClient.addInterceptor(new Interceptor() {
#Override
public okhttp3.Response intercept(Chain chain) throws IOException {
Request original = chain.request();
// Request customization: add request headers
Request.Builder requestBuilder = original.newBuilder()
.header("Authorization", "key=legacy server key from FB console"); // <-- this is the important line
Request request = requestBuilder.build();
return chain.proceed(request);
}
});
httpClient.addInterceptor(logging);
OkHttpClient client = httpClient.build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://fcm.googleapis.com")//url of FCM message server
.client(client)
.addConverterFactory(GsonConverterFactory.create())//use for convert JSON file into object
.build();
// prepare call in Retrofit 2.0
FirebaseAPI firebaseAPI = retrofit.create(FirebaseAPI.class);
//for messaging server
NotifyData notifydata = new NotifyData("Notification title","Notification body");
Call<Message> call2 = firebaseAPI.sendMessage(new Message("topic or deviceID", notifydata));
call2.enqueue(new Callback<Message>() {
#Override
public void onResponse(Call<Message> call, Response<Message> response) {
Log.d("Response ", "onResponse");
t1.setText("Notification sent");
}
#Override
public void onFailure(Call<Message> call, Throwable t) {
Log.d("Response ", "onFailure");
t1.setText("Notification failure");
}
});
}
POJOs
public class Message {
String to;
NotifyData notification;
public Message(String to, NotifyData notification) {
this.to = to;
this.notification = notification;
}
}
and
public class NotifyData {
String title;
String body;
public NotifyData(String title, String body ) {
this.title = title;
this.body = body;
}
}
and FirebaseAPI
public interface FirebaseAPI {
#POST("/fcm/send")
Call<Message> sendMessage(#Body Message message);
}
Here is walk around how to get notifications without second server apart from the Firebase one. So we use Firebase only, without additional server.
At the mobile app code, we create its own notifications function by Android libraries like here, not using Firebase libraries like here, without Firebase Cloud messaging.
Here is an example with Kotlin:
private fun notification() {
createNotificationChannel()
val intent = Intent(this, LoginActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
}
val pendingIntent: PendingIntent = PendingIntent.getActivity(this, 0, intent, 0)
val notificationBuilder = NotificationCompat.Builder(this, "yuh_channel_id")
.setSmallIcon(R.drawable.ic_send)
.setContentText("yuh")
.setContentText("yuh")
.setAutoCancel(true)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setContentIntent(pendingIntent)
val notificationManager =
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.notify(0, notificationBuilder.build())
with(NotificationManagerCompat.from(this)) {
// notificationId is a unique int for each notification that you must define
notify(0, notificationBuilder.build())
}
}
private fun createNotificationChannel() {
// Create the NotificationChannel, but only on API 26+ because
// the NotificationChannel class is new and not in the support library
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val name = "yuh_channel"
val descriptionText = "yuh_description"
val importance = NotificationManager.IMPORTANCE_DEFAULT
val CHANNEL_ID = "yuh_channel_id"
val channel = NotificationChannel(CHANNEL_ID, name, importance).apply {
description = descriptionText
}
// Register the channel with the system
val notificationManager: NotificationManager =
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
}
In the Firebase database, create collection "pending notifications". Documents should contain user name (to send notification to) and source name (where should user go upon tapping the notification).
In the app code, implement option for adding new records to the Pending Notifications collection. E. g. if user A sends message to user B, then the document with the id of user B (who will be notified) is created in the collection.
In the app code, set up background (when the app is not visible to the user) service. Like here. In the background service, set up a listener for changes in the "Notifications Pending" collection. When the new record with the user id comes to the collection, call the notification function created in the paragrath 1 supra and delete the consequent record from the collection.
So I had an idea here. See: If the FCM, as well as the GCM, has a endpoit to http request where we can send a post json with our message data, including the token (s) of devices that we want this message to be delivered.
So why not send a post to Firebase server with this notification to be delivered to user B? you understand ?
So, you send the message and chat with a call post to ensure delivery of the notification if the user is with your app in the background. I am also in need of it soon, I will test later. What do you say about?
Simplest way :
void sendFCMPush(String msg,String token) {
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
httpClient.addInterceptor(new Interceptor() {
#Override
public okhttp3.Response intercept(Chain chain) throws IOException {
Request original = chain.request();
// Request customization: add request headers
Request.Builder requestBuilder = original.newBuilder()
.header("Authorization", "key="+Const.FIREBASE_LEGACY_SERVER_KEY); // <-- this is the important line
Request request = requestBuilder.build();
return chain.proceed(request);
}
});
httpClient.addInterceptor(logging);
OkHttpClient client = httpClient.build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://fcm.googleapis.com/")//url of FCM message server
.client(client)
.addConverterFactory(GsonConverterFactory.create())//use for convert JSON file into object
.build();
// prepare call in Retrofit 2.0
FirebaseAPI firebaseAPI = retrofit.create(FirebaseAPI.class);
//for messaging server
NotifyData notifydata = new NotifyData("Chatting", msg);
Call<Message> call2 = firebaseAPI.sendMessage(new Message(token, notifydata));
call2.enqueue(new Callback<Message>() {
#Override
public void onResponse(Call<Message> call, retrofit2.Response<Message> response) {
Log.e("## SUCCES #E$#", response.body().toString());
}
#Override
public void onFailure(Call<Message> call, Throwable t) {
Log.e("E$ FAILURE E$#", t.getMessage());
}
});
}
Create Class to make Object:
public class Message {
String to;
NotifyData data;
public Message(String to, NotifyData data) {
this.to = to;
this.data = data;
}
}
Create Class to make Object:
public class Notification {
String title;
String message;
enter code here`enter code here`
public Notification(String title, String message) {
this.title = title;
this.message = message;
}
}