Managing Firebase Messaging Device Groups from an Android App
Previous | Table of Contents | Next |
Managing Firebase Cloud Messaging Device Groups with Node.js | Firebase Cloud Upstream Messaging |
Having covered server-side creation and management of device groups in the previous chapter, the next step is to explain how to perform the same operations from within a client app. Many of the concepts are largely the same in that the operations are performed using HTTP POST requests, though there are some additional steps and restrictions that need to be taken into consideration when taking this approach to device group management.
Managing Device Groups from a Client App
Working with Firebase device groups from within a client app is primarily a case of constructing appropriately configured HTTP POST requests and submitting them to the Firebase cloud server. In common with server-side implementation, the client app also makes use of the sender ID when creating and managing a device group. Unlike the server, however, the client app uses an ID token instead of the server key when posting device group requests. Obtaining an ID token is a multistep process which will be outlined in this chapter.
A key limitation of client-side device group management is that the user must be authenticated with a Google account within the app before the ID token can be obtained and any device group requests are posted. A further limitation is that the only notification name key permitted for the device group is the email address associated with the Google account.
Generating the Client
The first step toward getting the ID token is to create a client ID for the Firebase project in the Google Developer Console. To access the console, open a browser window and navigate to the following URL: https://console.cloud.google.com
Sign into the console if necessary and, using the drop-down menu highlighted in Figure 29-1, make sure that Firebase Examples is the currently selected project. With the project loaded into the console, select the API Manager -> Credentials option from the left-hand navigation panel (also highlighted in Figure 29-1):
Figure 29-1
On the resulting credentials page, click on the Create credentials button and select the OAuth client ID option:
Figure 29-2
On the next page, select the Web application option and name the credentials “Firebase Device Group Example”. Although this ID is being used in an Android app, the web application option is selected here because the requests are still made using HTTP posts. Leave the remaining fields unchanged and click on the Create button.
A dialog will appear containing both the client ID and secret. This client ID may be copied now or located later within the list of OAuth Client IDs within the Credentials page of the developer console.
Creating the Client App Project
As already mentioned, the app must be authenticated with a Google account before any device group actions can be performed. Rather that repeat steps already taken in earlier chapters, this example will repurpose the GoogleAuth project created in the chapter entitled Google Authentication using the Firebase SDK. Locate this project on your filesystem if you have already completed the chapter, or download a copy along with the other book code examples from the following URL:
http://www.ebookfrenzy.com/web/firebase_android
Make a copy of the GoogleAuth project folder, name it GoogleAuth_FCM and then open the renamed project in Android Studio.
Adding the Group Button
Load the activity_google_auth.xml file into the layout editor, enable Autoconnect mode and add a new button view to the layout so that it is positioned against the bottom and left-hand margin lines as illustrated in Figure 29-3. Change the text on the Button to read “Create Group” and assign an onClick handler method named createGroup():
Figure 29-3
Obtaining the ID Token
Code now needs to be added to the main activity to obtain the ID token associated with the user’s Google account. To obtain the ID token, the client ID generated above needs to be referenced when building the GoogleSignInOptions object that will be used during the authentication process. Within the GoogleAuthActivity.java file, locate the following code in the onCreate() method:
GoogleSignInOptions signInOptions = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN) .requestIdToken(getString(R.string.default_web_client_id)) .requestEmail() .requestProfile() .build();
Modify the requestIdToken() method call to pass through your client ID instead of the default web client ID used previously (note also the removal of the getString() method call):
.requestIdToken("<your project client id goes here>")
When the user successfully authenticates, the ID token will be available within the resulting Account object which is available within the onActivityResult() method. The Account object will also contain the email address for the user’s Google account which must be used as the notification key name when creating the device group.
Add two new variables to the class and modify the onActivityResult() method to extract both the email address and ID token. Also import the Log package and declare a tag string so that diagnostic output can be included for testing purposes:
. . import android.util.Log; . . private static final String TAG = "GoogleAuth"; private String idToken = null; private String userEmail = null; . . @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == RC_SIGN_IN) { GoogleSignInResult result = Auth.GoogleSignInApi.getSignInResultFromIntent(data); if (result.isSuccess()) { GoogleSignInAccount account = result.getSignInAccount(); idToken = account.getIdToken(); userEmail = account.getEmail(); Log.i(TAG, "Token = " + idToken); Log.i(TAG, "Email = " + userEmail); authWithFirebase(account); } else { this.notifyUser("onActivityResult: failed"); } } }
Compile and run the app and sign in using a Google account. Refer to the logcat output in the Android Studio Android Monitor tool window and verify that both the idToken and email address values have been displayed. If the idToken variable is set to null, double check that the client ID was generated for the correct Firebase project and that it has been entered correctly into the code.
Tap the Sign Out button to sign out of the app before continuing.
Declaring the Sender ID
When the request is sent to the Google cloud messaging server, it will need to include the sender ID for the project. As outlined in the previous chapter, the sender ID for the project is available within the Firebase console. Within the console, select the Firebase Examples project, click on the gear icon next to the Overview heading and select the Project settings menu option.
On the Settings screen, select the Cloud Messaging tab and locate the Sender ID field in the Project credentials section. Copy the ID and include it when adding the following variable declaration beneath the userEmail variable added in the previous section:
private static final String sender_id = "<your sender id here>";
Creating an Asynchronous Task
The HTTP POST request to create the device group will be performed using the HttpURLConnection class. Since the amount of time required to complete this operation will depend on the speed of the internet connection of the device, it is generally recommended that such actions be performed outside of the main thread of the application. Running the operation on a separate thread will avoid the app locking up while awaiting a response from the server.
For this example, AsyncTask will be used to asynchronously communicate with the cloud messaging server. Within the GoogleAuthActivity.java file, begin the initial implementation of the AsyncTask class as follows:
public class CreateDeviceGroup extends AsyncTask<String, Void, String> { @Override protected String doInBackground(String... params) { return "Failed to create device group"; } @Override protected void onPostExecute(String result) { Log.i(TAG, "Notification key: " + result); } }
The doInBackground() method contains the code that will be executed asynchronously from the main thread and is configured to accept an array of String objects assigned to a variable named params. The onPostExecute() method, on the other hand, is called when the doInBackground() method returns and outputs the result (either the failure message or the notification key assigned to the new device group) to the logcat panel.
Creating the Device Group
The work to create the device group will be performed asynchronously by the doInBackground() method of the CreateDeviceGroup AsyncTask class. Implement the code within this method now as follows:
. . import org.json.JSONArray; import org.json.JSONObject; import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; import java.util.Arrays; import java.util.Scanner; . . public class CreateDeviceGroup extends AsyncTask<String, Void, String> { @Override protected String doInBackground(String... params) { try { URL url = new URL("https://android.googleapis.com/gcm/googlenotification"); HttpURLConnection con = (HttpURLConnection) url.openConnection(); con.setDoOutput(true); con.setRequestProperty("project_id", sender_id); con.setRequestProperty("Content-Type", "application/json"); con.setRequestProperty("Accept", "application/json"); con.setRequestMethod("POST"); con.connect(); JSONObject data = new JSONObject(); data.put("operation", "add"); data.put("notification_key_name", userEmail); data.put("registration_ids", new JSONArray(Arrays.asList(params))); data.put("id_token", idToken); OutputStream os = con.getOutputStream(); os.write(data.toString().getBytes("UTF-8")); os.close(); InputStream is = con.getInputStream(); String responseString = new Scanner(is, "UTF-8").useDelimiter("\\A").next(); is.close(); JSONObject response = new JSONObject(responseString); return response.getString("notification_key"); } catch (Exception e) { Log.i(TAG, "FAILED " + e); } return "Failed to create device group"; }
Before moving on to the next step, it is worth taking time to explain what is happening in the above method. First, much of the code in the method has the potential to throw an exception, hence the enclosure of the code in a try/catch structure:
try { . . } catch (Exception e) { Log.i(TAG, "FAILED " + e); }
The first task performed configures an HttpURLConnection object for connection to the Google cloud messaging server:
URL url = new URL("https://android.googleapis.com/gcm/googlenotification"); HttpURLConnection con = (HttpURLConnection) url.openConnection(); con.setDoOutput(true);
Once the connection has been configured, the header for the HTTP request is constructed. This consists of the sender ID, the content type (JSON) and a declaration that this is a POST request. The connection to the server is then established via a call to the connect() method of the HttpURLConnection object:
con.setRequestProperty("project_id", sender_id); con.setRequestProperty("Content-Type", "application/json"); con.setRequestProperty("Accept", "application/json"); con.setRequestMethod("POST"); con.connect();
Next, the HTTP request JSON data is constructed as a JSON object consisting of key/value pairs using the format outlined in the previous chapter, including the add operation value, the notification key name (in this case the user’s email address), and the array of device registration tokens that will be passed to the task when it is executed:
JSONObject data = new JSONObject(); data.put("operation", "add"); data.put("notification_key_name", userEmail); data.put("registration_ids", new JSONArray(Arrays.asList(params))); data.put("id_token", idToken);
After the JSON object has been initialized, the code obtains a reference to the output stream of the HttpURLConnection object and writes the JSON object to it before closing the stream:
OutputStream os = con.getOutputStream(); os.write(data.toString().getBytes("UTF-8")); os.close();
After closing the output stream, the input stream of the HTTP connection is opened and used to read the response string from the Google cloud server. The response is converted into a JSON object, from which the value assigned to the notification key is extracted and returned:
InputStream is = con.getInputStream(); String responseString = new Scanner(is, "UTF-8").useDelimiter("\\A").next(); is.close(); JSONObject response = new JSONObject(responseString); return response.getString("notification_key");
When the method returns, the onPostExecute() method is called and passed the response string.
Executing the AsyncTask
All that remains is to create an instance of the AsyncTask and execute it, passing through an array containing the registration tokens of the devices to be included in the group.
When the user interface layout was modified earlier in the chapter, the Create Group button was configured to call a method named createGroup() when clicked. Edit the GoogleAuthActivity.java file and add this method, substituting registration tokens where appropriate (for convenience, consider using those from the previous chapter since these devices are already configured to run the example Messaging app):
public void createGroup(View view) { String token1 = "<registration token one here>"; String token2 = "<registration token two here>"; CreateDeviceGroup task = new CreateDeviceGroup(); task.execute(new String[] { token1, token2 }); }
Testing the App
Compile and run the app and, if the user is already signed into a Google account, sign out and then back in again (as implemented in this example, the ID token is only obtained during the sign-in process). Check the logcat output to ensure that the userEmail and idToken variables are initialized, then click on the Create Group button. If the group has been successfully created, the notification key will appear in the logcat output.
Copy the notification key, edit the send.js file created in the previous chapter and replace the previous key with the new one.
Make sure that the example Messaging app is running and placed in the background on the two devices from which the registration tokens were taken and then run the send.js script as follows:
node send.js
Once the message has been sent, it should appear within the status bar of both devices, the Node.js output should read as follows:
Successfully sent message: { successCount: 2, failureCount: 0, failedRegistrationTokens: [] }
Edit the remove.js file, and replace the existing notification key with the new key before running the script to remove the device group from the server.
Removing Devices from a Device Group
If devices need to be removed from a device group from the client app, an HTTP Post request similar to that created for creating a device group may be used. The only section of the doInBackground() method that would need to be changed is the code responsible for constructing the JSON object. In order to perform the deletion, the request must contain the remove operation value, the notification key name (in other words the user’s email address), the registration tokens of the devices to be removed and the ID token:
JSONObject data = new JSONObject(); data.put("operation", "remove"); data.put("notification_key_name", userEmail); data.put("registration_ids", new JSONArray(Arrays.asList(params))); data.put("id_token", idToken);
Summary
In addition to creating and managing device groups from the server, many of these tasks may also be performed from the client app. As with the server, the client app uses HTTP POST requests to perform device group operations such as creating a group and adding and removing devices. Device group management features are only available within apps where the user has authenticated using a Google account. This account is used to obtain an ID token which is used to authenticate the client when sending the HTTP requests. Unlike server side device group management, the notification key name can only be set to the email address associated with the user’s Google account.
Previous | Table of Contents | Next |
Managing Firebase Cloud Messaging Device Groups with Node.js | Firebase Cloud Upstream Messaging |