Subscribe Now

* You will receive the latest news and updates on your favorite celebrities!

Trending News

Blog Post

Building a custom Cloud Messaging API for Android
Android, Tutorials

Building a custom Cloud Messaging API for Android 

Firebase Cloud Messaging, previously Google Cloud Messaging is by far the most reliable solution for cloud messaging applications. Even though FCM is completely free to use, the data we send are passed to google servers which is not a good idea if we are trying to make a highly secure application. For this reason, I am trying to build an open source Cloud SDK for android incorporating various features google offers through Firebase like FCM, Realtime Database, Remote Config, etc.

Note: This articles is for those who want to build their on cloud messaging service like firebase. For that, you are gonna need a dedicated system to host the server SDK with Node.js environment.

Pre-requisites

  1. Android development environment.
  2. Node.js development environment.
  3. A dedicated machine to host Server. (not necessarily a PC, raspberry Pi works just fine)
  4. and some free time…..😃

What is Cloud Messaging?

Cloud messaging is just another term for sending real-time data from servers to device. HTTP protocol is not a good choice for this type of connection as the server cannot send data dynamically without being requested for it by the client. So, we will be using Web-Sockets to implement real-time communication in our application. There is a whole lot of discussion when to use HTTP and when to use Web-sockets available in the internet. In short, HTTP is like you ordering food from a restaurant. The restaurant will only fulfill your order if you make the order. On the other hand, Web-Sockets is like your mom, whenever food is made in your home, she gives it to you even if you didn’t ask for it.

This SDK is written in Java ( my favorite language ), and Web-Sockets will be implemented via a library called Socket.io. You don’t need any professional level programming expertise to work with this library as it is tailored to ease the efforts of hobbyists and DIY enthusiasts.

Dependencies

Before we begin with the actual coding part, we need to include some dependencies in our project.

    //Socket.io
    implementation ('io.socket:socket.io-client:2.0.0') {
        exclude group: 'org.json', module: 'json'
    }

    //GSON
    implementation 'com.google.code.gson:gson:2.8.6'

    //Notification Provider Library
    implementation 'com.github.JerrySJoseph:NotificationProvider:v1.0.2-stable'

Device Identification

As the cloud messaging is device specific, a unique device id for every device is a good option. In order to create a device specific ID, we can use ANDROID_ID which is different for every android device.

public class Client {
    //If implementing authentication, auth-token might be useful
    String authToken;
    //unique device ID
    String clientID;

    public Client(){
    }

    public Client(String authToken, String clientID) {
        this.authToken = authToken;
        this.clientID = clientID;
    }

    public String getAuthToken() {
        return authToken;
    }

    public void setAuthToken(String authToken) {
        this.authToken = authToken;
    }

    public String getClientID() {
        return clientID;
    }

    public void setClientID(String clientID) {
        this.clientID = clientID;
    }

    public static Client CreateClientForThisDevice(Context context, String authToken)
    {
        String uniqueDeviceID= Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);
        return new Client(authToken,uniqueDeviceID);
    }

    public String toJSONString()
    {
        return new Gson().toJson(this);
    }

    public JSONObject toJSONObject()
    {
        JSONObject jsonObject = null;

        try {
            jsonObject=new JSONObject();
            jsonObject.put("authToken",authToken);
            jsonObject.put("clientID",clientID);
        } catch (JSONException e) {
            e.printStackTrace();
        }

        return jsonObject;

    }

}

A good option to spawn Client objects dynamically when necessary is to create a factory interface.

public interface ClientFactory {
    Client newClient();
}

Connecting to Server

The first step to implement real-time communication is to establish a socket connection. For that, we will be using an amazing library called socket.io which is available for both android and Node.js.

 //Creating an instance of socket with server URL
 Socket mSocket= IO.socket(SERVER_URL);
 //Connecting to socket
 mSocket.connect();

Client Handshake

After connecting to server via socket, we need to perform a handshake to exchange client details. If the handshake was successful, we receive an acknowledgement, and we register further event listeners.

//Client handshake for registering client in server
    private void performClientHandshake()
    {
        Client me=mClientFactory.newClient();
        mSocket.emit("client-handshake", me.toJSONString(), new Ack() {
            @Override
            public void call(Object... args) {

                //Handshake successfull. Register all push and other event listener for communication
                Log.e(TAG,(String)args[0]);

                //Change the event name for custom events
                mSocket.on("cloud-event-other",onEventListener);

                //Event listenere for push notification
                mSocket.on("cloud-event-push",onPushNotificationListener);
            }
        });
    }

We can also, register some predefined events to catch errors and responses like this,

 mSocket.on(Socket.EVENT_CONNECT,onConnectListener);
 mSocket.on(Socket.EVENT_DISCONNECT,onDisconnectListener);
 mSocket.on(Socket.EVENT_CONNECT_ERROR,onConnectErrorListener);

Here is the complete code of JSCloudMessagingService.

public abstract class JSCloudMessagingService extends Service
{
    static Socket mSocket=null;
    static String SERVER_URL=null;
    static String TAG="Socket Service";

    //Client factory
    private ClientFactory mClientFactory= new ClientFactory() {
        @Override
        public Client newClient() {
            return Client.CreateClientForThisDevice(getApplicationContext(),"my-auth-token");
        }
    };

    //Notification factory
    private NotificationFactory mNotificationFactory= new NotificationFactory(){
        @Override
        public Notification newNotification() {

            return new NotificationProvider(getApplicationContext())
                    .setTitle(getString(R.string.sDefaultNotifTitle))
                    .setBody(getString(R.string.sDefaultNotifContent))
                    .setChannelID(getString(R.string.sDefaultNotifChannel))
                    .setChannelName(getString(R.string.sDefaultNotifChannel))
                    .setNotificationIcon(R.drawable.ic_iot)
                    .setImportance(NotificationManager.IMPORTANCE_HIGH)
                    .getNotificationInstance();
        }
    };

    @Override
    public void onCreate() {
        super.onCreate();
        if(SERVER_URL==null || SERVER_URL.isEmpty())
            SERVER_URL=getServerUrl();

        try {
            if(mSocket==null)
                mSocket= IO.socket(SERVER_URL);
            connectAndRegisterEvents();
        } catch (URISyntaxException e) {
            e.printStackTrace();
        }

        Log.e(TAG,"Service - created");
        Toast.makeText(getApplicationContext(),"Service created",Toast.LENGTH_SHORT).show();
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) { return null; }

    //override this method to return server URL
    public abstract String getServerUrl();

    //Called when new custom message recieved from server
    public abstract void onNewMessage(Object... args);

    //called when new push messaged recieved from server
    public abstract void onNewPush(Object... args);

    //set custom foreground Notification (compulsory)
    public abstract Notification getNotification();

    //called when any exception occurs
    public abstract void onCloudException(JSCloudServerException exception);

    //optional overrides
    public void onCloudDisconnect(){ }


    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        startForeground(101,getNotification());
        Log.e(TAG,"Service -on start");
        return START_STICKY;
    }

    //returns default notification object
    public Notification getDefaultNotification()
    {
        return mNotificationFactory.newNotification();
    }

    //Register socket Events
    private void connectAndRegisterEvents(){
        try{
            mSocket.on(Socket.EVENT_CONNECT,onConnectListener);
            mSocket.on(Socket.EVENT_DISCONNECT,onDisconnectListener);
            mSocket.on(Socket.EVENT_CONNECT_ERROR,onConnectErrorListener);
            mSocket.connect();
        }catch (JSCloudServerException e)
        {
            onCloudException(e);
        }

    }

    //Fired on successful connection
    Emitter.Listener onConnectListener= new Emitter.Listener() {
        @Override
        public void call(Object... args) {
            Log.e(TAG,"Connected to server. Your socket id is "+ mSocket.id());
            performClientHandshake();
        }
    };

    //Fired when disconnected from server
    Emitter.Listener onDisconnectListener= new Emitter.Listener() {
        @Override
        public void call(Object... args) {
            Log.e(TAG,"Disconnected from Server.");
            onCloudDisconnect();
        }
    };

    //Fired when error occurs while connecting
    Emitter.Listener onConnectErrorListener= new Emitter.Listener() {
        @Override
        public void call(Object... args) {
            Log.e(TAG,"Connection Error "+ mSocket.id());
            onCloudException(new JSCloudServerException((Exception)args[0]));
        }
    };

    //Fired when push notification recieved
    Emitter.Listener onPushNotificationListener= new Emitter.Listener() {
        @Override
        public void call(Object... args) {
            onNewPush(args);
        }
    };

    //Fired on other events
    Emitter.Listener onEventListener= new Emitter.Listener() {
        @Override
        public void call(Object... args) {
           onNewMessage(args);
        }
    };


    //Client handshake for registering client in server
    private void performClientHandshake()
    {
        Client me=mClientFactory.newClient();
        mSocket.emit("client-handshake", me.toJSONString(), new Ack() {
            @Override
            public void call(Object... args) {

                //Handshake successfull. Register all push and other event listener for communication
                Log.e(TAG,(String)args[0]);

                //Change the event name for custom events
                mSocket.on("cloud-event-other",onEventListener);

                //Event listenere for push notification
                mSocket.on("cloud-event-push",onPushNotificationListener);
            }
        });
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        mSocket.disconnect();
        Log.e(TAG,"Service -on destroy");
        Toast.makeText(getApplicationContext(),"Service destroyed",Toast.LENGTH_SHORT).show();
    }


}

How to use this?

Using this service is pretty much the same as using FCM service. Just create a service extending JSCloudMessagingService and customize it as you want. Here is a small example on how to do that.

public class MyMessagingService extends JSCloudMessagingService {

    @Override
    public void onNewMessage(Object... args) {
        showNotification((String)args[0],(String)args[1]);
    }

    @Override
    public void onNewPush(Object... args) {
        Log.e("SOCKET","New push");
        showNotification((String)args[0],(String)args[1]);
    }

    @Override
    public Notification getNotification() {
        return getDefaultNotification();
    }

    @Override
    public void onCloudException(JSCloudServerException exception) {
        new Handler(Looper.getMainLooper()).post(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(MyMessagingService.this,exception.getMessage(),Toast.LENGTH_SHORT).show();
            }
        });

    }

    @Override
    public String getServerUrl() {
        return "http://192.168.1.26:3000/"; //Change this with your server IP and port
    }

    private void showNotification(String title,String message)
    {
        new NotificationProvider(this)
                .setTitle(title)               //Required
                .setBody(message)      //Required
                .setAutocancel(true)
                .setNotificationIcon(R.mipmap.ic_launcher)
                .setChannelID("Channel_id")
                .setChannelName("Channel_name")
                .show();
    }
}

Add your custom service to the manifest file like this,

        <service
            android:name=".MyMessagingService"
            android:enabled="true" >
        </service>

Then all you have to do is start the service from any activity or fragment.

 public void startService() {
        Intent serviceIntent = new Intent(this, ConnService.class);
        ContextCompat.startForegroundService(this, serviceIntent);

    }
    public void stopService() {
        Intent serviceIntent = new Intent(this, ConnService.class);
        stopService(serviceIntent);
    }

Conclusion

Please note, this service is not optimized for performance nor battery life. It is not yet complete and is just a demonstration on how you can implement the same. After the latest background service policies in android, OS kills every background services if its associated UI is in background or killed. So, In order to maintain the connection in a background service, a foreground notification should always be active when ever the device is online. There are several hacks to run a never ending background service without a notification, but it is not recommended nor reliable.

In this article I have shown you how to implement the client (Android) side. The setup and implementation of server side will be covered in part 2 of this article.

Server : – Building Custom Cloud messaging API- The Server

Android SDK :- https://github.com/JerrySJoseph/JS-Cloud-SDK-Android

Server SDK :- https://www.npmjs.com/package/js-cloud-messaging-api

Thank you!

Related posts

Leave a Reply

Required fields are marked *