I'm trying to work on a messenger app, but the twist here is I have 2 Entities (i.e. 2 apps A & B) here.
Now I'm trying to put messaging logic between the two using Firebase. Firebase doesn't support communication between two different applications (A & B) over the same project url. In order to overcome that restriction, I have used the same google-service.json of app A for app B as well.
For app B, I have just changed the project id and auth key. That seems to have worked as I intended. I have tested the push notification as well using the Firebase Console and it seemed to have been working.
Then I have tried to implement the server logic. To make one-on-one notification.
CASE 1
But the problem arises here is that from app B, if I send a notification request, I get a MismatchSenderId error where the project id has not been tempered with.
{"multicast_id":[removed],"success":0,"failure":1,"canonical_ids":0,"results":[{"error":"MismatchSenderId"}]}
CASE 2
and for app A, here is the following response I get:
{"multicast_id":[removed],"success":1,"failure":0,"canonical_ids":0,"results":[{"message_id":"0:1473661851590851%0e4bcac9f9fd7ecd"}]}
For this, the success value is 1 hence, the notification should be sent but it's not sending when I'm making the request from the device. But it works flawlessly when I perform the same server call using Postman or any other client.
Here are my codes MyFirebaseInstanceIDService.java
public class MyFirebaseInstanceIDService extends FirebaseInstanceIdService {
private static final String TAG = "MyFirebaseIIDService";
private static final String FRIENDLY_ENGAGE_TOPIC = "friendly_engage";
#Override
public void onCreate() {
String savedToken = Utility.getFirebaseInstanceId(getApplicationContext());
String defaultToken = getApplication().getString(R.string.pref_firebase_instance_id_default_key);
Log.d("GCM", savedToken);
if (savedToken.equalsIgnoreCase(defaultToken))
//currentToken is null when app is first installed and token is not available
//also skip if token is already saved in preferences...
{
String CurrentToken = FirebaseInstanceId.getInstance().getToken();
if (CurrentToken != null)
Utility.setFirebaseInstanceId(getApplicationContext(), CurrentToken);
Log.d("Value not set", CurrentToken);
updateFCMTokenId(CurrentToken);
}
super.onCreate();
}
/**
* The Application's current Instance ID token is no longer valid
* and thus a new one must be requested.
*/
#Override
public void onTokenRefresh() {
// If you need to handle the generation of a token, initially or
// after a refresh this is where you should do that.
String token = FirebaseInstanceId.getInstance().getToken();
Log.d(TAG, "FCM Token: " + token);
Utility.setFirebaseInstanceId(getApplicationContext(), token);
updateFCMTokenId(token);
}
private void updateFCMTokenId(final String token) {
SQLiteHandler db = new SQLiteHandler(getBaseContext());
final HashMap<String, String> map = db.getUserDetails();
//update fcm token for push notifications
StringRequest str = new StringRequest(Request.Method.POST, AppConfig.UPDATE_GCM_ID, new Response.Listener<String>() {
#Override
public void onResponse(String response) {
Log.d("GCM RESPONSE", response);
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
}
}) {
#Override
protected Map<String, String> getParams() throws AuthFailureError {
HashMap<String, String> param = new HashMap<>();
param.put("user_id", map.get("uid"));
param.put("gcm_registration_id", token);
return param;
}
};
str.setShouldCache(false);
str.setRetryPolicy(new DefaultRetryPolicy(AppConfig.DEFAULT_RETRY_TIME, DefaultRetryPolicy.DEFAULT_MAX_RETRIES, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
AppController.getInstance().addToRequestQueue(str);
}
}
FirebaseMessagingService.java
public class MyFirebaseMessagingService extends FirebaseMessagingService {
private static final String TAG = "MyFirebaseMsgService";
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
//Displaying data in log
//It is optional
try {
Log.d(TAG, "From: " + remoteMessage.getFrom());
Log.d(TAG, "Notification Message Body: " + remoteMessage.getData().get("message"));
} catch (Exception e) {
e.printStackTrace();
}
//Calling method to generate notification
sendNotification(remoteMessage.getData().get("message"));
}
//This method is only generating push notification
//It is same as we did in earlier posts
private void sendNotification(String messageBody) {
Intent intent = new Intent(this, ChatRoomActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent,
PendingIntent.FLAG_ONE_SHOT);
Uri defaultSoundUri= RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
android.support.v4.app.NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle("NAME")
.setContentText(messageBody)
.setAutoCancel(true)
.setSound(defaultSoundUri)
.setContentIntent(pendingIntent);
NotificationManager notificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(0, notificationBuilder.build());
}
}
And here is the declaration in the Manifest.xml within Application tag
<service
android:name=".MyFirebaseMessagingService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<service
android:name=".MyFirebaseInstanceIDService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.INSTANCE_ID_EVENT" />
</intent-filter>
</service>
TIA
CASE 1 Solved
I have managed to solve CASE 1 where for B I had to use the server api key of B , similarly for A
EDIT 2
Added Server side code
public function sendNotification($message, $gcm_id, $user_level)
{
if ($user_level == "level") {
$server_key = "xys";
} else $server_key = "ABC";
$msg = array
(
'message' => $message,
'title' => 'Title',
'vibrate' => 1,
'sound' => 1,
'largeIcon' => 'large_icon',
'smallIcon' => 'small_icon'
);
$fields = array
(
'to' => $gcm_id,
'data' => $msg
);
$headers = array
(
'Authorization: key=' . $server_key,
'Content-Type: application/json'
);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://fcm.googleapis.com/fcm/send');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($fields));
$result = curl_exec($ch);
curl_close($ch);
echo $result;
}
Edit #1:
1) Make sure you are sending a valid json to the fcm.
2) Make sure you are sending to the right token.
Other informations on how to send notifications:
Send messages to specific devices
To send messages to specific devices, set the to the registration token for the specific app instance
curl -H "Content-type: application/json" -H "Authorization:key=<Your Api key>" -X POST -d '{ "data": { "score": "5x1","time": "15:10"},"to" : "<registration token>"}' https://fcm.googleapis.com/fcm/send
Send messages to topics
here the topic is : /topics/foo-bar
curl -H "Content-type: application/json" -H "Authorisation:key=<Your Api key>" -X POST -d '{ "to": "/topics/foo-bar","data": { "message": "This is a Firebase Cloud Messaging Topic Message!"}}' https://fcm.googleapis.com/fcm/send
Send messages to device groups
Sending messages to a device group is very similar to sending messages to an individual device. Set the to parameter to the unique notification key for the device group
curl -H "Content-type: application/json" -H "Authorisation:key=<Your Api key>" -X POST -d '{"to": "<aUniqueKey>","data": {"hello": "This is a Firebase Cloud Messaging Device Group Message!"}}' https://fcm.googleapis.com/fcm/send
Original:
The problem is you server configuration. If you want to manage two firebase apps in single server you have you have to config two firebase apps with your Firebase APK_KEY that located at:
Go to your applications in Firebase console -> Click on three dots at the top right -> Manage -> CLOUD MESSAGES -> (Server key)
After you get your both server keys for your two apps, you have to configure it like this:
var firebaseLib = require("firebase");
var app1Config = {
apiKey: "<PROJECT_1_API_KEY>",
authDomain: "<PROJECT_1_ID>.firebaseapp.com",
databaseURL: "https://<PROJECT_1_DATABASE_NAME>.firebaseio.com",
storageBucket: "<PROJECT_1_BUCKET>.appspot.com",
}
var app2Config = {
apiKey: "<PROJECT_2_API_KEY>",
authDomain: "<PROJECT_2_ID>.firebaseapp.com",
databaseURL: "https://<PROJECT_2_DATABASE_NAME>.firebaseio.com",
storageBucket: "<PROJECT_2_BUCKET>.appspot.com",
}
var firebaseApp1 = firebaseLib.initailize(app1Config); // Primary
var firebaseApp2 = firebaseLib.initailize(app2Config, "Secondary"); // Secondary
I fixed error MismatchSenderId
Example, below:
valid token: cwsm26j-8qM:APA91bEGbg5xxxxxxxxxxxxxxxxxxxxxx
invalid token: APA91bEGbg5xxxxxxxxxxxxxxxxxxxxxx
Related
I have integrated FCM in my app. Whenever app is in background, no fcm message is received. I have tried both notification type and data type messages. Even notification messages are not displayed in notification tray. They are just lost!
Please help me out where I am going wrong. I have followed everything as per documentation and have been researching on this for a whole week.
My Manifest:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application ....>
<service
android:name=".MyFirebaseMessagingService"
android:exported="true"
android:enabled="true">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
</application>
My Firebase messaging service:
public class MyFirebaseMessagingService extends FirebaseMessagingService {
private static final String TAG = "MyFirebaseMsgService";
SharedPreferences sharedPref;
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
if (remoteMessage.getData().size() > 0) {
Log.d(TAG, "Message data payload: " + remoteMessage.getData());
sendNotification(remoteMessage.getData().get("title"), remoteMessage.getData().get("message"));
}
if (remoteMessage.getNotification() != null) {
Log.d(TAG, "Message Notification Body: " + remoteMessage.getNotification().getBody());
sendNotification(remoteMessage.getNotification().getTitle(), remoteMessage.getNotification().getBody());
}
}
#Override
public void onNewToken(String token) {
sendRegistrationToServer(token);
}
private void sendRegistrationToServer(String token) {
//Sending handled here
}
/**
* Create and show a simple notification containing the received FCM message.
*
* #param messageBody FCM message body received.
*/
private void sendNotification(String title, String messageBody) {
Intent intent = new Intent(this, UserHomeActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.putExtra("title", title);
intent.putExtra("message", messageBody);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 273, intent,
PendingIntent.FLAG_ONE_SHOT);
//String channelId = getString(R.string.default_notification_channel_id);
String channelId = "Sandeep123";
Uri defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
NotificationCompat.Builder notificationBuilder =
new NotificationCompat.Builder(this, channelId)
.setContentTitle(title)
.setSmallIcon(R.mipmap.ic_launcher_foreground_new)
.setColorized(true)
.setColor(Color.BLUE)
.setContentText(messageBody)
.setAutoCancel(true)
.setVisibility(VISIBILITY_PUBLIC)
.setSound(defaultSoundUri)
.setContentIntent(pendingIntent);
NotificationManager notificationManager =(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
//NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
// Since android Oreo notification channel is needed.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(channelId,
getString(R.string.channel_name),
NotificationManager.IMPORTANCE_HIGH);
notificationManager.createNotificationChannel(channel);
}
notificationManager.notify(createID(), notificationBuilder.build());
}
public int createID() {
Date now = new Date();
int id = Integer.parseInt(new SimpleDateFormat("ddHHmmss", Locale.US).format(now));
return id;
}
}
I have added both my debug and release SHA-1 in firebase console. I dont know where else i can be going wrong. It works on all devices when app is active. But it does not work at all when app is in background.
*------------Update - server side code
function sendGcmNotification($amountAdded, $tok,$des){
define( 'API_ACCESS_KEY', '***' );
$title = "Rs.".$amountAdded." added as credit";
$notificationMsg = "***";
//$token = array();
//$token[] = $tok;
$msg =
[
'message' => $notificationMsg,
'title' => $title
];
$android = ["priority"=>"high"];
$fields =
[
'to' => $tok,
'data' => $msg,
'time_to_live' => 900,
'priority' => 10,
'android' => $android
];
$headers =
[
'Authorization: key=' . API_ACCESS_KEY,
'Content-Type: application/json'
];
$ch = curl_init();
curl_setopt( $ch,CURLOPT_URL, 'https://fcm.googleapis.com/fcm/send' );
curl_setopt( $ch,CURLOPT_POST, true );
curl_setopt( $ch,CURLOPT_HTTPHEADER, $headers );
curl_setopt( $ch,CURLOPT_RETURNTRANSFER, true );
curl_setopt( $ch,CURLOPT_SSL_VERIFYPEER, false );
curl_setopt( $ch,CURLOPT_POSTFIELDS, json_encode( $fields ) );
$result = curl_exec($ch );
curl_close( $ch );
echo $result;
}
The Log cat Log is as follows:
2019-01-24 11:14:08.310 1541-1578/? W/ActivityManager: Background start not allowed: service Intent { act=com.google.firebase.MESSAGING_EVENT pkg=in.dailydelivery.dailydelivery cmp=in.dailydelivery.dailydelivery/.MyFirebaseMessagingService (has extras) } to in.dailydelivery.dailydelivery/.MyFirebaseMessagingService from pid=26445 uid=10210 pkg=in.dailydelivery.dailydelivery 2019-01-24 11:14:08.311 26445-26445/?
E/FirebaseInstanceId: Error while delivering the message: ServiceIntent not found
Please help me out.
Sandeep.
For FCM to deliver the message to app in foreground/background to create notification on notification bar the message format being send from app server should match the following format, please note Notification Message won't receive any callback when app is background, only Data message will receive
Notification Message format:
{
"message":{
"token":"bk3RNwTe3H0:CI2k_HHwgIpoDKCIZvvDMExUdFQ3P1...",
"notification":{
"title":"Portugal vs. Denmark",
"body":"great match!"
}
}
}
Data message:
{
"message":{
"token":"bk3RNwTe3H0:CI2k_HHwgIpoDKCIZvvDMExUdFQ3P1...",
"data":{
"Nick" : "Mario",
"body" : "great match!",
"Room" : "PortugalVSDenmark"
}
}
}
Refer below link for more details
https://firebase.google.com/docs/cloud-messaging/concept-options#setting-the-priority-of-a-message
For fcm to deliver the push notification when device is locked or in background, the message from app server should have the following tags
{
....
"android": {"priority":"high"},
"priority": 10,
....
}
Refer below for more details
https://firebase.google.com/docs/cloud-messaging/concept-options#setting-the-priority-of-a-message
Messages with both notification and data payload, when received in the background. In this case, the notification is delivered to the device’s system tray, and the data payload is delivered in the extras of the intent of your launcher Activity.
Handling Messages: Receive messages in an Android app
You need to declare the service in the application tag of your Manifest.
see this sample code.
To know more, please check this guide.
I am really stuck and want to push notification only to specific user using Firebase. I know the FCM method and have got the token. But this tutorial or other tutorials that I've followed are making me send push notification to both users including the sender. I just want to get notification on Recipient Device. Its been a long day , I've been working on this problem.
Looking for some key or UID of the Recipient now. Is it to be fetched from token that I have : FirebaseInstanceId.getInstance().getToken();
Update : I am not able to store and retrieve token (device token) and other children and also am able to log the message in firebase "Functions" tab , but still not able to push the notification to the device . I am testing it on emulator .Should I test it on real device ?
My Node.js code:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
var rootRef =
functions.database.ref('/notifications/{user_id}/{notification_id}');
exports.sendNotification = rootRef.onWrite(event => {
const user_id = event.params.user_id;
const notification_id = event.params.notification_id;
var request = event.data.val();
var payload = {
data: {
title : "New Message from ",
body: "userName has sent you message",
icon: "default",
}
};
console.log('We have a notification for device token: ', user_id );
console.log('Checking if getting inside the node -- ', request.user );
admin.messaging().sendToDevice(user_id, payload)
.then(function(response){
console.log('This was the notification Feature',response);
})
.catch(function(error){
console.log('Error sending message ',error);
})
})
MyFirebaseMEssagingService.java
public class MyFirebaseMessagingService extends FirebaseMessagingService
{
private static final String TAG = "FCM Service";
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
// TODO: Handle FCM messages here.
// If the application is in the foreground handle both data and
notification messages here.
// Also if you intend on generating your own notifications as a result
of a received FCM
// message, here is where that should be initiated.
//remoteMessage.getNotification().getBody());
if(remoteMessage.getNotification()!=null){
Map<String,String> payload = remoteMessage.getData();
// String title =
remoteMessage.getNotification().getTitle();
// String message =
remoteMessage.getNotification().getBody();
// Log.d(TAG, "Message Notification Title : " + title);
// Log.d(TAG, "Message Notification Body: " + message);
System.out.println("Messaging Service : Title - " + payload.get("title")
+ " , body - " + payload.get("body"));
sendNotification(payload);
}
}
#Override
public void onDeletedMessages(){
}
private void sendNotification(Map<String,String> payload){
NotificationCompat.Builder builder = new
NotificationCompat.Builder(this);
builder.setSmallIcon(R.mipmap.ic_launcher);
builder.setContentTitle("Firebase Push
Notification"+payload.get("title"));
builder.setContentText("Notification Text : "+ payload.get("body"));
Intent intent = new Intent(this, ChatWindow.class);
TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
stackBuilder.addNextIntent(intent);
PendingIntent pendingIntent =
stackBuilder.getPendingIntent(0,PendingIntent.FLAG_UPDATE_CURRENT);
builder.setContentIntent(pendingIntent);
NotificationManager notificationManager = (NotificationManager)
getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(0, builder.build());
}
}
FirebaseIDService.java:
public class FirebaseIDService extends FirebaseInstanceIdService {
private static final String TAG = "FirebaseIDService";
#Override
public void onTokenRefresh() {
// Get updated InstanceID token.
String refreshedToken = FirebaseInstanceId.getInstance().getToken();
Log.d(TAG, "Refreshed token: " + refreshedToken);
DatabaseReference firebase ;//= new Firebase("https://materialtabs-
b5734.firebaseio.com//messages");
firebase =
FirebaseDatabase.getInstance().getReferenceFromUrl("https://materialtabs-
b5734.firebaseio.com//notifications/");
// TODO: Implement this method to send any registration to your app's
servers.
sendRegistrationToServer(refreshedToken);
}
/**
* Persist token to third-party servers.
*
* Modify this method to associate the user's FCM InstanceID token with any
server-side account
* maintained by your application.
*
* #param token The new token.
*/
private void sendRegistrationToServer(String token) {
// Add custom implementation, as needed.
System.out.println("Reading Token : "+ token);
}
}
And I am starting my 'MyFirebaseMessagingService.java' inside my MainActivity file in "onCreate()"
as "startService(new Intent(this, MyFirebaseMessagingService.class));"
Please help me. I think I am missing some minor details or it could be major, not sure !
For that, you need to store the firebase token of the recipient's device and then whenever you want to send the notification to the recipient you have to retrieve the token of the recipient's from the database and then you can send the notification using that token to the only recipient.
I have been trying to read the official docs and guides about how to send message from one device to another. I have saved registration token of both devices in the Real Time Database, thus I have the registration token of another device.
I have tried the following way to send the message
RemoteMessage message = new RemoteMessage.Builder(getRegistrationToken())
.setMessageId(incrementIdAndGet())
.addData("message", "Hello")
.build();
FirebaseMessaging.getInstance().send(message);
However this is not working. The other device doesn't receive any message. I am not even sure, if I can use upstream message sending to conduct device to device communication.
PS: I just want to know if device-to-device messaging is possible using FCM? If yes, then is the code I used have some issue? If yes, then what is the correct way.
Update:
My question was to ask whether device to device messaging without using any separate server other than firebase could messaging is possible or not, if yes than how, since there's no documentation about it. I do not understand what is left to explain here? Anyways I got the answer and will update it as an answer once the question gets reopened.
Firebase has two features to send messages to devices:
the Notifications panel in your Firebase Console allows you to send notifications to specific devices, groups of users, or topics that users subscribed to.
by calling Firebase Cloud Messaging API, you can send messages with whatever targeting strategy you prefer. Calling the FCM API requires access to your Server key, which you should never expose on client devices. That's why you should always run such code on an app server.
The Firebase documentation shows this visually:
Sending messages from one device directly to another device is not supported through the Firebase Cloud Messaging client-side SDKs.
Update: I wrote a blog post detailing how to send notifications between Android devices using Firebase Database, Cloud Messaging and Node.js.
Update 2: You can now also use Cloud Functions for Firebase to send messages securely, without spinning up a server. See this sample use-case to get started. If you don't want to use Cloud Functions, you can run the same logic on any trusted environment you already have, such as your development machine, or a server you control.
Warning There is a very important reason why we don't mention this approach anywhere. This exposes your server key in the APK that
you put on every client device. It can (and thus will) be taken from
there and may lead to abuse of your project. I highly recommend
against taking this approach, except for apps that you only put on
your own devices. – Frank van Puffelen
Ok, so the answer by Frank was correct that Firebase does not natively support device to device messaging. However there's one loophole in that. The Firebase server doesn't identify whether you have send the request from an actual server or are you doing it from your device.
So all you have to do is send a Post Request to Firebase's messaging server along with the Server Key. Just keep this in mind that the server key is not supposed to be on the device, but there's no other option if you want device-to-device messaging using Firebase Messaging.
I am using OkHTTP instead of default way of calling the Rest API. The code is something like this -
public static final String FCM_MESSAGE_URL = "https://fcm.googleapis.com/fcm/send";
OkHttpClient mClient = new OkHttpClient();
public void sendMessage(final JSONArray recipients, final String title, final String body, final String icon, final String message) {
new AsyncTask<String, String, String>() {
#Override
protected String doInBackground(String... params) {
try {
JSONObject root = new JSONObject();
JSONObject notification = new JSONObject();
notification.put("body", body);
notification.put("title", title);
notification.put("icon", icon);
JSONObject data = new JSONObject();
data.put("message", message);
root.put("notification", notification);
root.put("data", data);
root.put("registration_ids", recipients);
String result = postToFCM(root.toString());
Log.d(TAG, "Result: " + result);
return result;
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(String result) {
try {
JSONObject resultJson = new JSONObject(result);
int success, failure;
success = resultJson.getInt("success");
failure = resultJson.getInt("failure");
Toast.makeText(getCurrentActivity(), "Message Success: " + success + "Message Failed: " + failure, Toast.LENGTH_LONG).show();
} catch (JSONException e) {
e.printStackTrace();
Toast.makeText(getCurrentActivity(), "Message Failed, Unknown error occurred.", Toast.LENGTH_LONG).show();
}
}
}.execute();
}
String postToFCM(String bodyString) throws IOException {
RequestBody body = RequestBody.create(JSON, bodyString);
Request request = new Request.Builder()
.url(FCM_MESSAGE_URL)
.post(body)
.addHeader("Authorization", "key=" + SERVER_KEY)
.build();
Response response = mClient.newCall(request).execute();
return response.body().string();
}
I hope Firebase will come with a better solution in future. But till then, I think this is the only way. The other way would be to send topic message or group messaging. But that was not in the scope of the question.
Update:
The JSONArray is defined like this -
JSONArray regArray = new JSONArray(regIds);
regIds is a String array of registration ids, you want to send this message to. Keep in mind that the registration ids must always be in an array, even if you want it to send to a single recipient.
I have also been using direct device to device gcm messaging in my prototype. It has been working very well. We dont have any server. We exchange GCM reg id using sms/text and then communicate using GCM after that. I am putting here code related to GCM handling
**************Sending GCM Message*************
//Sends gcm message Asynchronously
public class GCM_Sender extends IntentService{
final String API_KEY = "****************************************";
//Empty constructor
public GCM_Sender() {
super("GCM_Sender");
}
//Processes gcm send messages
#Override
protected void onHandleIntent(Intent intent) {
Log.d("Action Service", "GCM_Sender Service Started");
//Get message from intent
String msg = intent.getStringExtra("msg");
msg = "\"" + msg + "\"";
try{
String ControllerRegistrationId = null;
//Check registration id in db
if(RegistrationIdAdapter.getInstance(getApplicationContext()).getRegIds().size() > 0 ) {
String controllerRegIdArray[] = RegistrationIdAdapter.getInstance(getApplicationContext()).getRegIds().get(1);
if(controllerRegIdArray.length>0)
ControllerRegistrationId = controllerRegIdArray[controllerRegIdArray.length-1];
if(!ControllerRegistrationId.equalsIgnoreCase("NULL")){
// 1. URL
URL url = new URL("https://android.googleapis.com/gcm/send");
// 2. Open connection
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
// 3. Specify POST method
urlConnection.setRequestMethod("POST");
// 4. Set the headers
urlConnection.setRequestProperty("Content-Type", "application/json");
urlConnection.setRequestProperty("Authorization", "key=" + API_KEY);
urlConnection.setDoOutput(true);
// 5. Add JSON data into POST request body
JSONObject obj = new JSONObject("{\"time_to_live\": 0,\"delay_while_idle\": true,\"data\":{\"message\":" + msg + "},\"registration_ids\":[" + ControllerRegistrationId + "]}");
// 6. Get connection output stream
OutputStreamWriter out = new OutputStreamWriter(urlConnection.getOutputStream());
out.write(obj.toString());
out.close();
// 6. Get the response
int responseCode = urlConnection.getResponseCode();
BufferedReader in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null){
response.append(inputLine);
}
in.close();
Log.d("GCM getResponseCode:", new Integer(responseCode).toString());
}else{
Log.d("GCM_Sender:","Field REGISTRATION_TABLE is null");
}
}else {
Log.d("GCM_Sender:","There is no Registration ID in DB ,please sync devices");
}
} catch (Exception e) {
e.printStackTrace();
//MessageSender.getInstance().sendMessage(msg, Commands.SMS_MESSAGE);
}
}
//Called when service is no longer alive
#Override
public void onDestroy() {
super.onDestroy();
//Do a log that GCM_Sender service has been destroyed
Log.d("Action Service", "GCM_Sender Service Destroyed");
}
}
**************Receiving GCM Message*************
public class GCM_Receiver extends WakefulBroadcastReceiver {
public static final String RETRY_ACTION ="com.google.android.c2dm.intent.RETRY";
public static final String REGISTRATION ="com.google.android.c2dm.intent.REGISTRATION";
public SharedPreferences preferences;
//Processes Gcm message .
#Override
public void onReceive(Context context, Intent intent) {
ComponentName comp = new ComponentName(context.getPackageName(),
GCMNotificationIntentService.class.getName());
//Start GCMNotificationIntentService to handle gcm message asynchronously
startWakefulService(context, (intent.setComponent(comp)));
setResultCode(Activity.RESULT_OK);
/*//Check if DatabaseService is running .
if(!DatabaseService.isServiceRunning) {
Intent dbService = new Intent(context,DatabaseService.class);
context.startService(dbService);
}*/
//Check if action is RETRY_ACTION ,if it is then do gcm registration again .
if(intent.getAction().equals(RETRY_ACTION)) {
String registrationId = intent.getStringExtra("registration_id");
if(TextUtils.isEmpty(registrationId)){
DeviceRegistrar.getInstance().register(context);
}else {
//Save registration id to prefs .
preferences = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = preferences.edit();
editor.putString("BLACKBOX_REG_ID",registrationId);
editor.commit();
}
} else if (intent.getAction().equals(REGISTRATION)) {
}
}
}
//Processes gcm messages asynchronously .
public class GCMNotificationIntentService extends IntentService{
public static final int NOTIFICATION_ID = 1;
private NotificationManager mNotificationManager;
String gcmData;
private final String TAG = "GCMNotificationIntentService";
//Constructor with super().
public GCMNotificationIntentService() {
super("GcmIntentService");
}
//Called when startService() is called by its Client .
//Processes gcm messages .
#Override
protected void onHandleIntent(Intent intent) {
Log.d("GCMNotificationIntentService", "GCMNotificationIntentService Started");
Bundle extras = intent.getExtras();
//Get instance of GoogleCloudMessaging .
GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(this);
//Get gcm message type .
String messageType = gcm.getMessageType(intent);
if (!extras.isEmpty()) {
if (GoogleCloudMessaging.MESSAGE_TYPE_SEND_ERROR
.equals(messageType)) {
sendNotification("Send error: " + extras.toString());
} else if (GoogleCloudMessaging.MESSAGE_TYPE_DELETED
.equals(messageType)) {
sendNotification("Deleted messages on server: "
+ extras.toString());
} else if (GoogleCloudMessaging.MESSAGE_TYPE_MESSAGE
.equals(messageType)) {
Log.i(TAG, "Completed work # " + SystemClock.elapsedRealtime());
gcmData = extras.getString("message");
Intent actionService = new Intent(getApplicationContext(),Action.class);
actionService.putExtra("data", gcmData);
//start Action service .
startService(actionService);
//Show push notification .
sendNotification("Action: " + gcmData);
//Process received gcmData.
Log.d(TAG,"Received Gcm Message from Controller : " + extras.getString("message"));
}
}
GCM_Receiver.completeWakefulIntent(intent);
}
//Shows notification on device notification bar .
private void sendNotification(String msg) {
mNotificationManager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);
Intent notificationIntent = new Intent(this, BlackboxStarter.class);
//Clicking on GCM notification add new layer of app.
notificationIntent.setFlags( Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent contentIntent = PendingIntent.getActivity(this, 0,notificationIntent, PendingIntent.FLAG_CANCEL_CURRENT);
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(
this).setSmallIcon(R.drawable.gcm_cloud)
.setContentTitle("Notification from Controller")
.setStyle(new NotificationCompat.BigTextStyle().bigText(msg))
.setContentText(msg);
mBuilder.setContentIntent(contentIntent);
mNotificationManager.notify(NOTIFICATION_ID, mBuilder.build());
//Play default notification
try {
Uri notification = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
Ringtone r = RingtoneManager.getRingtone(getApplicationContext(), notification);
r.play();
} catch (Exception e) {
e.printStackTrace();
}
}
//Called when service is no longer be available .
#Override
public void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
Log.d("GCMNotificationIntentService", "GCMNotificationIntentService Destroyed");
}
}
According to the new documentation which was updated on October 2, 2018 you must send post request as below
https://fcm.googleapis.com/fcm/send
Content-Type:application/json
Authorization:key=AIzaSyZ-1u...0GBYzPu7Udno5aA //Server key
{
"to": "sent device's registration token",
"data": {
"hello": "message from someone",
}
}
To get device's registration token extend FirebaseMessagingService and override onNewToken(String token)
For more info refer to doc https://firebase.google.com/docs/cloud-messaging/android/device-group
I am late but above solutions has helped me to write down this simple answer, you can send your message directly to android devices from android application, here is the simple implementation I have done and it works great for me.
compile android volley library
compile 'com.android.volley:volley:1.0.0'
Just copy paste this simple function ;) and your life will become smooth just like knife in butter. :D
public static void sendPushToSingleInstance(final Context activity, final HashMap dataValue /*your data from the activity*/, final String instanceIdToken /*firebase instance token you will find in documentation that how to get this*/ ) {
final String url = "https://fcm.googleapis.com/fcm/send";
StringRequest myReq = new StringRequest(Request.Method.POST,url,
new Response.Listener<String>() {
#Override
public void onResponse(String response) {
Toast.makeText(activity, "Bingo Success", Toast.LENGTH_SHORT).show();
}
},
new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
Toast.makeText(activity, "Oops error", Toast.LENGTH_SHORT).show();
}
}) {
#Override
public byte[] getBody() throws com.android.volley.AuthFailureError {
Map<String, Object> rawParameters = new Hashtable();
rawParameters.put("data", new JSONObject(dataValue));
rawParameters.put("to", instanceIdToken);
return new JSONObject(rawParameters).toString().getBytes();
};
public String getBodyContentType()
{
return "application/json; charset=utf-8";
}
#Override
public Map<String, String> getHeaders() throws AuthFailureError {
HashMap<String, String> headers = new HashMap<String, String>();
headers.put("Authorization", "key="+YOUR_LEGACY_SERVER_KEY_FROM_FIREBASE_CONSOLE);
headers.put("Content-Type","application/json");
return headers;
}
};
Volley.newRequestQueue(activity).add(myReq);
}
Note
If you want to send message to topics so you can change parameter instanceIdToken to something like /topics/topicName.
For groups implementation is the same but you just need to take care of parameters. checkout Firebase documentation and you can pass those parameters.
let me know if you face any issue.
I am developing an Android app that using Push Notification feature. I need to push from server. I use Firebase for it. This is my first time using Firebase. But when I push from server using PHP and CURL, it is giving me invalid registration error.
I get the Firebase token in Android like this
String token = FirebaseInstanceId.getInstance().getToken();
Then I save sent that token to server and saved in the database.
At server, I am pushing like this
class Pusher extends REST_Controller {
function __construct()
{
parent::__construct();
}
public function notification_get()
{
$rows = $this->db->get('device_registration')->result();
$tokens= array();
if(count($rows)>0)
{
foreach($rows as $row)
{
$tokens[] = $row->token;
}
}
$message = array("message"=>"FCM PUSH NOTIFICATION TESTING");
if(count($tokens)>0)
{
$result = $this->send_notification($tokens,$message);
if(!$result)
{
die("Unable to send");
}
else{
$this->response($result, REST_Controller::HTTP_OK);
}
}
}
function send_notification($tokens,$message)
{
$url = 'https://fcm.googleapis.com/fcm/send';
$fields = array(
'registration_ids'=>$tokens,
'data'=>$message
);
$headers = array(
'Authorization:key = AIzaSyApyfgXsNQ3dFTGWR6ns_9pttr694VDe5M',//Server key from firebase
'Content-Type: application/json'
);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($fields));
$result = curl_exec($ch);
if($result==FALSE)
{
return FALSE;
}
curl_close($ch);
return $result;
}
}
I am using CodeIgniter 3 framework for building Rest API. When I push accessing URL from browser, it returns JSON data with error as in the below screenshot.
As you can see it is giving InvalidRegistration error and message is not pushed to devices. What is wrong with my code?
Additional
This is my FirebaseMessagingService class that show notification in Android
public class FirebaseMessagingService extends com.google.firebase.messaging.FirebaseMessagingService {
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
super.onMessageReceived(remoteMessage);
showNotification(remoteMessage.getData().get("message"));
}
private void showNotification(String message)
{
Intent i = new Intent(this,MainActivity.class);
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent pendingIntent = PendingIntent.getActivity(this,0,i,PendingIntent.FLAG_UPDATE_CURRENT);
NotificationCompat.Builder builder = new NotificationCompat.Builder(this).setAutoCancel(true)
.setContentTitle("FCM Test")
.setContentText(message)
.setSmallIcon(R.drawable.info)
.setContentIntent(pendingIntent);
NotificationManager manager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
manager.notify(0,builder.build());
}
}
Although I am not using codeigniter, I was encountering the InvalidRegistration error while sending to an iOS device, so I thought I would share my solution here.
I had to change registration_ids to to in PHP when sending a Notification message to a single device token, and make sure the value of to was a string and not an array.
Change this:
'registration_ids'=>$tokens,
To this:
'to'=>$tokens[0],
Invalid Registration ID Check the formatting of the registration ID
that you pass to the server. Make sure it matches the registration ID
the phone receives in the com.google.firebase.INSTANCE_ID_EVENT
intent and that you're not truncating it or adding additional
characters. Happens when error code is InvalidRegistration.
Please check with both the side app side and your side that the exact same registration id is stored in the server which Application on mobile receives it in on onTokenRefresh method. You should have received the exact same registration token as developer got in FirebaseInstanceId.getInstance().getToken()
As i got your comment and you've updated the code here is some change in your code it is from google doc it self...
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
// TODO(developer): Handle FCM messages here.
Log.d(TAG, "From: " + remoteMessage.getFrom());
// Check if message contains a data payload.
if (remoteMessage.getData().size() > 0) {
Log.d(TAG, "Message data payload: " + remoteMessage.getData());
}
// Check if message contains a notification payload.
if (remoteMessage.getNotification() != null) {
Log.d(TAG, "Message Notification Body: " + remoteMessage.getNotification().getBody());
}
// Also if you intend on generating your own notifications as a result of a received FCM
// message, here is where that should be initiated. See sendNotification method below.
}
Firebase has three message types:
Notification messages: Notification message works on background or
foreground. When app is in background, Notification messages are
delivered to the system tray. If the app is in the foreground,
messages are handled by onMessageReceived() or
didReceiveRemoteNotification callbacks. These are essentially what is
referred to as Display messages.
Data messages: On Android platform, data message can work on
background and foreground. The data message will be handled by
onMessageReceived(). A platform specific note here would be: On
Android, the data payload can be retrieved in the Intent used to
launch your activity.
Messages with both notification and data payloads: When in the
background, apps receive the notification payload in the notification
tray, and only handle the data payload when the user taps on the
notification. When in the foreground, your app receives a message
object with both payloads available. Secondly, the click_action
parameter is often used in notification payload and not in data
payload. If used inside data payload, this parameter would be treated
as custom key-value pair and therefore you would need to implement
custom logic for it to work as intended.
For Java in Android Do not use FirebaseInstallation
for generating token i don't know why but it does not return the valid token. every time I receive "InvalidRegistration" when try to POST through FCM REST API.
{
"multicast_id": 8303815118005358735,
"success": 0,
"failure": 1,
"canonical_ids": 0,
"results": [
{
"error": "InvalidRegistration"
}
]
}
Instead Use This:
if (firebaseUser != null) {
FirebaseInstanceId.getInstance().getInstanceId()
.addOnCompleteListener(task -> {
if (!task.isSuccessful()) {
Log.d(TAG, "getInstanceId failed", task.getException());
return;
}
if (task.getResult() != null) updateToken(task.getResult().getToken());
});
}
private void updateToken(String refreshToken) {
DocumentReference documentReference;
Token token1 = new Token(refreshToken);//just a class with str field
Map<String, Object> tokenMAp = new HashMap<>();
tokenMAp.put("tokenKey", token1.getToken());
Log.d(TAG, "updateToken: " + token1.getToken());
String id = firebaseUser.getUid();
baseref=sp.getString(BASE_REF,DEFAULT);
documentReference= FirebaseFirestore.getInstance().document(baseref);
updateDocWithToken(documentReference,tokenMAp);
}
private void updateDocWithToken(DocumentReference documentReference, Map<String, Object> tokenMAp) {
documentReference.set(tokenMAp, SetOptions.merge());
}
I created an application that uses GoogleCloudMessaging. Application can register to the gcm and store its registration id to the database at my server. I am using php, for sending push notifications but when the google sends it to my device, the intent service finds its message type to be null. I have tried the same code in a different application and it worked well. but this time it doesn't. The application can get the message from google and handle it by showing a notification with an empty text. I provided the intent service and php codes below. Thanks for your answers.
send_message.php
<?php
if (isset($_GET["regId"]) && isset($_GET["message"])) {
$regId = $_GET["regId"];
$message = $_GET["message"];
include_once './GCM.php';
$gcm = new GCM();
$registatoin_ids = array($regId);
$message = array("price" => $message);
$result = $gcm->send_notification($registatoin_ids, $message);
echo $result;
}
?>
GCM.php
<?php
class GCM {
//put your code here
// constructor
function __construct() {
}
/**
* Sending Push Notification
*/
public function send_notification($registatoin_ids, $message) {
// include config
include_once './config.php';
// Set POST variables
$url = 'https://android.googleapis.com/gcm/send';
$fields = array(
'registration_ids' => $registatoin_ids,
'data' => $message,
);
$headers = array(
'Authorization: key=' . GOOGLE_API_KEY,
'Content-Type: application/json'
);
// Open connection
$ch = curl_init();
// Set the url, number of POST vars, POST data
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
// Disabling SSL Certificate support temporarly
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($fields));
// Execute post
$result = curl_exec($ch);
if ($result === FALSE) {
die('Curl failed: ' . curl_error($ch));
}
// Close connection
curl_close($ch);
echo $result;
}
}
?>
MyIntentService.java
public class MyIntentService extends IntentService {
public static final int NOTIFICATION_ID = 1;
private NotificationManager mNotificationManager;
NotificationCompat.Builder builder;
public MyIntentService() {
super("MyIntentService");
}
#Override
protected void onHandleIntent(Intent intent) {
Log.v(MainActivity.TAG, "Handling intent.");
Bundle extras = intent.getExtras();
GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(this);
// The getMessageType() intent parameter must be the intent you received
// in your BroadcastReceiver.
String messageType = gcm.getMessageType(intent);
generateNotification(getApplicationContext(), extras.getString("price"));
Log.v(MainActivity.TAG, "IntentService messagetype= " + messageType);
if (!extras.isEmpty()) { // has effect of unparcelling Bundle
/*
* Filter messages based on message type. Since it is likely that GCM will be
* extended in the future with new message types, just ignore any message types
* not interested in, or that you don't recognize.
*/
if (GoogleCloudMessaging.MESSAGE_TYPE_SEND_ERROR.equals(messageType)) {
sendNotification("Send error: " + extras.toString());
} else if (GoogleCloudMessaging.MESSAGE_TYPE_DELETED.equals(messageType)) {
sendNotification("Deleted messages on server: " + extras.toString());
// If it's a regular GCM message, do some work.
} else if (GoogleCloudMessaging.MESSAGE_TYPE_MESSAGE.equals(messageType)) {
// This loop represents the service doing some work.
// for (int i = 0; i < 5; i++) {
// Log.i(TAG, "Working... " + (i + 1)
// + "/5 # " + SystemClock.elapsedRealtime());
// try {
// Thread.sleep(5000);
// } catch (InterruptedException e) {
// }
// }
Log.i(MainActivity.TAG, "Completed work # " +SystemClock.elapsedRealtime());
// Post notification of received message.
sendNotification("Received: " + extras.toString());
generateNotification(getApplicationContext(),
"Received:" + extras.getString("price"));
Log.i(MainActivity.TAG, "Received: " + extras.toString());
}
}
// Release the wake lock provided by the WakefulBroadcastReceiver.
WakefulBroadcastReceiver.completeWakefulIntent(intent);
}
In short, "Log.v(MainActivity.TAG, "IntentService messagetype= " + messageType);" displays "IntentService messagetype= null". How can i solve this problem?
public class GcmBroadcastReceiver extends WakefulBroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
// Explicitly specify that GcmIntentService will handle the intent.
ComponentName comp = new ComponentName(context.getPackageName(),
GcmIntentService.class.getName());
// Start the service, keeping the device awake while it is launching.
startWakefulService(context, (intent.setComponent(comp)));
setResultCode(Activity.RESULT_OK);
}
}
Check you AndroidManifest.xml !
And Fix settings for GCM like below.
In my case, I solved this "null" problem.
Good luck!
<permission
android:name="[MY PACKAGE NAME].permission.C2D_MESSAGE"
android:protectionLevel="signature" />
...
<uses-permission android:name="[MY PACKAGE NAME].permission.C2D_MESSAGE" />
...
<service android:name="[MY PACKAGE NAME].GCMIntentService" />
...
<receiver
android:name="[MY PACKAGE NAME].GcmBroadcastReceiver" android:exported="true"
android:permission="com.google.android.c2dm.permission.SEND" >
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<action android:name="com.google.android.c2dm.intent.REGISTRATION" />
<category android:name="[MY PACKAGE NAME]" />
</intent-filter>
</receiver>
In My case, I fixed it by removing:
<action android:name="com.google.android.c2dm.intent.REGISTRATION" />
It is not longer necessary according to the official guide:
https://developer.android.com/google/gcm/client.html
Note, also note that exported="true" in the Receiver is not required either.
Instead of checking the messageType, as proposed by the Android documentation, I just check the Intent action. It's a work around in my eyes, but works:
/**
* Action for GCM registration intents.
*/
private static final String ACTION_GCM_REGISTRATION =
"com.google.android.c2dm.intent.REGISTRATION";
/**
* Action for new app updated installed intent.
*/
private static final String ACTION_PACKAGE_REPLACED =
"android.intent.action.PACKAGE_REPLACED";
#Override
protected void onHandleIntent(final Intent intent) {
final Bundle extras = intent.getExtras();
final String action = intent.getAction();
final GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(this);
// messageType will be null for broadcasts with action registration
// or package replaced
String messageType = gcm.getMessageType(intent);
if (!extras.isEmpty()) {
if (GoogleCloudMessaging.
MESSAGE_TYPE_MESSAGE.equals(messageType)) {
onNotification("Received: " + extras.toString());
} else if (action.equals(ACTION_GCM_REGISTRATION)) {
onRegistration(extras.getString("registration_id"), intent);
} else if (action.equals(ACTION_PACKAGE_REPLACED)) {
onNewAppVersion();
}
}
GcmBroadcastReceiver.completeWakefulIntent(intent);
}