Thursday 31 May 2018

Xamarin.Forms Mvvm: How To Send Local Notification With A Repeat Interval

Introduction

In this article, we will learn how to send Local Notifications for a particular date and time at a repeated interval (Day/Minute/HourDay/ etc.,).



Description

Local notifications are used to send application-related data at some time through notifications.
First, follow the below steps to create the new Xamarin.Forms project.
  1. Open Visual Studio for Mac.
  2. Click on the File menu and select New Solution.
  3. In the left pane of the dialog, let's select the type of templates to display. Multiplatform > App > Xamarin.Forms > Blank Forms App and click "Next".
  4. Next, enter your app name (Ex: LocalNotificationdemo). At the bottom, select target platforms to Android & iOS and shared code to Portable Class Library and click the "Next" button.
  5. Then, choose project location with the help of Browse button and click "Create".
The project structure will be created like below.
  • LocalNotificationdemoIt is for Shared Code
  • LocalNotificationdemo.DroidIt is for Android.
  • LocalNotificationdemo.iOSIt is for iOS. 
Now, follow the below steps for getting a local notification and repeating per Day/Minute/Hour etc.,
  • Taking Switch for notifications on or off.
  • Taking TimePicker and DatePicker for scheduling local notification.
  • Converting TimePicker selected time, DatePicker selectedDate to DateTime type and passing datetime to DependencyService.
  • Taking Editor and passing editor text as a notification message.
  • Save button, In this, we are checking notification checking on or off. If notification switch on then calling Dependency Service to schedule notification.
  • In Platform specific, we are scheduling notification and giving repeat interval time like Day/Minute/Hour etc,.
Portable class library(PCL)

Views:

Create your own XAML page named LocalNotificationPage.xaml inside the Views folder. And here, we are taking inputs from user like notification On/Off, date, time and message text.

LocalNotificationPage.xaml


  1. <?xml version="1.0" encoding="UTF-8"?>      
  2. <ContentPage xmlns="http://xamarin.com/schemas/2014/forms"      
  3.     xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"      
  4.     x:Class="LocalNotificationDemo.Views.LocalNotificationPage"      
  5.     Title="Local Notification"      
  6.     BackgroundColor="#533F95">      
  7.     <ContentPage.Content>      
  8.         <Grid VerticalOptions="FillAndExpand" Padding="25,40,25,30" RowSpacing="20">      
  9.             <Grid.RowDefinitions>      
  10.                 <RowDefinition Height="Auto"/>      
  11.                 <RowDefinition Height="Auto"/>      
  12.                 <RowDefinition Height="Auto"/>      
  13.                 <RowDefinition Height="Auto"/>      
  14.                 <RowDefinition Height="Auto"/>      
  15.             </Grid.RowDefinitions>      
  16.             <Grid Grid.Row="0">      
  17.                 <StackLayout HorizontalOptions="FillAndExpand" Orientation="Horizontal">      
  18.                     <Label Text="Notifications ON/OFF" TextColor="White" FontSize="16" HorizontalOptions="StartAndExpand" VerticalOptions="Center"/>      
  19.                     <Switch IsToggled=" {Binding NotificationONOFF}" HorizontalOptions="EndAndExpand" VerticalOptions="Center"/>      
  20.                 </StackLayout>      
  21.             </Grid>      
  22.             <Grid Grid.Row="1">      
  23.                 <StackLayout HorizontalOptions="FillAndExpand" Orientation="Horizontal">      
  24.                 <Label Text="SET TIME" HorizontalOptions="StartAndExpand" FontSize="15" TextColor="White" VerticalOptions="Center"/>      
  25.                 <TimePicker HorizontalOptions="EndAndExpand" Time=" {Binding SelectedTime}" TextColor="White" BackgroundColor="Transparent" Format="t"/>      
  26.                 </StackLayout>      
  27.             </Grid>      
  28.             <Grid Grid.Row="2">      
  29.                  <StackLayout HorizontalOptions="FillAndExpand" Orientation="Horizontal">      
  30.                 <Label Text="SET DATE" TextColor="White" FontSize="15" VerticalOptions="Center" HorizontalOptions="StartAndExpand"/>      
  31.                  <DatePicker HorizontalOptions="EndAndExpand" Date=" {Binding SelectedDate}" TextColor="White"  BackgroundColor="Transparent" Format="MM-dd-yyyy"/>      
  32.                 </StackLayout>      
  33.             </Grid>      
  34.             <Grid Grid.Row="3">      
  35.                 <StackLayout HorizontalOptions="FillAndExpand" Spacing="10">      
  36.                  <Label Text="Enter Message" FontSize="15" HorizontalOptions="StartAndExpand" TextColor="White" VerticalOptions="Center"/>      
  37.                  <Editor HeightRequest="120" Text=" {Binding MessageText}" TextColor="Purple" BackgroundColor="White" HorizontalOptions="FillAndExpand"/>      
  38.                 </StackLayout>      
  39.             </Grid>      
  40.             <Grid Grid.Row="4">      
  41.                 <Button Text="Save" Command="{Binding SaveCommand}" FontSize="15" TextColor="White" BackgroundColor="Purple"  HorizontalOptions="FillAndExpand" BorderRadius="15"/>      
  42.             </Grid>      
  43.         </Grid>      
  44.     </ContentPage.Content>      
  45. </ContentPage>     

LocalNotificationPage.xaml.cs

Make BindingContext in code behind with LocalNotificationPageViewModel.

  1. using LocalNotifcationDemo.ViewModels;    
  2. using Xamarin.Forms;    
  3. namespace LocalNotifcationDemo.Views {    
  4.     public partial class LocalNotificationPage: ContentPage {    
  5.         public LocalNotificationPage() {    
  6.             InitializeComponent();    
  7.             BindingContext = new LocalNotificationPageViewModel();    
  8.         }    
  9.     }    
  10. }   

ViewModels:

LocalNotificationPageViewModel.cs

In this ViewModel, we are calling DependencyService and passing the selected date, time and message text to the service.

Properties
  • NotificationONOFF
  • SelectedDate
  • SelectedTime
  • MessageText
Commands
  • SaveCommand  
  1. using System;      
  2. using System.Collections.Generic;      
  3. using System.ComponentModel;      
  4. using System.Globalization;      
  5. using System.Runtime.CompilerServices;      
  6. using LocalNotificationDemo.DependencyServices;      
  7. using Xamarin.Forms;      
  8. namespace LocalNotificationDemo.ViewModels {      
  9.     public class LocalNotificationPageViewModel: INotifyPropertyChanged {      
  10.         Command _saveCommand;      
  11.         public Command SaveCommand {      
  12.             get {      
  13.                 return _saveCommand;      
  14.             }      
  15.             set {      
  16.                 SetProperty(ref _saveCommand, value);      
  17.             }      
  18.         }      
  19.         bool _notificationONOFF;      
  20.         public bool NotificationONOFF {      
  21.             get {      
  22.                 return _notificationONOFF;      
  23.             }      
  24.             set {      
  25.                 SetProperty(ref _notificationONOFF, value);      
  26.                 Switch_Toggled();      
  27.             }      
  28.         }      
  29.         void Switch_Toggled() {      
  30.             if (NotificationONOFF == false) {      
  31.                 MessageText = string.Empty;      
  32.                 SelectedTime = DateTime.Now.TimeOfDay;      
  33.                 SelectedDate = DateTime.Today;      
  34.                 DependencyService.Get<ILocalNotificationService>().Cancel(0);      
  35.             }      
  36.         }      
  37.         DateTime _selectedDate = DateTime.Today;      
  38.         public DateTime SelectedDate {      
  39.             get {      
  40.                 return _selectedDate;      
  41.             }      
  42.             set {      
  43.                 SetProperty(ref _selectedDate, value);      
  44.             }      
  45.         }      
  46.         TimeSpan _selectedTime = DateTime.Now.TimeOfDay;      
  47.         public TimeSpan SelectedTime {      
  48.             get {      
  49.                 return _selectedTime;      
  50.             }      
  51.             set {      
  52.                 SetProperty(ref _selectedTime, value);      
  53.             }      
  54.         }      
  55.         string _messageText;      
  56.         public string MessageText {      
  57.             get {      
  58.                 return _messageText;      
  59.             }      
  60.             set {      
  61.                 SetProperty(ref _messageText, value);      
  62.             }      
  63.         }      
  64.         public LocalNotificationPageViewModel() {      
  65.             SaveCommand = new Command(() => SaveLocalNotification());      
  66.         }      
  67.         void SaveLocalNotification() {      
  68.             if (NotificationONOFF == true) {      
  69.                 var date = (SelectedDate.Date.Month.ToString("00") + "-" + SelectedDate.Date.Day.ToString("00") + "-" + SelectedDate.Date.Year.ToString());      
  70.                 var time = Convert.ToDateTime(SelectedTime.ToString()).ToString("HH:mm");      
  71.                 var dateTime = date + " " + time;      
  72.                 var selectedDateTime = DateTime.ParseExact(dateTime, "MM-dd-yyyy HH:mm", CultureInfo.InvariantCulture);      
  73.                 if (!string.IsNullOrEmpty(MessageText)) {      
  74.                     DependencyService.Get<ILocalNotificationService>().Cancel(0);      
  75.     DependencyService.Get<ILocalNotificationService().LocalNotification("Local Notification", MessageText, 0, selectedDateTime);      
  76.                     App.Current.MainPage.DisplayAlert("LocalNotificationDemo""Notification details saved successfully ""Ok");      
  77.                 } else {      
  78.                     App.Current.MainPage.DisplayAlert("LocalNotificationDemo""Please enter meassage""OK");      
  79.                 }      
  80.             } else {      
  81.                 App.Current.MainPage.DisplayAlert("LocalNotificationDemo""Please switch on notification""OK");      
  82.             }      
  83.         }      
  84.         protected bool SetProperty<T> (ref T backingStore, T value, [CallerMemberName] string propertyName = "", Action onChanged = null) {      
  85.             if (EqualityComparer<T>.Default.Equals(backingStore, value))     
  86.                  return false;      
  87.             backingStore = value;      
  88.             onChanged?.Invoke();      
  89.             OnPropertyChanged(propertyName);      
  90.             return true;      
  91.         }      
  92.         public event PropertyChangedEventHandler PropertyChanged;      
  93.         protected void OnPropertyChanged([CallerMemberName] string propertyName = "") {      
  94.             var changed = PropertyChanged;      
  95.             if (changed == null)     
  96.                  return;      
  97.             changed.Invoke(thisnew PropertyChangedEventArgs(propertyName));      
  98.         }      
  99.     }      
  100. }    

Portable class library (PCL):

Create an interface ILocalNotificationService inside the DependencyServices folder that has the methods declaration of LocalNotification and Cancel.

ILocalNotificationService.cs


  1. using System;      
  2.       
  3. namespace LocalNotificationDemo.DependencyServices      
  4. {      
  5.     public interface ILocalNotificationService      
  6.     {      
  7.         void LocalNotification(string title, string body, int id, DateTime notifyTime);      
  8.         void Cancel(int id);      
  9.     }      
  10. }    

Models:
In models, we need to create the below class with Title, Body, ID, IconId and NotifyTime. It will be useful to pass a local notification data to BroadcastReceiver receiver for repeating in Android.

LocalNotification.cs


  1. using System;      
  2. namespace LocalNotificationDemo.Models{      
  3.           
  4.     public class LocalNotification{      
  5.       
  6.         public string Title { getset; }      
  7.       
  8.         public string Body { getset; }      
  9.       
  10.         public int Id { getset; }      
  11.       
  12.         public int IconId { getset; }      
  13.       
  14.         public DateTime NotifyTime { getset; }      
  15.     }      
  16. }     

Xamarin.Android

Create a class LocalNotificationService and need to implement LocalNotification and Cancel methods like below. In this, we have created BroadcastReceiver for repeating notification.

LocalNotificationService.cs


  1. using System;      
  2. using System.IO;      
  3. using System.Xml.Serialization;      
  4. using Android.App;      
  5. using Android.Content;      
  6. using Android.Media;      
  7. using Android.Support.V4.App;      
  8. using Java.Lang;      
  9. using LocalNotificationDemo.DependencyServices.Droid;      
  10. using LocalNotificationDemo.Droid;      
  11. using LocalNotificationDemo.Models;      
  12.       
  13. [assembly: Xamarin.Forms.Dependency(typeof(LocalNotificationService))]      
  14.       
  15. namespace LocalNotificationDemo.DependencyServices.Droid{      
  16.           
  17.     public class LocalNotificationService : ILocalNotificationService      
  18.     {      
  19.         int _notificationIconId { getset; }      
  20.         readonly DateTime _jan1st1970 = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);      
  21.         internal string _randomNumber;      
  22.       
  23.         public void LocalNotification(string title, string body, int id, DateTime notifyTime){      
  24.                   
  25.             //long repeateDay = 1000 * 60 * 60 * 24;      
  26.             long repeateForMinute = 60000; // In milliseconds     
  27.             long totalMilliSeconds = (long)(notifyTime.ToUniversalTime() - _jan1st1970).TotalMilliseconds;      
  28.             if (totalMilliSeconds < JavaSystem.CurrentTimeMillis()){      
  29.                 totalMilliSeconds = totalMilliSeconds + repeateForMinute;      
  30.             }      
  31.       
  32.             var intent = CreateIntent(id);      
  33.             var localNotification = new LocalNotification();      
  34.             localNotification.Title = title;      
  35.             localNotification.Body = body;      
  36.             localNotification.Id = id;      
  37.             localNotification.NotifyTime = notifyTime;      
  38.       
  39.             if (_notificationIconId != 0){      
  40.                 localNotification.IconId = _notificationIconId;      
  41.             }      
  42.             else{      
  43.                 localNotification.IconId = Resource.Drawable.notificationgrey;      
  44.             }      
  45.       
  46.             var serializedNotification = SerializeNotification(localNotification);      
  47.             intent.PutExtra(ScheduledAlarmHandler.LocalNotificationKey, serializedNotification);      
  48.       
  49.             Random generator = new Random();        
  50.             _randomNumber = generator.Next(100000, 999999).ToString("D6");       
  51.       
  52.             var pendingIntent = PendingIntent.GetBroadcast(Application.Context, Convert.ToInt32(_randomNumber), intent, PendingIntentFlags.Immutable);      
  53.             var alarmManager = GetAlarmManager();      
  54.             alarmManager.SetRepeating(AlarmType.RtcWakeup, totalMilliSeconds, repeateForMinute, pendingIntent);      
  55.         }      
  56.            
  57.         public void Cancel(int id){      
  58.                   
  59.             var intent = CreateIntent(id);      
  60.             var pendingIntent = PendingIntent.GetBroadcast(Application.Context, Convert.ToInt32(_randomNumber), intent, PendingIntentFlags.Immutable);      
  61.             var alarmManager = GetAlarmManager();      
  62.             alarmManager.Cancel(pendingIntent);      
  63.             var notificationManager = NotificationManagerCompat.From(Application.Context);      
  64.             notificationManager.CancelAll();      
  65.             notificationManager.Cancel(id);      
  66.         }      
  67.       
  68.         public static Intent GetLauncherActivity(){      
  69.                   
  70.             var packageName = Application.Context.PackageName;      
  71.             return Application.Context.PackageManager.GetLaunchIntentForPackage(packageName);      
  72.         }      
  73.       
  74.       
  75.         private Intent CreateIntent(int id){      
  76.                   
  77.             return new Intent(Application.Context, typeof(ScheduledAlarmHandler))      
  78.                 .SetAction("LocalNotifierIntent" + id);      
  79.         }      
  80.       
  81.         private AlarmManager GetAlarmManager(){      
  82.                   
  83.             var alarmManager = Application.Context.GetSystemService(Context.AlarmService) as AlarmManager;      
  84.             return alarmManager;      
  85.         }      
  86.       
  87.         private string SerializeNotification(LocalNotification notification){      
  88.                   
  89.             var xmlSerializer = new XmlSerializer(notification.GetType());      
  90.       
  91.             using (var stringWriter = new StringWriter()){      
  92.                 xmlSerializer.Serialize(stringWriter, notification);      
  93.                 return stringWriter.ToString();      
  94.             }      
  95.         }      
  96.     }      
  97.       
  98.     [BroadcastReceiver(Enabled = true, Label = "Local Notifications Broadcast Receiver")]      
  99.     public class ScheduledAlarmHandler : BroadcastReceiver{      
  100.       
  101.         public const string LocalNotificationKey = "LocalNotification";      
  102.       
  103.         public override void OnReceive(Context context, Intent intent){      
  104.             var extra = intent.GetStringExtra(LocalNotificationKey);      
  105.             var notification = DeserializeNotification(extra);      
  106.             //Generating notification      
  107.             var builder = new NotificationCompat.Builder(Application.Context)      
  108.                 .SetContentTitle(notification.Title)      
  109.                 .SetContentText(notification.Body)      
  110.                 .SetSmallIcon(notification.IconId)      
  111.                 .SetSound(RingtoneManager.GetDefaultUri(RingtoneType.Ringtone))      
  112.                 .SetAutoCancel(true);      
  113.       
  114.             var resultIntent = LocalNotificationService.GetLauncherActivity();      
  115.             resultIntent.SetFlags(ActivityFlags.NewTask | ActivityFlags.ClearTask);      
  116.             var stackBuilder = Android.Support.V4.App.TaskStackBuilder.Create(Application.Context);      
  117.             stackBuilder.AddNextIntent(resultIntent);      
  118.       
  119.             Random random = new Random();      
  120.             int randomNumber = random.Next(9999 - 1000) + 1000;       
  121.       
  122.             var resultPendingIntent =      
  123.                 stackBuilder.GetPendingIntent(randomNumber, (int)PendingIntentFlags.Immutable);      
  124.             builder.SetContentIntent(resultPendingIntent);      
  125.             // Sending notification      
  126.             var notificationManager = NotificationManagerCompat.From(Application.Context);      
  127.             notificationManager.Notify(randomNumber, builder.Build());      
  128.         }      
  129.       
  130.         private LocalNotification DeserializeNotification(string notificationString){      
  131.                   
  132.             var xmlSerializer = new XmlSerializer(typeof(LocalNotification));      
  133.             using (var stringReader = new StringReader(notificationString))      
  134.             {      
  135.                 var notification = (LocalNotification)xmlSerializer.Deserialize(stringReader);      
  136.                 return notification;      
  137.             }      
  138.         }      
  139.     }      
  140. }     

Note
In Android, we are using AlarmManger for repeating location notifications.

Xamarin.iOS
Now, create a class LocalNotificationService and implement LocalNotification and cancel method like below.

LocalNotificationService.cs


  1. using System;      
  2. using UIKit;      
  3. using Foundation;      
  4. using System.Linq;      
  5. using Xamarin.Forms;      
  6. using LocalNotificationDemo.iOS;      
  7. using LocalNotificationDemo.DependencyServices;      
  8.       
  9. [assembly: Dependency(typeof(LocalNotificationService))]      
  10. namespace LocalNotificationDemo.iOS{      
  11.           
  12.     public class LocalNotificationService : ILocalNotificationService{      
  13.               
  14.         const string NotificationKey = "LocalNotificationKey";      
  15.       
  16.         public void LocalNotification(string title, string body, int id, DateTime notifyTime){      
  17.                   
  18.             var notification = new UILocalNotification{      
  19.                       
  20.                 AlertTitle = title,      
  21.                 AlertBody = body,      
  22.                 SoundName = UILocalNotification.DefaultSoundName,      
  23.                 FireDate = notifyTime.ToNSDate(),      
  24.                 RepeatInterval = NSCalendarUnit.Minute,      
  25.       
  26.                 UserInfo = NSDictionary.FromObjectAndKey(NSObject.FromObject(id), NSObject.FromObject(NotificationKey))      
  27.             };      
  28.             UIApplication.SharedApplication.ScheduleLocalNotification(notification);      
  29.         }      
  30.       
  31.         public void Cancel(int id){      
  32.                   
  33.             var notifications = UIApplication.SharedApplication.ScheduledLocalNotifications;      
  34.             var notification = notifications.Where(n => n.UserInfo.ContainsKey(NSObject.FromObject(NotificationKey)))      
  35.                 .FirstOrDefault(n => n.UserInfo[NotificationKey].Equals(NSObject.FromObject(id)));      
  36.             UIApplication.SharedApplication.CancelAllLocalNotifications();      
  37.             if (notification != null){      
  38.                 UIApplication.SharedApplication.CancelLocalNotification(notification);      
  39.                 UIApplication.SharedApplication.CancelAllLocalNotifications();      
  40.             }      
  41.         }      
  42.     }      
  43.       
  44.     public static class DateTimeExtensions{      
  45.               
  46.         static DateTime nsUtcRef = new DateTime(2001, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);      
  47.         // last zero is milliseconds      
  48.       
  49.         public static double SecondsSinceNSRefenceDate(this DateTime dt){      
  50.             return (dt.ToUniversalTime() - nsUtcRef).TotalSeconds;      
  51.         }      
  52.       
  53.         public static NSDate ToNSDate(this DateTime dt){      
  54.             return NSDate.FromTimeIntervalSinceReferenceDate(dt.SecondsSinceNSRefenceDate());      
  55.         }      
  56.     }      
  57. }   
  
Note:
In iOS, UILocationNotification is having RepeatIntervel property. It will be useful to repeat the notification by Day/Minute.


AppDelegate.cs

In AppDelegate.cs, first, add the following code to the FinishedLaunching method. We have checked to see if the device is running iOS 8. If so, we are required to ask for the user's permission to receive notifications.


  1. // We have checked to see if the device is running iOS 8, if so we are required to ask for the user's permission to receive notifications      
  2.             if (UIDevice.CurrentDevice.CheckSystemVersion(8, 0)){      
  3.                 var notificationSettings = UIUserNotificationSettings.GetSettingsForTypes(      
  4.                     UIUserNotificationType.Alert | UIUserNotificationType.Badge | UIUserNotificationType.Sound, null      
  5.                 );      
  6.       
  7.                 app.RegisterUserNotificationSettings(notificationSettings);      
  8.  }     

Still, in AppDelegate.cs, add the following method which will be called when a notification is received.


  1. public override void ReceivedLocalNotification(UIApplication application, UILocalNotification notification){      
  2.                   
  3.             UIAlertController okayAlertController = UIAlertController.Create(notification.AlertAction, notification.AlertBody, UIAlertControllerStyle.Alert);      
  4.             okayAlertController.AddAction(UIAlertAction.Create("OK", UIAlertActionStyle.Default, null));      
  5.             UIApplication.SharedApplication.ApplicationIconBadgeNumber = 0;      
  6.         }    
   
We need to handle the case where the notification was launched because of a local notification. Edit the method FinishedLaunching in the AppDelegate to include the following snippet of code.


  1. // check for a notification      
  2.             if (options != null){      
  3.                 if (options.ContainsKey(UIApplication.LaunchOptionsLocalNotificationKey)){      
  4.                     UILocalNotification localNotification = options[UIApplication.LaunchOptionsLocalNotificationKey] as UILocalNotification;      
  5.                     if (localNotification != null){      
  6.                         new UIAlertView(localNotification.AlertAction, localNotification.AlertBody, null"OK"null).Show();      
  7.                         // reset our badge      
  8.                         UIApplication.SharedApplication.ApplicationIconBadgeNumber = 0;      
  9.                     }      
  10.            }      
  11. }  
   
On iOS 8 you will be prompted to allow notifications like below.


Output:

Please download the source code from here.

     

No comments:

Post a Comment