Open MouratidisA opened 1 year ago
I'm having the same issue, how would one use it to register on the hub? Where are the examples?
Same problem here..
Any update on this?
Same issue - I see they have released 4.2.0-beta1 .... will this mean maui 8 is supported?
Why is it that is so difficult to get any kind of official response? Anyone on the Azure notificationHubs-dotnet team ? thanks
Is there are any updates or solutions? how to implement support of Notification Hub on MAUI?
We basically cant wait any longer for a response - does anyone know of a different notification framework that works for android and IOS that is being supported on maui?
@michaelonz that is big issue at the moment. there is nothing long term post june that will work on both iOS and android. and for people on this forum that moderate it and not to reply is just awful . I guess is to try onesignal (free) but does not work with hotrestart if you are using a pc.. or any paid alternative and would be nice to know if someone has implemented anything that works
Anyone from Azure notificationHubs-dotnet team respond on the above issue, its been long time that we are waiting for MAUI supported package of IOS Azure Push notification
I solved iOS push by using Microsoft.Azure.NotificationHubs
var hub = NotificationHubClient.CreateClientFromConnectionString(this.connectionString, this.notificationHubName); var installation = new Installation { InstallationId = deviceToken, PushChannel = deviceToken, Platform = NotificationPlatform.Apns, Tags = Array.Empty() };
await hub.CreateOrUpdateInstallationAsync(installation);
deviceToken is obtained from RegisteredForRemoteNotifications
Please, update your docs and examples and show how to use Azure Notification Hub with MAUI.
@RobertHedgate - What nuget packages did you have installed to make this work - I cant find the NotificationHubClient from the IOS platform code.
@michaelonz https://www.nuget.org/packages/Microsoft.Azure.NotificationHubs works for both android and iOS.
@RobertHedgate this package works for .NET 6, but not with the .NET8
@a-martsineuski My iOS app is on .net8. Doesn´t the .net6 flag mean .net6 or higher?
@RobertHedgate let's clarify: your app is net8.0-ios\net8.0-android? Yes, that means .NET6 or higher, but not .NETX-android.NETX-ios. It not possible to install it in the MAUI project
@a-martsineuski no it is MAUI app with platforms and all.
@RobertHedgate could you show a demo app, how did you install it?
@a-martsineuski I created a quick repo of my solution. It is not complete but contains where one gets the tokens and calls the azure hub. Can be found here https://github.com/RobertHedgate/MauiAppPush
@RobertHedgate I tried to run/build your test project as it is without any changes on Visual Studio for Mac 17.6.10 (build 428), and I get an error during the build proccess:
/Users/yevhenmyroshnychenko/Projects/Work/MauiAppPush/MauiAppPush: Error JAVA0000: Error in /Users/yevhenmyroshnychenko/.nuget/packages/xamarin.androidx.collection.jvm/1.3.0.2/buildTransitive/net7.0-android33.0/../../jar/androidx.collection.collection-jvm.jar:androidx/collection/ArrayMapKt.class: Type androidx.collection.ArrayMapKt is defined multiple times: /Users/yevhenmyroshnychenko/.nuget/packages/xamarin.androidx.collection.jvm/1.3.0.2/buildTransitive/net7.0-android33.0/../../jar/androidx.collection.collection-jvm.jar:androidx/collection/ArrayMapKt.class, /Users/yevhenmyroshnychenko/.nuget/packages/xamarin.androidx.collection.ktx/1.2.0.9/buildTransitive/net6.0-android31.0/../../jar/androidx.collection.collection-ktx.jar:androidx/collection/ArrayMapKt.class Compilation failed java.lang.RuntimeException: com.android.tools.r8.CompilationFailedException: Compilation failed to complete, origin: /Users/yevhenmyroshnychenko/.nuget/packages/xamarin.androidx.collection.jvm/1.3.0.2/buildTransitive/net7.0-android33.0/../../jar/androidx.collection.collection-jvm.jar androidx/collection/ArrayMapKt.class at com.android.tools.r8.utils.S0.a(R8_8.2.33_429c93fd24a535127db6f4e2628eb18f2f978e02f99f55740728d6b22bef16dd:135) at com.android.tools.r8.D8.main(R8_8.2.33_429c93fd24a535127db6f4e2628eb18f2f978e02f99f55740728d6b22bef16dd:5) Caused by: com.android.tools.r8.CompilationFailedException: Compilation failed to complete, origin: /Users/yevhenmyroshnychenko/.nuget/packages/xamarin.androidx.collection.jvm/1.3.0.2/buildTransitive/net7.0-android33.0/../../jar/androidx.collection.collection-jvm.jar:androidx/collection/ArrayMapKt.class at Version.fakeStackEntry(Version_8.2.33.java:0) at com.android.tools.r8.T.a(R8_8.2.33_429c93fd24a535127db6f4e2628eb18f2f978e02f99f55740728d6b22bef16dd:5) at com.android.tools.r8.utils.S0.a(R8_8.2.33_429c93fd24a535127db6f4e2628eb18f2f978e02f99f55740728d6b22bef16dd:82) at com.android.tools.r8.utils.S0.a(R8_8.2.33_429c93fd24a535127db6f4e2628eb18f2f978e02f99f55740728d6b22bef16dd:32) at com.android.tools.r8.utils.S0.a(R8_8.2.33_429c93fd24a535127db6f4e2628eb18f2f978e02f99f55740728d6b22bef16dd:31) at com.android.tools.r8.utils.S0.b(R8_8.2.33_429c93fd24a535127db6f4e2628eb18f2f978e02f99f55740728d6b22bef16dd:2) at com.android.tools.r8.D8.a(R8_8.2.33_429c93fd24a535127db6f4e2628eb18f2f978e02f99f55740728d6b22bef16dd:42) at com.android.tools.r8.D8.b(R8_8.2.33_429c93fd24a535127db6f4e2628eb18f2f978e02f99f55740728d6b22bef16dd:13) at com.android.tools.r8.D8.a(R8_8.2.33_429c93fd24a535127db6f4e2628eb18f2f978e02f99f55740728d6b22bef16dd:40) at com.android.tools.r8.utils.S0.a(R8_8.2.33_429c93fd24a535127db6f4e2628eb18f2f978e02f99f55740728d6b22bef16dd:122) ... 1 more Caused by: com.android.tools.r8.utils.b: Type androidx.collection.ArrayMapKt is defined multiple times: /Users/yevhenmyroshnychenko/.nuget/packages/xamarin.androidx.collection.jvm/1.3.0.2/buildTransitive/net7.0-android33.0/../../jar/androidx.collection.collection-jvm.jar:androidx/collection/ArrayMapKt.class, /Users/yevhenmyroshnychenko/.nuget/packages/xamarin.androidx.collection.ktx/1.2.0.9/buildTransitive/net6.0-android31.0/../../jar/androidx.collection.collection-ktx.jar:androidx/collection/ArrayMapKt.class at com.android.tools.r8.utils.Q2.a(R8_8.2.33_429c93fd24a535127db6f4e2628eb18f2f978e02f99f55740728d6b22bef16dd:21) at com.android.tools.r8.utils.D2.a(R8_8.2.33_429c93fd24a535127db6f4e2628eb18f2f978e02f99f55740728d6b22bef16dd:54) at com.android.tools.r8.utils.D2.a(R8_8.2.33_429c93fd24a535127db6f4e2628eb18f2f978e02f99f55740728d6b22bef16dd:10) at java.base/java.util.concurrent.ConcurrentHashMap.merge(ConcurrentHashMap.java:2048) at com.android.tools.r8.utils.D2.a(R8_8.2.33_429c93fd24a535127db6f4e2628eb18f2f978e02f99f55740728d6b22bef16dd:6) at com.android.tools.r8.graph.m4$a.d(R8_8.2.33_429c93fd24a535127db6f4e2628eb18f2f978e02f99f55740728d6b22bef16dd:6) at com.android.tools.r8.dex.c.a(R8_8.2.33_429c93fd24a535127db6f4e2628eb18f2f978e02f99f55740728d6b22bef16dd:61) at com.android.tools.r8.dex.c.a(R8_8.2.33_429c93fd24a535127db6f4e2628eb18f2f978e02f99f55740728d6b22bef16dd:12) at com.android.tools.r8.dex.c.a(R8_8.2.33_429c93fd24a535127db6f4e2628eb18f2f978e02f99f55740728d6b22bef16dd:9) at com.android.tools.r8.D8.a(R8_8.2.33_429c93fd24a535127db6f4e2628eb18f2f978e02f99f55740728d6b22bef16dd:45) at com.android.tools.r8.D8.d(R8_8.2.33_429c93fd24a535127db6f4e2628eb18f2f978e02f99f55740728d6b22bef16dd:17) at com.android.tools.r8.D8.c(R8_8.2.33_429c93fd24a535127db6f4e2628eb18f2f978e02f99f55740728d6b22bef16dd:69) at com.android.tools.r8.utils.S0.a(R8_8.2.33_429c93fd24a535127db6f4e2628eb18f2f978e02f99f55740728d6b22bef16dd:28) ... 6 more (JAVA0000) (MauiAppPush) java
@DeveloperLookBook I have added some nuget to android so it should build now but you still need to add google-sevice,json and all certificate to your project to make it work. You should more look at the project and use its registration to azure in your own projects.
Hi @RobertHedgate - can you please specify what certificates we need to add and where we need to put them in the folder structure along with if any properties are needed on the files (eg embedded resource etc etc) - so good to finally see an example working with .net8
@michaelonz Added this below to the readme of the project. Hope it helps.
For iOS you don´t need anything else in your app except selecting the correct certificate when you build.
Android you need the google-service.json file you download from you firebase console and add it directly under Android folder.
Here are a tutorial on iOS. https://learn.microsoft.com/en-us/azure/notification-hubs/ios-sdk-get-started
Setting up Android FCM V1 https://learn.microsoft.com/en-us/azure/notification-hubs/firebase-migration-rest
If GoogleServieJson doesn´t show up as build action https://github.com/dotnet/maui/issues/14486
@RobertHedgate - i downloaded your sample (thank you) and i downloaded my google-services.json and set the buildaction to googleservices.json.
When I run it on android (havent tried apple yet) I get the following error:
[FirebaseApp] Default FirebaseApp failed to initialize because no default options were found. This usually means that com.google.gms:google-services was not applied to your gradle project.
Any ideas?
NOTE: I have already tried re downloading the google-services.json file also.
Hi @RobertHedgate - No need for you to look into my issue above .... I worked it out.
The
Hope this helps others also.
I still havent got the end to end message working but this resolved the following error:
[FirebaseApp] Default FirebaseApp failed to initialize because no default options were found. This usually means that com.google.gms:google-services was not applied to your gradle project.
I repost my comment from https://github.com/Azure/azure-notificationhubs-xamarin/issues/125, just in case it is useful for anyone:
Now it's explained in the docs: https://learn.microsoft.com/en-us/dotnet/maui/data-cloud/push-notifications?view=net-maui-8.0#create-a-net-maui-app
They show how to do it with api key instead of ListenConnectionString which has some benefits but of course adds some complexity. @RobertHedgate proposal still looks like the one with the least amount of boilerplate code to make everything work.
I believe the only "issue" with @RobertHedgate proposal is that it doensn't refresh the azure hub installation when the token changes in the onNewToken
function.
In my case, I did a hybrid between both. I followed the tutorial, but my NotificationRegistrationService
(the one that they use the api key and the http calls) is like this:
public class NotificationRegistrationService : INotificationRegistrationService
{
const string CachedDeviceTokenKey = "cached_notification_hub_device_token";
const string CachedTagsKey = "cached_notification_hub_tags";
const string CachedUserIdKey = "cached_notification_hub_user_id";
IDeviceInstallationService _deviceInstallationService;
private NotificationHubClient _hub;
public NotificationRegistrationService(IDeviceInstallationService deviceInstallationService)
{
_deviceInstallationService = deviceInstallationService ?? throw new ArgumentNullException(nameof(deviceInstallationService));
_hub = NotificationHubClient.CreateClientFromConnectionString(Config.ListenConnectionString, Config.NotificationHubName);
}
public async Task DeregisterDeviceAsync()
{
var cachedToken = await SecureStorage.GetAsync(CachedDeviceTokenKey)
.ConfigureAwait(false);
if (cachedToken == null)
return;
var deviceId = GetDeviceId();
await _hub.DeleteInstallationAsync(deviceId);
SecureStorage.Remove(CachedDeviceTokenKey);
SecureStorage.Remove(CachedTagsKey);
SecureStorage.Remove(CachedUserIdKey);
}
public async Task RegisterDeviceAsync(string userId, params string[] tags)
{
var deviceInstallation = _deviceInstallationService?.GetDeviceInstallation(tags);
if (!string.IsNullOrEmpty(userId))
deviceInstallation.UserId = userId;
await _hub.CreateOrUpdateInstallationAsync(deviceInstallation);
await SecureStorage.SetAsync(CachedDeviceTokenKey, deviceInstallation.PushChannel)
.ConfigureAwait(false);
await SecureStorage.SetAsync(CachedTagsKey, JsonSerializer.Serialize(tags));
await SecureStorage.SetAsync(CachedUserIdKey, userId);
}
public async Task RefreshRegistrationAsync()
{
var cachedToken = await SecureStorage.GetAsync(CachedDeviceTokenKey)
.ConfigureAwait(false);
var serializedTags = await SecureStorage.GetAsync(CachedTagsKey)
.ConfigureAwait(false);
var cachedUserId = await SecureStorage.GetAsync(CachedUserIdKey)
.ConfigureAwait(false);
if (string.IsNullOrWhiteSpace(cachedToken) ||
string.IsNullOrWhiteSpace(serializedTags) ||
string.IsNullOrEmpty(cachedUserId) ||
string.IsNullOrWhiteSpace(_deviceInstallationService.Token) ||
cachedToken == _deviceInstallationService.Token)
return;
var tags = JsonSerializer.Deserialize<string[]>(serializedTags);
await RegisterDeviceAsync(cachedUserId, tags);
}
private string _userIdPropName;
private string UserIdPropName => _userIdPropName ??= "/" + GetJsonPropertyName(typeof(Installation), nameof(Installation.UserId));
private string _tagsPropName;
private string TagsPropName => _tagsPropName ??= GetJsonPropertyName(typeof(Installation), nameof(Installation.Tags));
public async Task UpdateUserId(string userName)
{
var deviceId = GetDeviceId();
var updates = new List<PartialUpdateOperation>
{
new() { Operation = UpdateOperationType.Replace, Path = UserIdPropName, Value = userName },
await GetUpdateTagOperation(new[] { (NotificationTags.Username, userName) })
};
await _hub.PatchInstallationAsync(deviceId, updates);
}
public async Task UpdateTag(NotificationTags tag, string value)
{
var deviceId = GetDeviceId();
var updates = new List<PartialUpdateOperation>
{
await GetUpdateTagOperation(new[] { (tag, value) })
};
await _hub.PatchInstallationAsync(deviceId, updates);
}
private async Task<PartialUpdateOperation> GetUpdateTagOperation(IEnumerable<(NotificationTags, string)> newTags)
{
var serializedTags = await SecureStorage.GetAsync(CachedTagsKey)
.ConfigureAwait(false);
var tags = new List<string>();
if (!string.IsNullOrWhiteSpace(serializedTags))
tags.AddRange(JsonSerializer.Deserialize<string[]>(serializedTags));
foreach (var (tagType, value) in newTags)
{
var tagId = NotificationHubUtils.GetTagId(tagType);
var tagIndex = tags.FindIndex(a => a.StartsWith(tagId));
var tag = NotificationHubUtils.GetTag(tagType, value);
if (tagIndex >= 0)
tags[tagIndex] = tag;
else
tags.Add(tag);
}
return new() { Operation = UpdateOperationType.Replace, Path = TagsPropName, Value = JsonSerializer.Serialize(tags) };
}
private string GetDeviceId()
{
var deviceId = _deviceInstallationService?.GetDeviceId();
if (string.IsNullOrWhiteSpace(deviceId))
throw new Exception("Unable to resolve an ID for the device.");
return deviceId;
}
private static string GetJsonPropertyName(Type type, string propertyName)
{
if (type is null)
throw new ArgumentNullException(nameof(type));
return type.GetProperty(propertyName)
?.GetCustomAttribute<Newtonsoft.Json.JsonPropertyAttribute>()
?.PropertyName;
}
}
:warning: I think currently there is an issue with their Android implementation in their example. They implement
Android.Gms.Tasks.IOnSuccessListener
on the Main activity, but they don't add it as a listener to anything. My guess is that they wanted to add what in java is like:FirebaseMessaging.getInstance().getToken().addOnCompleteListener(IOnSuccessListener)
. The solution is to simply set theDeviceInstallation.Token
toFirebase.Instance.getToken()
before theRegisterDevice
function is called (Firebase docs recommend updating it in the OnCreate function)
Question Is there a way to implement notification hub registration on MAUI applications (Android and iOS)?
I've tried implementing notification hub registration using NotificationHubClient on IOS( but I don't think a client application should use this implementation to register).
Implementation:
What is the proper way to implement push notifications on MAUI for Android and iOS using the Azure Notification hubs?