It is more than 2 weeks since I tried to configure and implement in my NET8 MAUI application the push notifications using Azure Notification Hubs for Android. Also, I paid the Azure support (not very useful), and I still can’t configure the hub for Windows and Android. So, I tried another plugin at this point but had to ignore the Windows notification (sigh!).
So, I show you everything I discovered without using external plugin but only what MAUI offers and HttpClient
. I split this topic in a few posts:
- MAUI Push Notifications using Azure Notification Hub for Windows
- MAUI Push Notifications using Azure Notification Hub for Android
- MAUI Push Notifications using Azure Notification Hub for iOS
In this post, I won’t explain how to configure the Azure Notification Hub; if you need more information, please read my previous post.
Setting up Firebase Cloud Messaging (FCM) for Android
First, Firebase Cloud Messaging enables you to send push notifications to Android devices. So, I have to configure it as the first action to proceed.
Create a Firebase Project
- Go to the Firebase console.
- Click on ‘Add project’.
- Follow the instructions and set up your project.
Obtain Server Key
- Navigate to Project settings.
- Click on the Cloud Messaging tab. You will see Cloud Messaging API (Legacy) Disabled message. Click on Manage API in Google Cloud Console. You will be redirected to Google Cloud Console. Click on Enable.
- Back to Firebase and copy your Server key.
Configure FCM with Azure
- Go to the Azure portal.
- In your Notification Hub, under Settings, select Google (GCM/FCM).
- Enter your Server Key.
- Click Save.
That’s it! You now have Azure Notification Hubs integrated with FCM.
Setting up your .NET MAUI Project
Add google-services.json
Open Firebase Console and select Add Firebase to your Android app.
On the Add Firebase to your Android app page, enter an Android package name. It should match the package name of your .NET MAUI application.
Select Register app.
Select Download google-services.json. Then save the file into a Platforms\Android
folder. In the properties of the file in your project, mark this file as GoogleServicesJson
Add Required Packages
<ItemGroup Condition="'$(TargetFramework)' == '$(NetVersion)-android'">
<GoogleServicesJson Include="Platforms\Android\google-services.json" />
<PackageReference Include="Xamarin.Firebase.Messaging" Version="122.0.0" />
<PackageReference Include="Xamarin.Google.Dagger" Version="2.39.1" />
</ItemGroup>
JAVA0000
If you get an error like this one
Error JAVA0000 Error in C:\Users\SushmithaBanoji.nuget\packages\xamarin.androidx.collection.jvm\1.3.0.1\buildTransitive\net6.0-android31.0....\jar\androidx.collection.collection-jvm.jar:androidx/collection/ArrayMapKt.class:
Type androidx.collection.ArrayMapKt is defined multiple times: C:\Users\SushmithaBanoji.nuget\packages\xamarin.androidx.collection.jvm\1.3.0.1\buildTransitive\net6.0-android31.0....\jar\androidx.collection.collection-jvm.jar:androidx/collection/ArrayMapKt.class, C:\Users\SushmithaBanoji.nuget\packages\xamarin.androidx.collection.ktx\1.2.0.5\buildTransitive\net6.0-android31.0....\jar\androidx.collection.collection-ktx.jar:androidx/collection/ArrayMapKt.class
Compilation failed
then you have to add another package because there is an issue with one of the Xamarin NuGet package
<PackageReference Include="Xamarin.AndroidX.Fragment.Ktx" Version="1.6.2"/>
Firebase is not initialized
Another error you can get when the application is trying to get a token with FirebaseMessaging.Instance.GetToken()
is
Java.Lang.IllegalStateException: ‘Default FirebaseApp is not initialized in this process com.languageinuse.app. Make sure to call FirebaseApp.initializeApp(Context) first.’
Java.Lang.IllegalStateException: Default FirebaseApp is not initialized in this process com.languageinuse.app. Make sure to call FirebaseApp.initializeApp(Context) first.
at Java.Interop.JniEnvironment.StaticMethods.CallStaticObjectMethod(JniObjectReference type, JniMethodInfo method, JniArgumentValue* args) in /Users/runner/work/1/s/xamarin-android/external/Java.Interop/src/Java.Interop/obj/Release/net7.0/JniEnvironment.g.cs:line 21452
at Java.Interop.JniPeerMembers.JniStaticMethods.InvokeObjectMethod(String encodedMember, JniArgumentValue* parameters) in /Users/runner/work/1/s/xamarin-android/external/Java.Interop/src/Java.Interop/Java.Interop/JniPeerMembers.JniStaticMethods.cs:line 165
at Firebase.Messaging.FirebaseMessaging.get_Instance() in C:\a\_work\1\s\generated\com.google.firebase.firebase-messaging\obj\Release\net7.0-android\generated\src\Firebase.Messaging.FirebaseMessaging.cs:line 106
at MauiPushNotification.Platforms.Android.Notification.DeviceInstallationService.RegisterDevice(String notificationHubNamespace, String notificationHub, String key) in C:\Projects\GitHub\MauiPushNotification\MauiPushNotification\MauiPushNotification\Platforms\Android\Notification\DeviceInstallationService.cs:line 29
--- End of managed Java.Lang.IllegalStateException stack trace ---
java.lang.IllegalStateException: Default FirebaseApp is not initialized in this process com.languageinuse.app. Make sure to call FirebaseApp.initializeApp(Context) first.
at com.google.firebase.FirebaseApp.getInstance(FirebaseApp.java:179)
at com.google.firebase.messaging.FirebaseMessaging.getInstance(FirebaseMessaging.java:126)
at crc648356ffd500c1cdc0.MainActivity.n_onCreate(Native Method)
at crc648356ffd500c1cdc0.MainActivity.onCreate(MainActivity.java:39)
at android.app.Activity.performCreate(Activity.java:8595)
at android.app.Activity.performCreate(Activity.java:8573)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1456)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3764)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3922)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:103)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:139)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:96)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2443)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loopOnce(Looper.java:205)
at android.os.Looper.loop(Looper.java:294)
at android.app.ActivityThread.main(ActivityThread.java:8177)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:552)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:971)
--- End of managed Java.Lang.IllegalStateException stack trace ---
java.lang.IllegalStateException: Default FirebaseApp is not initialized in this process com.languageinuse.app. Make sure to call FirebaseApp.initializeApp(Context) first.
at com.google.firebase.FirebaseApp.getInstance(FirebaseApp.java:179)
at com.google.firebase.messaging.FirebaseMessaging.getInstance(FirebaseMessaging.java:126)
at crc648356ffd500c1cdc0.MainActivity.n_onCreate(Native Method)
at crc648356ffd500c1cdc0.MainActivity.onCreate(MainActivity.java:39)
at android.app.Activity.performCreate(Activity.java:8595)
at android.app.Activity.performCreate(Activity.java:8573)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1456)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3764)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3922)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:103)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:139)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:96)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2443)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loopOnce(Looper.java:205)
at android.os.Looper.loop(Looper.java:294)
at android.app.ActivityThread.main(ActivityThread.java:8177)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:552)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:971)
Add permissions
Update AndroidManifest.xml:
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
Update MainActivity.cs
Remember the connection strings from the Azure Notification Hub setup? You’ll need them now.
Search for DefaultListenSharedAccessSignature
access policy and copy SharedAccessKey
.
protected override async void OnCreate(Bundle? savedInstanceState)
{
base.OnCreate(savedInstanceState);
await DeviceInstallationService.RegisterDevice("YOUR HUB NAME", "YOUR SharedAccessKey");
}
Don’t use
DefaultFullSharedAccessSignature
in client applications!
Create DeviceInstallationService.cs
Azure Hotification Hub requires device registration, so it knows what device should receive a notification.
public static class DeviceInstallationService
{
private static bool NotificationsSupported
=> GoogleApiAvailability.Instance.IsGooglePlayServicesAvailable(Application.Context) == ConnectionResult.Success;
private static string? GetDeviceId()
=> Settings.Secure.GetString(Application.Context.ContentResolver, Settings.Secure.AndroidId);
public static async Task RegisterDevice(string notificationHubNamespace, string notificationHub, string key)
{
if (!NotificationsSupported)
return;
try
{
var firebaseToken = await FirebaseMessaging.Instance.GetToken();
var deviceInstallation = new
{
InstallationId = GetDeviceId(),
Platform = "gcm",
PushChannel = firebaseToken.ToString()
};
using var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Add("x-ms-version", "2015-01");
httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Authorization",
CreateToken($"https://{notificationHubNamespace}.servicebus.windows.net",
"DefaultListenSharedAccessSignature", key));
await httpClient.PutAsJsonAsync($"https://{notificationHubNamespace}.servicebus.windows.net/{notificationHub}" +
$"/installations/{deviceInstallation.InstallationId}?api-version=2015-01",
deviceInstallation);
}
catch(Exception ex)
{
LogCenter.Save("[Android] Push Notification Registration Error", "", exc: ex);
}
}
private static string CreateToken(string resourceUri, string keyName, string key)
{
var sinceEpoch = DateTime.UtcNow - DateTime.UnixEpoch;
var week = 60 * 60 * 24 * 7;
var expiry = Convert.ToString((int)sinceEpoch.TotalSeconds + week);
var stringToSign = HttpUtility.UrlEncode(resourceUri) + "\n" + expiry;
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(key));
var signature = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign)));
var sasToken = string.Format(CultureInfo.InvariantCulture,
"SharedAccessSignature sr={0}&sig={1}&se={2}&skn={3}",
HttpUtility.UrlEncode(resourceUri), HttpUtility.UrlEncode(signature), expiry,
keyName);
return sasToken;
}
}
Here we use FirebaseInstanceId.Instance.Token
to get the token and then we send a PUT HTTP Request to register our device with NotificationHub
. You can find more details here: Notification Hubs REST API Methods.
Now your device is registered.
Setting up the Receivers
Now, the last step is to define our receiver. You need to set up receivers to handle notifications pushed to your app.
For Android, use FirebaseMessagingService
. Override OnMessageReceived()
to define how the notifications should be handled:
[Service(Exported = false)]
[IntentFilter(new[] { "com.google.firebase.MESSAGING_EVENT" })]
public class PushNotificationFirebaseMessagingService : FirebaseMessagingService
{
public override void OnMessageReceived(RemoteMessage p0)
{
base.OnMessageReceived(p0);
var receivedNotification = p0.GetNotification();
// implement your logic here...
}
}
This app is not authorized to use Firebase Authentication
Firebase auth was working fine, but the debug build suddenly started failing without any change of code, logging the following message
D/PhoneAuthActivity( 7392): signInWithCredential:failure:com.google.firebase.auth.FirebaseAuthException: This app is not authorized to use Firebase Authentication. Please verifythat the correct package name and SHA-1 are configured in the Firebase Console. [ App validation failed ].
Using a try ... catch
the error detail is the following:
LanguageInUse.Platforms.Android.Notification.DeviceInstallationService.RegisterDevice(String notificationHubNamespace, String notificationHub, String key) in C:\Projects\ERDevOps\LIUApp\LanguageInUse\Platforms\Android\Notification\DeviceInstallationService.cs:line 30
--- End of managed Java.IO.IOException stack trace ---
java.io.IOException: java.util.concurrent.ExecutionException: java.io.IOException: FIS_AUTH_ERROR
at com.google.firebase.messaging.FirebaseMessaging.blockingGetToken(FirebaseMessaging.java:626)
at com.google.firebase.messaging.FirebaseMessaging.lambda$getToken$4$com-google-firebase-messaging-FirebaseMessaging(FirebaseMessaging.java:382)
at com.google.firebase.messaging.FirebaseMessaging$$ExternalSyntheticLambda9.run(Unknown Source:4)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:487)
at java.util.concurrent.FutureTask.run(FutureTask.java:264)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:307)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:644)
at com.google.android.gms.common.util.concurrent.zza.run(com.google.android.gms:play-services-basement@@18.2.0:2)
at java.lang.Thread.run(Thread.java:1012)
Caused by: java.util.concurrent.ExecutionException: java.io.IOException: FIS_AUTH_ERROR
at com.google.android.gms.tasks.Tasks.zza(com.google.android.gms:play-services-tasks@@18.0.2:5)
at com.google.android.gms.tasks.Tasks.await(com.google.android.gms:play-services-tasks@@18.0.2:8)
at com.google.firebase.messaging.FirebaseMessaging.blockingGetToken(FirebaseMessaging.java:624)
... 9 more
Caused by: java.io.IOException: FIS_AUTH_ERROR
at com.google.firebase.messaging.GmsRpc.handleResponse(GmsRpc.java:309)
at com.google.firebase.messaging.GmsRpc.lambda$extractResponseWhenComplete$0$com-google-firebase-messaging-GmsRpc(GmsRpc.java:320)
at com.google.firebase.messaging.GmsRpc$$ExternalSyntheticLambda0.then(Unknown Source:2)
at com.google.android.gms.tasks.zzc.run(com.google.android.gms:play-services-tasks@@18.0.2:3)
at androidx.profileinstaller.ProfileInstallReceiver$$ExternalSyntheticLambda0.execute(Unknown Source:0)
at com.google.android.gms.tasks.zzd.zzd(com.google.android.gms:play-services-tasks@@18.0.2:1)
at com.google.android.gms.tasks.zzr.zzb(com.google.android.gms:play-services-tasks@@18.0.2:5)
at com.google.android.gms.tasks.zzw.zzb(com.google.android.gms:play-services-tasks@@18.0.2:3)
at com.google.android.gms.tasks.zzc.run(com.google.android.gms:play-services-tasks@@18.0.2:8)
at com.google.android.gms.cloudmessaging.zzz.execute(Unknown Source:0)
at com.google.android.gms.tasks.zzd.zzd(com.google.android.gms:play-services-tasks@@18.0.2:1)
at com.google.android.gms.tasks.zzr.zzb(com.google.android.gms:play-services-tasks@@18.0.2:5)
at com.google.android.gms.tasks.zzw.zzb(com.google.android.gms:play-services-tasks@@18.0.2:3)
at com.google.android.gms.tasks.TaskCompletionSource.setResult(com.google.android.gms:play-services-tasks@@18.0.2:1)
at com.google.android.gms.cloudmessaging.zzp.zzd(com.google.android.gms:play-services-cloud-messaging@@17.0.0:3)
at com.google.android.gms.cloudmessaging.zzr.zza(com.google.android.gms:play-services-cloud-messaging@@17.0.0:2)
at com.google.android.gms.cloudmessaging.zzf.handleMessage(com.google.android.gms:play-services-cloud-messaging@@17.0.0:14)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loopOnce(Looper.java:205)
at android.os.Looper.loop(Looper.java:294)
at android.app.ActivityThread.main(ActivityThread.java:8177)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:552)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:971)
If I added to the PushNotificationFirebaseMessagingService.cs
the plugin for local notification called Plugin.LocalNotification
, the error changed in the following one:
at LanguageInUse.Platforms.Android.Notification.DeviceInstallationService.RegisterDevice(String notificationHubNamespace, String notificationHub, String key) in C:\Projects\ERDevOps\LIUApp\LanguageInUse\Platforms\Android\Notification\DeviceInstallationService.cs:line 30
--- End of managed Java.IO.IOException stack trace ---
java.io.IOException: java.util.concurrent.ExecutionException: java.io.IOException: SERVICE_NOT_AVAILABLE
at com.google.firebase.messaging.FirebaseMessaging.blockingGetToken(FirebaseMessaging.java:626)
at com.google.firebase.messaging.FirebaseMessaging.lambda$getToken$4$com-google-firebase-messaging-FirebaseMessaging(FirebaseMessaging.java:382)
at com.google.firebase.messaging.FirebaseMessaging$$ExternalSyntheticLambda9.run(Unknown Source:4)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:487)
at java.util.concurrent.FutureTask.run(FutureTask.java:264)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:307)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:644)
at com.google.android.gms.common.util.concurrent.zza.run(com.google.android.gms:play-services-basement@@18.2.0:2)
at java.lang.Thread.run(Thread.java:1012)
Caused by: java.util.concurrent.ExecutionException: java.io.IOException: SERVICE_NOT_AVAILABLE
at com.google.android.gms.tasks.Tasks.zza(com.google.android.gms:play-services-tasks@@18.0.2:5)
at com.google.android.gms.tasks.Tasks.await(com.google.android.gms:play-services-tasks@@18.0.2:8)
at com.google.firebase.messaging.FirebaseMessaging.blockingGetToken(FirebaseMessaging.java:624)
... 9 more
Caused by: java.io.IOException: SERVICE_NOT_AVAILABLE
at com.google.android.gms.cloudmessaging.zzv.then(com.google.android.gms:play-services-cloud-messaging@@17.0.0:5)
at com.google.android.gms.tasks.zzc.run(com.google.android.gms:play-services-tasks@@18.0.2:3)
at com.google.android.gms.cloudmessaging.zzz.execute(Unknown Source:0)
at com.google.android.gms.tasks.zzd.zzd(com.google.android.gms:play-services-tasks@@18.0.2:1)
at com.google.android.gms.tasks.zzr.zzb(com.google.android.gms:play-services-tasks@@18.0.2:5)
at com.google.android.gms.tasks.zzw.zza(com.google.android.gms:play-services-tasks@@18.0.2:4)
at com.google.android.gms.tasks.TaskCompletionSource.setException(com.google.android.gms:play-services-tasks@@18.0.2:1)
at com.google.android.gms.cloudmessaging.zzp.zzc(com.google.android.gms:play-services-cloud-messaging@@17.0.0:3)
at com.google.android.gms.cloudmessaging.zzm.zzb(com.google.android.gms:play-services-cloud-messaging@@17.0.0:8)
at com.google.android.gms.cloudmessaging.zzm.zza(com.google.android.gms:play-services-cloud-messaging@@17.0.0:1)
at com.google.android.gms.cloudmessaging.zzm.zzd(com.google.android.gms:play-services-cloud-messaging@@17.0.0:1)
at com.google.android.gms.cloudmessaging.zzi.run(Unknown Source:2)
... 7 more
Caused by: com.google.android.gms.cloudmessaging.zzq: Timed out while binding
at com.google.android.gms.cloudmessaging.zzm.zzb(com.google.android.gms:play-services-cloud-messaging@@17.0.0:6)
... 10 more
Topic sync or token retrieval failed on hard failure exceptions: FIS_AUTH_ERROR. Won’t retry the operation.
I fought this error for about a week and I couldn’t understand what the problem was. In my case, the problem was related to the configuration in the Google Cloud under credentials.
So, my advice is to try your app without any restriction and then add the restrictions and test your app.