74

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?

Suyash
  • 2,437
  • 2
  • 15
  • 26
  • 3
    I haven't used FCM yet,....but I have used GCM....assuming FCM is almost like GCM.....device A send the message to server who will push the message to device B. Check out https://firebase.google.com/support/faq/#messaging-difference – j4rey May 25 '16 at 11:39
  • 1
    @j4rey89 Yeah I know it can be done using an external server. I'm asking if can be done without it since that would require me to maintain and pay for two servers instead of one. – Suyash May 25 '16 at 11:46
  • 7
    @Suyash it is mandatory to run your own server in order to send FCM messages between your devices. If you are worried about costs of running the server, you can begin deploying to Openshift Online (PaaS) or Google AppEngine (PaaS too) that have a free quota. – MrBrightside May 25 '16 at 12:38
  • 2
    @j4rey89 MrBrightside: sounds like an answer. :-) – Frank van Puffelen May 25 '16 at 13:43
  • http://stackoverflow.com/questions/38432243/how-to-send-device-to-device-notification-by-using-fcm-without-using-xmpp-or-any – Vishal Patoliya ツ Aug 08 '16 at 13:59
  • @Suyash you can send notifications using a post request to other device – Jagjit Singh Aug 12 '16 at 11:21
  • 1
    See: https://firebase.googleblog.com/2016/08/sending-notifications-between-android.html – Micro Nov 24 '16 at 22:05
  • 1
    it seems like you can send a device to device message through an http post without having your own server, according to this documentation on firebase: https://firebase.google.com/docs/cloud-messaging/http-server-ref I haven't been able to figure out how to make the post request. has anyone tried this? It seems too good to be true. – mjpablo23 Nov 29 '16 at 10:23
  • It's now possible with Google Cloud Functions to send device-to-device messages. See answer below: https://stackoverflow.com/a/47106374/144088 – Crashalot Nov 04 '17 at 01:26

13 Answers13

42

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

Community
  • 1
  • 1
eikooc
  • 1,847
  • 22
  • 31
  • 1
    good answer. Do you know any tutorials or videos that can explain how to do this? thank you – Ben Akin Aug 15 '16 at 20:28
  • 4
    See https://firebase.googleblog.com/2016/08/sending-notifications-between-android.html – Frank van Puffelen Aug 28 '16 at 18:54
  • Could you help me to understand please. As far as i understand if i need sent direct message from one uset to another i have to use HTTP and sent this message to my server and next server will use FCM to sent Notification to recipent and thus recipent retrive data with id of sender . Next step recipent connect to FCM and with help of ID retrive all data from FCM DB? Such way? – Aleksey Timoshchenko Aug 30 '16 at 14:51
  • Perfect answer, I had researched ab this 2 days. Very full informations ab FCM and sever need or not. Thank you !. – Khang Tran Nov 27 '16 at 11:20
27

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.

Brijesh Kumar
  • 1,631
  • 2
  • 15
  • 27
  • Hi, is there any possibility to send the messages to subscribed particular channel? – Suchith Mar 05 '17 at 18:35
  • 10
    The downside is 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. That's why you should never do this. – kirtan403 Apr 12 '17 at 17:35
  • @kirtan403 A Strong Encryption to the server key in the client side could stop that??? – Mr.Popular Apr 20 '17 at 20:46
  • @Mr.Popular Maybe, but if someone is able to decompile your code (of course, they can) then they can grab what you are using to encrypt your server key, and get your server key. And then they can send notifications to anyone without any restrictions.. So it is a very bad idea to put server key on the client side. A very very bad idea... – kirtan403 Apr 22 '17 at 10:36
  • Hi freinds, please tell me whats is regToken and how did i generate and also what is Constants.LEGACY_SERVER_KEY please help me about this – Qutbuddin Bohra Jun 04 '17 at 10:54
  • what is regToken ? – Kaushik Makwana Sep 19 '17 at 07:12
  • @KaushikMakwana reg_token is registration token which you can get from your FirebaseInstanceIDService as mentioned in https://firebase.google.com/docs/cloud-messaging/android/client – Brijesh Kumar Sep 19 '17 at 09:43
  • I've tried this code notification is being generated but `remoteMessage.getData()` is null (Empty) Please help – Tabish May 03 '18 at 04:29
  • 1
    @Tabish please use remoteMessage.getNotification(). We are not sending data here. – Brijesh Kumar May 03 '18 at 04:33
  • Thank you for your quick response and this did resolve the issue :) – Tabish May 03 '18 at 04:37
  • How to set the icon of the notification as an URL and not a drawable? @brijeshkumar – Curio Sep 13 '20 at 17:32
4

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'

enter image description here

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();

ansonk163
  • 67
  • 3
Rjz Satvara
  • 3,175
  • 2
  • 18
  • 41
3

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

Maxim Firsoff
  • 1,134
  • 12
  • 21
  • 2
    Wouldn't this still require use of the Authorization key on the client side? Which makes it insecure. Also I don't even know if creating a separate topic for each user is a good idea. – Suyash Sep 06 '16 at 08:34
  • Good idea, but: Topic messages are optimized for throughput rather than latency. For fast, secure delivery to single devices or small groups of devices, target messages to registration tokens, not topics. – Michalsx Feb 16 '17 at 19:49
  • @Maxim Firsoff- How to create a topic from FCM console or any other way ? – Ajay Sharma Jul 12 '17 at 09:29
  • @AjaySharma as I remember, FMC console doesn't have tools for it, you can make a topic programmatically (see above my pseudo code). – Maxim Firsoff Jul 12 '17 at 13:48
3

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:

  1. You have to use the same Google account on the devices
  2. You can't send high priority messages

Reference: Firebase doc See the section "Managing device groups on Android client apps"

greywolf82
  • 20,269
  • 17
  • 45
  • 90
2

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 ;


    });
1

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

Prashanth Debbadwar
  • 1,031
  • 17
  • 28
  • 1
    Yes, but this still requires our own external server right? Cause we shouldn't embed the API_KEY directly in our clients. My question was whether this is possible without an external server, which currently isn't as suggested by the other replies. – Suyash Oct 16 '16 at 07:50
1

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

}
ansonk163
  • 67
  • 3
eurosecom
  • 2,584
  • 3
  • 22
  • 37
1

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:

  1. The function triggers on writes to the Realtime Database path where followers are stored.

  2. The function composes a message to send via FCM.

  3. 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.

Crashalot
  • 31,452
  • 56
  • 235
  • 393
1

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);
}
ansonk163
  • 67
  • 3
Cabezas
  • 7,330
  • 6
  • 59
  • 63
0

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

DS.
  • 2,606
  • 4
  • 27
  • 34
  • 3
    The user won't be using the app all the time and we can't use firebase realtime database in the background since it keeps a persistent socket connection to the server which is too heavy on the battery of the device. – Suyash Jun 06 '16 at 12:21
  • I am able to send Firebase messages between devices and notifications using Smack Library. I don't implement any external server in My android code. Smack manages the connection and the incoming/outgoing of message stanzas using the XMPP protocol. – i_o Sep 13 '16 at 16:04
0

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?

  • 2
    FCM already has an endpoint, see [here](https://firebase.google.com/docs/cloud-messaging/server#auth). But it's not possible to use it directly in our clients since it requires the server api key. And even if it was publicly accesible it would cause security issues since any user would be able to send any FCM message to anyone. – Suyash Jun 11 '16 at 16:20
-3

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;
}
}
The Hungry Dictator
  • 3,247
  • 4
  • 34
  • 47
  • The Const.FIREBASE_LEGACY_SERVER_KEY is not safe to be used in client side code. Please at least read the other replies and comments before posting. – Suyash Jun 21 '17 at 12:17