Proposed Pull Request Change

title description services author manager ms.service ms.tgt_pltfrm ms.devlang ms.topic ms.custom ms.date ms.author ms.reviewer ms.lastreviewed
Send push notifications to specific Android apps using Azure Notification Hubs Learn how to send push notifications to specific Android apps by using Azure Notification Hubs. notification-hubs sethmanheim lizross azure-notification-hubs mobile-android java tutorial mvc, devx-track-java, devx-track-dotnet 02/06/2024 sethm heathertian 02/06/2024
📄 Document Links
GitHub View on GitHub Microsoft Learn View on Microsoft Learn
Raw New Markdown
Generating updated version of doc...
Rendered New Markdown
Generating updated version of doc...
+0 -0
+0 -0
--- title: Send push notifications to specific Android apps using Azure Notification Hubs description: Learn how to send push notifications to specific Android apps by using Azure Notification Hubs. services: notification-hubs author: sethmanheim manager: lizross ms.service: azure-notification-hubs ms.tgt_pltfrm: mobile-android ms.devlang: java ms.topic: tutorial ms.custom: mvc, devx-track-java, devx-track-dotnet ms.date: 02/06/2024 ms.author: sethm ms.reviewer: heathertian ms.lastreviewed: 02/06/2024 --- # Tutorial: Send push notifications to specific Android apps using Azure Notification Hubs [!INCLUDE [notification-hubs-selector-aspnet-backend-notify-users](../../includes/notification-hubs-selector-aspnet-backend-notify-users.md)] > [!NOTE] > For information about Firebase Cloud Messaging deprecation and migration steps, see [Google Firebase Cloud Messaging migration](notification-hubs-gcm-to-fcm.md). This tutorial shows you how to use Azure Notification Hubs to send push notifications to a specific app user on a specific device. An ASP.NET WebAPI backend is used to authenticate clients and to generate notifications, as shown in the guidance article [Registering from your app backend](notification-hubs-push-notification-registration-management.md#registration-management-from-a-backend). This tutorial builds on the notification hub that you created in the [Tutorial: Push notifications to Android devices by using Azure Notification Hubs and Firebase Cloud Messaging](notification-hubs-android-push-notification-google-fcm-get-started.md). In this tutorial, you take the following steps: > [!div class="checklist"] > * Create the backend Web API project that authenticates users. > * Update the Android application. > * Test the app ## Prerequisites Complete the [Tutorial: Push notifications to Android devices by using Azure Notification Hubs and Firebase Cloud Messaging](notification-hubs-android-push-notification-google-fcm-get-started.md) before doing this tutorial. [!INCLUDE [notification-hubs-aspnet-backend-notifyusers](../../includes/notification-hubs-aspnet-backend-notifyusers.md)] ## Create the Android Project The next step is to update the Android application created in the [Tutorial: Push notifications to Android devices by using Azure Notification Hubs and Firebase Cloud Messaging](notification-hubs-android-push-notification-google-fcm-get-started.md). 1. Open your `res/layout/activity_main.xml` file, replace the following content definitions: It adds new EditText controls for logging in as a user. Also a field is added for a username tag that will be part of notifications you send: ```xml <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <EditText android:id="@+id/usernameText" android:layout_width="match_parent" android:layout_height="wrap_content" android:ems="10" android:hint="@string/usernameHint" android:layout_above="@+id/passwordText" android:layout_alignParentEnd="true" /> <EditText android:id="@+id/passwordText" android:layout_width="match_parent" android:layout_height="wrap_content" android:ems="10" android:hint="@string/passwordHint" android:inputType="textPassword" android:layout_above="@+id/buttonLogin" android:layout_alignParentEnd="true" /> <Button android:id="@+id/buttonLogin" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/loginButton" android:onClick="login" android:layout_above="@+id/toggleButtonFCM" android:layout_centerHorizontal="true" android:layout_marginBottom="24dp" /> <ToggleButton android:layout_width="wrap_content" android:layout_height="wrap_content" android:textOn="WNS on" android:textOff="WNS off" android:id="@+id/toggleButtonWNS" android:layout_toLeftOf="@id/toggleButtonFCM" android:layout_centerVertical="true" /> <ToggleButton android:layout_width="wrap_content" android:layout_height="wrap_content" android:textOn="FCM on" android:textOff="FCM off" android:id="@+id/toggleButtonFCM" android:checked="true" android:layout_centerHorizontal="true" android:layout_centerVertical="true" /> <ToggleButton android:layout_width="wrap_content" android:layout_height="wrap_content" android:textOn="APNS on" android:textOff="APNS off" android:id="@+id/toggleButtonAPNS" android:layout_toRightOf="@id/toggleButtonFCM" android:layout_centerVertical="true" /> <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/editTextNotificationMessageTag" android:layout_below="@id/toggleButtonFCM" android:layout_centerHorizontal="true" android:hint="@string/notification_message_tag_hint" /> <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/editTextNotificationMessage" android:layout_below="@+id/editTextNotificationMessageTag" android:layout_centerHorizontal="true" android:hint="@string/notification_message_hint" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/send_button" android:id="@+id/sendbutton" android:onClick="sendNotificationButtonOnClick" android:layout_below="@+id/editTextNotificationMessage" android:layout_centerHorizontal="true" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" android:id="@+id/text_hello" /> </RelativeLayout> ``` 2. Open your `res/values/strings.xml` file and replace the `send_button` definition with the following lines that redefine the string for the `send_button` and add strings for the other controls: ```xml <string name="usernameHint">Username</string> <string name="passwordHint">Password</string> <string name="loginButton">1. Sign in</string> <string name="send_button">2. Send Notification</string> <string name="notification_message_hint">Notification message</string> <string name="notification_message_tag_hint">Recipient username</string> ``` Your `main_activity.xml` graphical layout should now look like the following image: ![Screenshot of an emulator displaying what the main activity X M L graphical layout will look like.][A1] 3. Create a new class named `RegisterClient` in the same package as your `MainActivity` class. Use the code below for the new class file. ```java import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.Set; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.util.EntityUtils; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import android.content.Context; import android.content.SharedPreferences; import android.util.Log; public class RegisterClient { private static final String PREFS_NAME = "ANHSettings"; private static final String REGID_SETTING_NAME = "ANHRegistrationId"; private String Backend_Endpoint; SharedPreferences settings; protected HttpClient httpClient; private String authorizationHeader; public RegisterClient(Context context, String backendEndpoint) { super(); this.settings = context.getSharedPreferences(PREFS_NAME, 0); httpClient = new DefaultHttpClient(); Backend_Endpoint = backendEndpoint + "/api/register"; } public String getAuthorizationHeader() { return authorizationHeader; } public void setAuthorizationHeader(String authorizationHeader) { this.authorizationHeader = authorizationHeader; } public void register(String handle, Set<String> tags) throws ClientProtocolException, IOException, JSONException { String registrationId = retrieveRegistrationIdOrRequestNewOne(handle); JSONObject deviceInfo = new JSONObject(); deviceInfo.put("Platform", "fcm"); deviceInfo.put("Handle", handle); deviceInfo.put("Tags", new JSONArray(tags)); int statusCode = upsertRegistration(registrationId, deviceInfo); if (statusCode == HttpStatus.SC_OK) { return; } else if (statusCode == HttpStatus.SC_GONE){ settings.edit().remove(REGID_SETTING_NAME).commit(); registrationId = retrieveRegistrationIdOrRequestNewOne(handle); statusCode = upsertRegistration(registrationId, deviceInfo); if (statusCode != HttpStatus.SC_OK) { Log.e("RegisterClient", "Error upserting registration: " + statusCode); throw new RuntimeException("Error upserting registration"); } } else { Log.e("RegisterClient", "Error upserting registration: " + statusCode); throw new RuntimeException("Error upserting registration"); } } private int upsertRegistration(String registrationId, JSONObject deviceInfo) throws UnsupportedEncodingException, IOException, ClientProtocolException { HttpPut request = new HttpPut(Backend_Endpoint+"/"+registrationId); request.setEntity(new StringEntity(deviceInfo.toString())); request.addHeader("Authorization", "Basic "+authorizationHeader); request.addHeader("Content-Type", "application/json"); HttpResponse response = httpClient.execute(request); int statusCode = response.getStatusLine().getStatusCode(); return statusCode; } private String retrieveRegistrationIdOrRequestNewOne(String handle) throws ClientProtocolException, IOException { if (settings.contains(REGID_SETTING_NAME)) return settings.getString(REGID_SETTING_NAME, null); HttpUriRequest request = new HttpPost(Backend_Endpoint+"?handle="+handle); request.addHeader("Authorization", "Basic "+authorizationHeader); HttpResponse response = httpClient.execute(request); if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { Log.e("RegisterClient", "Error creating registrationId: " + response.getStatusLine().getStatusCode()); throw new RuntimeException("Error creating Notification Hubs registrationId"); } String registrationId = EntityUtils.toString(response.getEntity()); registrationId = registrationId.substring(1, registrationId.length()-1); settings.edit().putString(REGID_SETTING_NAME, registrationId).commit(); return registrationId; } } ``` This component implements the REST calls required to contact the app backend to register for push notifications. It also locally stores the *registrationIds* created by the Notification Hub as detailed in [Registering from your app backend](notification-hubs-push-notification-registration-management.md#registration-management-from-a-backend). It uses an authorization token stored in local storage when you click the **Sign in** button. 4. In your `MainActivity` class, and add a field for the `RegisterClient` class and a string for your ASP.NET backend's endpoint. Be sure to replace `<Enter Your Backend Endpoint>` with your actual backend endpoint obtained previously. For example, `http://mybackend.azurewebsites.net`. ```java private RegisterClient registerClient; private static final String BACKEND_ENDPOINT = "<Enter Your Backend Endpoint>"; FirebaseInstanceId fcm; String FCM_token = null; ``` 5. In your `MainActivity` class, in the `onCreate` method, remove, or comment out the initialization of the `hub` field and the call to the `registerWithNotificationHubs` method. Then add code to initialize an instance of the `RegisterClient` class. The method should contain the following lines: ```java @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mainActivity = this; FirebaseService.createChannelAndHandleNotifications(getApplicationContext()); fcm = FirebaseInstanceId.getInstance(); registerClient = new RegisterClient(this, BACKEND_ENDPOINT); setContentView(R.layout.activity_main); } ``` 7. Add the following `import` statements to your `MainActivity.java` file. ```java import android.util.Base64; import android.view.View; import android.widget.EditText; import android.widget.Button; import android.widget.ToggleButton; import java.io.UnsupportedEncodingException; import android.content.Context; import java.util.HashSet; import android.widget.Toast; import org.apache.http.client.ClientProtocolException; import java.io.IOException; import org.apache.http.HttpStatus; import android.os.AsyncTask; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.DefaultHttpClient; import android.app.AlertDialog; import android.content.DialogInterface; import com.google.firebase.iid.FirebaseInstanceId; import com.google.firebase.iid.InstanceIdResult; import com.google.android.gms.tasks.OnSuccessListener; import java.util.concurrent.TimeUnit; ``` 8. Replace code on the onStart method with the following code: ```java super.onStart(); Button sendPush = (Button) findViewById(R.id.sendbutton); sendPush.setEnabled(false); ``` 9. Then, add the following methods to handle the **Sign in** button click event and sending push notifications. ```java public void login(View view) throws UnsupportedEncodingException { this.registerClient.setAuthorizationHeader(getAuthorizationHeader()); final Context context = this; new AsyncTask<Object, Object, Object>() { @Override protected Object doInBackground(Object... params) { try { FirebaseInstanceId.getInstance().getInstanceId().addOnSuccessListener(new OnSuccessListener<InstanceIdResult>() { @Override public void onSuccess(InstanceIdResult instanceIdResult) { FCM_token = instanceIdResult.getToken(); Log.d(TAG, "FCM Registration Token: " + FCM_token); } }); TimeUnit.SECONDS.sleep(1); registerClient.register(FCM_token, new HashSet<String>()); } catch (Exception e) { DialogNotify("MainActivity - Failed to register", e.getMessage()); return e; } return null; } protected void onPostExecute(Object result) { Button sendPush = (Button) findViewById(R.id.sendbutton); sendPush.setEnabled(true); Toast.makeText(context, "Signed in and registered.", Toast.LENGTH_LONG).show(); } }.execute(null, null, null); } private String getAuthorizationHeader() throws UnsupportedEncodingException { EditText username = (EditText) findViewById(R.id.usernameText); EditText password = (EditText) findViewById(R.id.passwordText); String basicAuthHeader = username.getText().toString()+":"+password.getText().toString(); basicAuthHeader = Base64.encodeToString(basicAuthHeader.getBytes("UTF-8"), Base64.NO_WRAP); return basicAuthHeader; } /** * This method calls the ASP.NET WebAPI backend to send the notification message * to the platform notification service based on the pns parameter. * * @param pns The platform notification service to send the notification message to. Must * be one of the following ("wns", "fcm", "apns"). * @param userTag The tag for the user who will receive the notification message. This string * must not contain spaces or special characters. * @param message The notification message string. This string must include the double quotes * to be used as JSON content. */ public void sendPush(final String pns, final String userTag, final String message) throws ClientProtocolException, IOException { new AsyncTask<Object, Object, Object>() { @Override protected Object doInBackground(Object... params) { try { String uri = BACKEND_ENDPOINT + "/api/notifications"; uri += "?pns=" + pns; uri += "&to_tag=" + userTag; HttpPost request = new HttpPost(uri); request.addHeader("Authorization", "Basic "+ getAuthorizationHeader()); request.setEntity(new StringEntity(message)); request.addHeader("Content-Type", "application/json"); HttpResponse response = new DefaultHttpClient().execute(request); if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { DialogNotify("MainActivity - Error sending " + pns + " notification", response.getStatusLine().toString()); throw new RuntimeException("Error sending notification"); } } catch (Exception e) { DialogNotify("MainActivity - Failed to send " + pns + " notification ", e.getMessage()); return e; } return null; } }.execute(null, null, null); } ``` The `login` handler for the **Sign in** button generates a basic authentication token using on the input username and password (it represents any token your authentication scheme uses), then it uses `RegisterClient` to call the backend for registration. The `sendPush` method calls the backend to trigger a secure notification to the user based on the user tag. The platform notification service that `sendPush` targets depends on the `pns` string passed in. 10. Add the following `DialogNotify` method to the `MainActivity` class. ```java protected void DialogNotify(String title, String message) { AlertDialog alertDialog = new AlertDialog.Builder(MainActivity.this).create(); alertDialog.setTitle(title); alertDialog.setMessage(message); alertDialog.setButton(AlertDialog.BUTTON_NEUTRAL, "OK", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }); alertDialog.show(); } ``` 11. In your `MainActivity` class, update the `sendNotificationButtonOnClick` method to call the `sendPush` method with the user's selected platform notification services as follows. ```java /** * Send Notification button click handler. This method sends the push notification * message to each platform selected. * * @param v The view */ public void sendNotificationButtonOnClick(View v) throws ClientProtocolException, IOException { String nhMessageTag = ((EditText) findViewById(R.id.editTextNotificationMessageTag)) .getText().toString(); String nhMessage = ((EditText) findViewById(R.id.editTextNotificationMessage)) .getText().toString(); // JSON String nhMessage = "\"" + nhMessage + "\""; if (((ToggleButton)findViewById(R.id.toggleButtonWNS)).isChecked()) { sendPush("wns", nhMessageTag, nhMessage); } if (((ToggleButton)findViewById(R.id.toggleButtonFCM)).isChecked()) { sendPush("fcm", nhMessageTag, nhMessage); } if (((ToggleButton)findViewById(R.id.toggleButtonAPNS)).isChecked()) { sendPush("apns", nhMessageTag, nhMessage); } } ``` 12. In the `build.gradle` file, add the following line to the `android` section after the `buildTypes` section. ```java useLibrary 'org.apache.http.legacy' ``` 13. If your app is targeting API level 28 (Android 9.0) or above, include the following declaration within the `<application>` element of `AndroidManifest.xml`. ```xml <uses-library android:name="org.apache.http.legacy" android:required="false" /> ``` 14. Build the project. ## Test the app 1. Run the application on a device or an emulator using Android Studio. 2. In the Android app, enter a username and password. They must both be the same string value and they must not contain spaces or special characters. 3. In the Android app, click **Sign in**. Wait for a toast message that states **Signed in and registered**. It enables the **Send Notification** button. ![Screenshot of an emulator showing what the Notification Hubs Notify Users app looks like after logging in.][A2] 4. Click the toggle buttons to enable all platforms where you ran the app and registered a user. 5. Enter the user's name that receives the notification message. That user must be registered for notifications on the target devices. 6. Enter a message for the user to receive as a push notification message. 7. Click **Send Notification**. Each device that has a registration with the matching username tag receives the push notification. ## Next steps In this tutorial, you learned how to push notifications to specific users that have tags associated with their registrations. To learn how to push location-based notifications, advance to the following tutorial: > [!div class="nextstepaction"] >[Push location-based notifications](notification-hubs-push-bing-spatial-data-geofencing-notification.md) [A1]: ./media/notification-hubs-aspnet-backend-android-notify-users/android-notify-users.png [A2]: ./media/notification-hubs-aspnet-backend-android-notify-users/android-notify-users-enter-password.png
Success! Branch created successfully. Create Pull Request on GitHub
Error: