Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to use Xamarin.Azure.NotificationHubs.iOS with MAUI ? #125

Open
punio opened this issue Jan 5, 2023 · 10 comments
Open

How to use Xamarin.Azure.NotificationHubs.iOS with MAUI ? #125

punio opened this issue Jan 5, 2023 · 10 comments

Comments

@punio
Copy link

punio commented Jan 5, 2023

Question
I used Xamarin.Azure.NotificationHubs.iOS with Xamarin.Forms, but with MAUI NuGet Package installation shows NU1202 and I can't install.

Why is this not a Bug or a feature Request?
I'm not very familiar with MAUI, but it seems that most of the Xamarin libraries can be used with MAUI.

Setup (please complete the following information if applicable):

  • OS: net7.0-ios16.1
  • IDE : Visual Studio 2022
  • Version of the Library used : 3.1.1(NuGet) / 3.1.5(GitHub)
@FM1973
Copy link

FM1973 commented Jan 31, 2023

I got the same problem.
@punio did you find a solution?

@punio
Copy link
Author

punio commented Feb 1, 2023

@FM1973
I found only out that can't use it as it is with MAUI.
It may be possible with Shiny, but I haven't tried it yet.

@FM1973
Copy link

FM1973 commented Feb 1, 2023

@punio
I took another road.
I´m now using Azure NotificationHub only on the server side.
On the client side I did the implementation like described in the following link:
https://learn.microsoft.com/en-us/azure/developer/mobile-apps/notification-hubs-backend-service-xamarin-forms#configure-the-native-android-project-for-push-notifications

Although I changed some bits and replaced deprecated code with the code one should use now.

Works pretty well...

@punio
Copy link
Author

punio commented Feb 2, 2023

@FM1973
Thank you for letting me know how to solve it.
I will try that solution too.

@eyeveye
Copy link

eyeveye commented Mar 8, 2023

@punio I took another road. I´m now using Azure NotificationHub only on the server side. On the client side I did the implementation like described in the following link: https://learn.microsoft.com/en-us/azure/developer/mobile-apps/notification-hubs-backend-service-xamarin-forms#configure-the-native-android-project-for-push-notifications

Although I changed some bits and replaced deprecated code with the code one should use now.

Works pretty well...

Hi, do you get the IOS working or android version?
If is IOS, can you share how you achieve it?

@fdhsdrdark
Copy link

According to this xamarin/XamarinComponents#1418
Xamarin.Azure.NotificationHubs.iOS is not going to be further supported!
So currently, what @FM1973 suggested seems the only solution, not only for MAUI but for .Net iOS in .Net 6 also.
Any input is more than welcome!

@RobertHedgate
Copy link

RobertHedgate commented Mar 28, 2024

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

@rimex-bkimmett
Copy link

Yeah, I've just finished migrating my app to Microsoft.Azure.NotificationHubs as well. I just need to get the token from APNS (or Firebase on Android) and feed that to the Installation request.

@RobertHedgate
Copy link

@rimex-bkimmett Please see this thread Azure/azure-notificationhubs-dotnet#298. I have created a boiler plate repo (https://github.com/RobertHedgate/MauiAppPush) on how I got it to work but more questions are answered is that thread,

@DavidMarquezF
Copy link

DavidMarquezF commented Aug 6, 2024

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;
       }
   }

⚠️ 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 the DeviceInstallation.Token to Firebase.Instance.getToken() before the RegisterDevice function is called (Firebase docs recommend updating it in the OnCreate function)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants