Implementing a Notification Provider

Weavy has an INotificationProvider provider interface that can be implemented with the push notification service of your choice. The default implementation in Weavy uses Azure Notification Hubs for sending push notifications. See Mobile push notifications for more info about setting up and configuring the built-in notification provider.

In this tutorial we are going to build a custom notification provider that will push notifications to the user's mobile devices. We are going to use OneSignal as an example.

Implement INotificationProvider

Create a new class called OneSignalNotificationProvider. Make sure the class implements the INotificationProvider interface. The interface requires the Weavy.Core.Providers.IProvider.Initialize and Push methods to be implemented.

namespace Weavy.Providers {
    public class OneSignalNotificationProvider : INotificationProvider{
        public void Initialize(NameValueCollection settings) {

        }

        public async Task Push(PushNotification notification) {

        }   
    }    
}

Update web.config

In order to use this new custom notification provider instead of the default one, you have to add the following to the web.config file.

<configuration>
    <weavy>
        <notification provider="Weavy.Providers.OneSignalNotificationProvider" />
    </weavy>
</configuration>

This tells Weavy to use the OneSignalNotificationProvider as the notification provider.

The <weavy/> element is probably already present in web.config. Then just add the <notification/> tag.

Pass settings to the provider

In most cases, you probably want to pass some provider specific settings to the custom provider class. This can be done by specifying one or more attributes on the <notification/> tag in web.config. OneSignal requires an APP ID and a REST API KEY in order to create a notification with the api. More information about that can be found at the OneSignal documentation site.

Get the APP ID and the REST API KEY and add the following (replace the placeholders) to the <notification/> tag in web.config:

<notification provider="Weavy.Providers.OneSignalNotificationProvider" appid="[APP ID]" restkey="[REST API KEY]" />

The values specified in web.config can now be fetched in the Initialize(NameValueCollection settings) method in the OneSignalNotificationProvider class.

namespace Weavy.Providers {
    public class OneSignalNotificationProvider : INotificationProvider{
        private string _appId = null;
        private string _restApiKey = null;

        public void Initialize(NameValueCollection settings) {
            _appId = settings["appid"];
            _restApiKey = settings["restkey"];
        }

        public async Task Push(PushNotification notification) {

        }   
    }    
}

Create a notification

Whenever a new notification should be sent to a Weavy user, the Push(PushNotification notification)method is called. The notification data is passed in the PushNotification notification parameter. OneSignal has a rest api enpoint that can be used to create a new notification. The following is a very basic example on how this could be called.

namespace Weavy.Providers {
    public class OneSignalNotificationProvider : INotificationProvider{
        private string _appId = null;
        private string _restApiKey = null;
        private string _baseUrl = "https://onesignal.com/api/v1";

        public void Initialize(NameValueCollection settings) {
            _appId = settings["appid"];
            _restApiKey = settings["restkey"];
        }

        public async Task Push(PushNotification notification) {
            var client = new HttpClient();
            var requestMessage = new HttpRequestMessage();
    
            requestMessage.Method = HttpMethod.Post;            
            requestMessage.Headers.Authorization= new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", _restApiKey);
            requestMessage.RequestUri = new Uri($"{_baseUrl}/notifications");

            // this is a unique external id that a user is tagged with in OneSignal as External ID. 
            var externalId = $"userid:{notification.Guid.ToString()}");

            var jsonContent = new OneSignalNotificationRequest {
                AppId = _appId,
                Url = new OneSignalNotificationDataUrl() { Url = notification.Url},
                Headings = new OneSignalNotificationHeadings {
                    En = notification.Destination == PushNotificationDestination.Spaces ? "Here's a new notification for you!" : "You got a new message"
                },
                Contents = new OneSignalNotificationContents {
                    En = notification.Message
                },                
                IncludeExternalUserIds = new string[] { externalId }
            };
            var content = SerializationExtensions.SerializeToJson(jsonContent);
            requestMessage.Content = new StringContent(content, Encoding.UTF8, "application/json");
            await client.SendAsync(requestMessage);
        }   
    }    
}

The externalId is passed to the request data to specify which user in OneSignal the notification should be sent to. The format for the external can be anything unique to the user. In this case we are using the users guid that is passed in the notification data.

var externalId = $"userid:{notification.Guid.ToString()}");

OneSignal requires the request json data to be in a specific format specified here,OneSignal - Create notifications. The jsonContent is a OneSignalNotificationRequest which is a helper class with typed properties that are serialized to json.

var jsonContent = new OneSignalNotificationRequest {
    ...
}
The helper classes created for this example are displayed below for reference.
sealed class OneSignalNotificationRequest {
    
    [JsonProperty(PropertyName = "app_id")]
    public string AppId { get; set; }

    [JsonProperty(PropertyName = "included_segments")]
    public string IncludedSegments { get; set; }

    [JsonProperty(PropertyName = "contents")]
    public OneSignalNotificationContents Contents { get; set; }

    [JsonProperty(PropertyName = "headings")]
    public OneSignalNotificationHeadings Headings { get; set; }

    [JsonProperty(PropertyName = "data")]
    public OneSignalNotificationDataUrl Url { get; set; }
    
    [JsonProperty(PropertyName = "include_external_user_ids")]
    public string[] IncludeExternalUserIds { get; set; }
}
        
sealed class OneSignalNotificationContents {
    [JsonProperty(PropertyName = "en")]
    public string En { get; set; }
}

sealed class OneSignalNotificationHeadings {
    [JsonProperty(PropertyName = "en")]
    public string En { get; set; }
}

sealed class OneSignalNotificationDataUrl {
    [JsonProperty(PropertyName = "url")]
    public string Url { get; set; }
}

Add OneSignal to your mobile app

Depending on what kind of platform you are building your Weavy app with, there are different ways to implement this. Please refer to the OneSignal documentation for all the possibilities.

OneSignal Mobile SDK Setup

Change the Xamarin.Forms example (OPTIONAL)

If you want to change the Xamarin.Forms mobile app example to use the OneSignal notification provider instead of Azure Notifications Hubs, do the following.

OneSignal Nuget packages

Start by adding the required Nuget packages for OneSignal to all the projects in the mobile app solution. More info on that can be found here: Xamarin SDK Setup

SharedPush

Add a SharedPush.cs to the PCL project and replace it with the following code:

public static class SharedPush
{
    // Called on iOS and Android to initialize OneSignal
    public static void Initialize()
    {
        OneSignal.Current.SetLogLevel(LOG_LEVEL.VERBOSE, LOG_LEVEL.WARN);

        //if you want to require user consent, change this to true
        SharedPush.SetRequiresConsent(false);

        OneSignal.Current.StartInit(Constants.OneSignalAppId).Settings(new Dictionary<string, bool>() {
        { IOSSettings.kOSSettingsKeyAutoPrompt, true },
        { IOSSettings.kOSSettingsKeyInAppLaunchURL, true } })
            .InFocusDisplaying(OSInFocusDisplayOption.Notification)
            .HandleNotificationOpened((result) =>
            {
                MessagingCenter.Send<Application, string>(Application.Current, "NOTIFICATION_RECEIVED", result.notification.payload.additionalData["url"].ToString());

                Debug.WriteLine("HandleNotificationOpened: {0}", result.notification.payload.body);
            })
            .HandleNotificationReceived((notification) =>
            {
                Debug.WriteLine("HandleNotificationReceived: {0}", notification.payload.body);
            })
            .EndInit();

        OneSignal.Current.IdsAvailable((playerID, pushToken) =>
        {
            Debug.WriteLine("OneSignal.Current.IdsAvailable:D playerID: {0}, pushToken: {1}", playerID, pushToken);
        });
    }

    // Just for iOS.
    // No effect on Android, device auto registers without prompting.
    public static void RegisterIOS()
    {
        OneSignal.Current.RegisterForPushNotifications();
    }

    public static void ConsentStatusChanged(bool consent)
    {
        OneSignal.Current.UserDidProvidePrivacyConsent(consent);
    }

    public static bool UserDidProvideConsent()
    {
        return !OneSignal.Current.RequiresUserPrivacyConsent();
    }

    public static void SetRequiresConsent(bool required)
    {
        OneSignal.Current.SetRequiresUserPrivacyConsent(required);
    }

    public static void SetExternalUserId(string externalId)
    {
        OneSignal.Current.SetExternalUserId(externalId);
    }

    public static void RemoveExternalUserId()
    {
        OneSignal.Current.RemoveExternalUserId();
    }
}

From the code above, when a notification arrives and is opened, a message is published:

MessagingCenter.Send<Application, string>(Application.Current, "NOTIFICATION_RECEIVED", result.notification.payload.additionalData["url"].ToString());

Make sure to subscribe to this event and replace the existing code ment for Azure Notification Hubs in the MainPage.cs

// remove these lines
//NotificationReceived += (sender, args) => {
//   var notificationArgs = args as NotificationEventArgs;
//    _webview.Uri = notificationArgs.NotificationUrl;
//};

// and add this...
MessagingCenter.Subscribe<MainPage, string>(this, "NOTIFICATION_RECEIVED", (sender, notificationUrl) =>
{
    _webview.Uri = notificationUrl;
});

Add the following properties to the Constants.cs class:

/// <summary>
/// One Signal App Id
/// </summary>
public static string OneSignalAppId = "YOUR_ONESIGNAL_APP_ID";

Add the following to the App.cs constructor to initialize OneSignal. Make sure to add it at the end of the method.

// initialize push notifications            
SharedPush.Initialize();

Android setup

Add the following to the AndroidManifest.xml file.

<permission android:name="${manifestApplicationId}.permission.C2D_MESSAGE" android:protectionLevel="signature" />
<uses-permission android:name="${manifestApplicationId}.permission.C2D_MESSAGE" />

In your application tag, add the following:

<application ....>
    <receiver android:name="com.onesignal.GcmBroadcastReceiver" android:permission="com.google.android.c2dm.permission.SEND">
        <intent-filter>
            <action android:name="com.google.android.c2dm.intent.RECEIVE" />
            <category android:name="${manifestApplicationId}" />
        </intent-filter>
    </receiver>
</application>

Replace all 3 of instances of ${manifestApplicationId} with your package name in AndroidManifest.xml.

iOS Setup

Make sure to enable Push Notifications in Entitlements.plist

Under iOS, OneSignal uses a Notification Service Extension to handle badge count etc. Please refer to the documentation for Xamarin SDK setup on how to do this. It's not required to create a Notification Service Extension to get up and running with OneSignal on iOS, but it enhances the experience.

Handle device registration

When a users is authenticated in the Weavy web application, a message from the web page to the mobile app is sent. The message is handled in the registerForNotificationsCallback callback. The user's guid (unique id) is passed to the callback. Use this to set the External ID in OneSignal to associate all user's devices with the user. This is done by calling SharedPush.SetExternalUserId(string externalId);.

// Callback for registration with Notification Provider
_webview.RegisterCallback("registerForNotificationsCallback", (userGuid) => {
    var guid = CrossSettings.Current.Get<string>("userguid");
    
    if (guid != userGuid)
    {
        CrossSettings.Current.Set<string>("userguid", userGuid);        

        // register external id with OneSignal
        SharedPush.SetExternalUserId(userGuid);
    }
});