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 OneSignalNotificationProvider() {

        }

        public async Task Push(PushNotification notification) {

        }   
    }    
}

Update settings.config

In order to use this new custom notification provider instead of the default one, you have to add the following to the settings.config file. If you keep the settings in web.config or the Azure Portal, please update or add the same value there.

<appSettings>    
    <add key="weavy.notification-provider" value="Weavy.Providers.OneSignalNotificationProvider, Weavy" />    
</appSettings>

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

The value part of the setting is the full class name including the namespace. And the part after the comma is the assembly name where you added the class.

Additional settings

You can add additional settings needed for the provider in settings.config as well. All settings can be fetched using ConfigurationService.AppSetting([key]). 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.

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

        public OneSignalNotificationProvider () {
            _appId = ConfigurationService.AppSetting("OneSignalAppId");
            _restApiKey = ConfigurationService.AppSetting("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 OneSignalNotificationProvider () {
            _appId = ConfigurationService.AppSetting("OneSignalAppId");
            _restApiKey = ConfigurationService.AppSetting("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

A Xamarin.Forms example

If you have a Xamarin.Forms mobile app and want to use the OneSignal notification provider, 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());

If you are using the Weavy Web View, make sure to subscribe to this event and set the Uri property of the webview.

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

Add the following properties to a Constants.cs class or similar. This is used in the SharedPush.Initialize method:

/// <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 page in Weavy has finished loading, an event is sent that you can handle to get the current signed in user. The user's guid (unique id) is can be read from the user. 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);.

weavyWebview.LoadFinished += (sender, args) => {
    // get the currently stored guid
    var userGuid = CrossSettings.Current.Get<string>("userguid");
    
    // get the current signed in user
    weavyWebview.GetUser((data) => {
        var user = JsonConvert.DeserializeObject<User>(data);
        var guid = user.Guid.ToString();

        // check if the user guid is already set to avoid multiple registrations to OneSignal                                                                                                                                                                                           
        if (guid != userGuid) {
            CrossSettings.Current.Set<string>("userguid", guid);        

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