An Example Firebase Upstream Cloud Messaging Client App

PreviousTable of ContentsNext
A Firebase Cloud Messaging Upstream XMPP App ServerFirebase Cloud Storage



With the app server running and communicating with the Firebase CCS, the next step is to build a client Android app that will make use of upstream messaging and the features built into the app server.

The purpose of the app created in this chapter is to allow users to send instant messages to each other.

Creating the Project

Launch Android Studio and select the Start a new Android Studio project quick start option from the welcome screen.

Within the new project dialog, enter UpstreamDemo into the Application name field and your domain as the Company Domain setting before clicking on the Next button.

On the form factors screen, enable the Phone and Tablet option and set the minimum SDK to API 16: Android 4.1 (Jellybean). Proceed through the screens, requesting the creation of an Empty Activity named UpstreamDemoActivity with a corresponding layout named activity_upstream_demo.

Adding Firebase Messaging Support to the Project

Select the Tools -> Firebase menu option and click on the Cloud Messaging entry in the Firebase assistant panel. Once selected, click on the Set up Firebase Cloud Messaging link. In the next panel, connect the project to Firebase using the provided connection button and select the Firebase Examples project from the list of existing projects. Once the project has been connected, click on the Add FCM to your app button.

A dialog will appear listing the changes that will be made to the project build files to add cloud messaging support to the project. Review these changes before clicking on the Accept Changes button.


Designing the User Interface

Select the activity_upstream_demo.xml user interface layout file, load it into the layout editor and delete the default “Hello World” TextView object.

Turn off Autoconnect mode and, from the Text category of the palette, drag a Plain EditText object and drop it so that it is positioned near the top of the layout and centered horizontally. With the object selected, use the Properties panel to delete the default “Name” string from the text property, enter text which reads “Your username” into the hint property field and change the ID to usernameText. Drag and drop a Button widget so that it is positioned beneath the username EditText object. Change the text property to read “Register”, specify an onClick method of registerUser and change the ID property to registerButton.

Add two more EditText widgets and a Button so that they are centered horizontally and positioned beneath the Register button as illustrated in Figure 32-1 below:


Firebase fcm upstream app ui.png

Figure 32-1


Delete the “Name” text from both EditText views and change the hint properties to read “To username” and “Message text” respectively. Change the text on the button to read “Send” and configure an onClick method named sendMessage. Change the IDs of the two EditText widgets to recipientText and messageText.

Either shift-click on each widget, or click and drag on the layout canvas to select all six widgets. Right-click on the top widget and select the Center Horizontally menu option to add horizontal constraints to all of the widgets. Select all of the widgets once again, and repeat this step, this time selecting the Center Vertically menu option.

On completion of these steps, the layout should match that of Figure 32-2:


Firebase fcm upstream app ui complete.png

Figure 32-2

Obtaining the Registration Token

Revisiting the techniques covered in the chapter entitled Integrating Firebase Cloud Messaging Support to an Android App, add a new FirebaseInstanceIdService subclass to the project by right-clicking on the app -> java -> <package name> entry in the project tool window and selecting the New -> Java Class… menu option. In the New Java Class dialog, enter FirebaseIDService into the Class Name field and select the FirebaseInstanceIdService class as the superclass before clicking on the OK button.

Edit the newly created FirebaseIDService.java file and modify it so that it reads as follows:

.
.
import com.google.firebase.iid.FirebaseInstanceId;
import com.google.firebase.iid.FirebaseInstanceIdService;

import android.util.Log;

public class FirebaseIDService extends FirebaseInstanceIdService {

    private static final String TAG = "FirebaseIDService";

    @Override
    public void onTokenRefresh() {
        	
        String token = FirebaseInstanceId.getInstance().getToken();
        Log.d(TAG, "Registration Token: = " + token);
        
        sendRegistrationToServer(token);
    }

    private void sendRegistrationToServer(String token) {
        
    }
}

Now that the service has been added to the project, it needs to be declared within the project manifest file so that it will be triggered in the event of a change to the token. Within the project tool window, locate the AndroidManifest.xml file, load it into the editor and add the service entry:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.ebookfrenzy.messaging">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MessagingActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <service
            android:name=".FirebaseIDService">
            <intent-filter>
                <action 
		  android:name="com.google.firebase.INSTANCE_ID_EVENT"/>
            </intent-filter>
        </service>
    </application>
</manifest>

With the code and manifest changes completed, compile and run the app on a device or emulator and review the output in the logcat panel of the Android Monitor tool window to locate the registration token string. The output will resemble the following (though the token will, of course, be different):

Registration Token: = cxpKdykhQeY:APA91bEl3LnBTnEzaWTUoW7aGARytp2KeOMuE_lZ496msWhQ5EjRRH75WYFO4Fwq91Dp7_KGcK8B9mnwAJ2E2sF5QcoXTl5eQ7PCspfYkHSgWlEFQr91t2PxXMwgOyGdPQlFPa65dCIu

Handling Incoming Messages

Clearly, the app will need a FirebaseMessagingService service to handle downstream messages arriving from the app server. Add a new service to the project for this purpose by right-clicking on the app -> java -> <package name> entry in the project tool window and selecting the New -> Java Class… menu option. In the New Class dialog, enter FirebaseMsgService into the Class Name field and select the FirebaseMessagingService class as the superclass before clicking on the OK button.

Edit the newly created FirebaseMsgService.java file and modify it so that it reads as follows:

.
.
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.widget.Toast;

import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;

public class FirebaseMsgService extends FirebaseMessagingService {

    private static final String TAG = "FirebaseMsgService";

    public FirebaseMsgService() {
    }

    @Override
    public void onMessageReceived(RemoteMessage remoteMessage) {

        if (remoteMessage.getData().size() > 0) {
            final String message = remoteMessage.getData().get("message");
            showMessage(message);
        }
    }

    public void showMessage(final String message)
    {  
	final Context context = this;
        new Handler(Looper.getMainLooper()).post(new Runnable() {
	  @Override public void run() {  
		Toast toast = Toast.makeText(context, message, 
				Toast.LENGTH_LONG);
                toast.show();
          }
        });
    };
}

When the onMessageReceived() method is called, it is passed as an argument a RemoteMessage object containing the details of a Firebase cloud message. The only kind of message the client app expects to receive contains message text from another user. This value is contained within the data payload assigned to the “message” key. The code in the onMessageReceived() method verifies that the payload contains data, extracts the message text and then passes it to a method named showMessage() which displays the message text in a Toast popup. Since displaying a toast message is a UI operation and this service is not running on the main UI thread a reference to the main UI thread is obtained and the Toast message displayed using that thread.

The final task before testing the code is to add an entry within the AndroidManifest.xml file for the service:

<service android:name=".FirebaseMsgService">
    <intent-filter>
        <action android:name="com.google.firebase.MESSAGING_EVENT"/>
    </intent-filter>
</service>

Registering the User

When the user enters a username and clicks the Register button, an upstream message needs to be sent to the app server containing a key-value pair consisting of the REGISTER action and the username string. When the user interface was designed, the onClick method of the Register button was configured to call a method named registerUser(). Edit the UpstreamDemoActivity.java file to add a variable containing the Sender ID for your project, a method to generate a random string to act as the message ID and to implement the registerUser() method:

.
.
import android.view.View;
import android.widget.EditText;

import com.google.firebase.iid.FirebaseInstanceId;
import com.google.firebase.messaging.FirebaseMessaging;
import com.google.firebase.messaging.RemoteMessage;

import java.util.Random;

public class UpstreamDemoActivity extends AppCompatActivity {

    static final String SenderID = "<your sender id here>";
.
.
    public void registerUser(View view) {

        EditText username = (EditText) findViewById(R.id.usernameText);

        FirebaseMessaging fm = FirebaseMessaging.getInstance();
        fm.send(new RemoteMessage.Builder(SenderID + "@gcm.googleapis.com")
                .setMessageId(getRandomMessageId())
                .addData("action", "REGISTER")
                .addData("account", username.getText().toString())
                .build());
    }

    static Random random = new Random();

    public String getRandomMessageId() {
        return "m-" + Long.toString(random.nextLong());
    }
}

Sending the Upstream Message

The final task before testing can commence is to add the sendMessage() method to the UpstreamDemoActivity.java file. This method needs to extract the username of the recipient and the message text from the EditText views in the user interface layout and then include them in a message configured for the MESSAGE action type. Add this method now so that it reads as follows:

public void sendMessage(View view) {
    EditText recipient = (EditText) findViewById(R.id.recipientText);
    EditText message = (EditText) findViewById(R.id.messageText);

    FirebaseMessaging fm = FirebaseMessaging.getInstance();
    fm.send(new RemoteMessage.Builder(SenderID + "@gcm.googleapis.com")
            .setMessageId(getRandomMessageId())
            .addData("action", "MESSAGE")
            .addData("recipient", recipient.getText().toString())
            .addData("message", message.getText().toString())
            .build());
}

Testing the Upstream Project

Begin the testing phase by starting the fcm-app-server server and making sure than no errors are displayed in the Run tool window. Also refer to the Smack Debug window and make sure that the interpreted packets panel shows a successful authentication between the app server and the CCS.

Build and run the UpstreamDemo client so that it is running on two different devices (or a suitable combination of devices and emulator sessions).

With the Smack debug window and the Android Studio Run tool window for the fcm-app-server project visible, enter a user name and tap the Register button on one of the app instances. As soon as the button is clicked, both the debug and run tool windows should update to show the effect of the upstream message transmission.

Repeat the above step on the second instance of the app, this time entering a different user name before tapping the Register button.

Using a text editor, open the account.properties file located in the top level directory of the fcm-app-server project and verify that entries for both user names are present and associated with registration tokens.

All being well, select one of the app instances, enter the username registered on the other device into the recipient field and some text into the message field. Click on the Send button and verify that the message appears on the other device in the form of a Toast message. Check the Smack Debug window and review the packets that were sent and received during the message transaction to gain an understanding of the protocol.

Repeat this step, this time sending a message from the second app instance using the username associated with the first instance.

If the messages fail to arrive, check both the Smack Debug window and fcm-app-server Android Studio Run tool window for errors and exceptions. Also check that you have access to port 5236 through your firewall. Problems may also occur if you are behind a proxy server.

Summary

This chapter has worked through the implementation of upstream messaging from an Android client app using the CCS and the example app server described in the previous chapter. Once completed, the client app was able to register a user via the app server, associating a user name to a registration token. Once two or more users are registered, text messages can be send between devices.




PreviousTable of ContentsNext
A Firebase Cloud Messaging Upstream XMPP App ServerFirebase Cloud Storage