Skip to main content

Manage Reminders

  • 13 minutes to read

A reminder sends an alert at a specified time before an appointment’s start time. An appointment can have multiple reminders.

This example describes how to:

  • Enable the Edit Appointment form that allows users to add reminders to appointments.
  • Plug reminders into the iOS and Android alarm systems so that they send notifications outside the application’s UI.
    Each notification includes appointment subject and time interval.
  • Handle reminder notifications to open an edit form for the corresponding appointments when a user taps notifications.

View Example

Note

In this example, the scheduler’s data storage is populated with generated data when a user starts the application. Reminders added to appointments remain functional while the application is running (even if it is hidden). When you close the application, system shows notifications for all the reminders but cannot access appointments and open the appointment edit form because the appointment storage is unavailable.
To save appointment data when users restart the application, bind the scheduler data storage to an external data source.

Create a New Application and Add a Scheduler

  1. Create a new Xamarin.Forms cross-platform solution and include the DevExpress Scheduler component.

  2. In the MainPage.xaml file of the .NET Standard project containing the shared code, use the dxsch prefix to declare a namespace and add a DayView to a content page:

    <ContentPage 
        xmlns="http://xamarin.com/schemas/2014/forms"
        xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
        xmlns:dxsch="http://schemas.devexpress.com/xamarin/2014/forms/scheduler"
        x:Class="Scheduler_Reminders.MainPage">
        <dxsch:DayView>
        </dxsch:DayView>
    </ContentPage>
    

Prepare a Data Source

  1. Add the following classes that represent data objects in the application:

    • ReminderAppointment - An appointment to be rendered in the scheduler.
    • AppointmentData - A data source that stores data for the scheduler. The AppointmentRepository property provides access to appointments.
    using System;
    using System.Collections.ObjectModel;
    
    namespace Scheduler_Reminders {
    
        public class ReminderAppointment {
            public int Id { get; set; }
            public bool AllDay { get; set; }
            public DateTime Start { get; set; }
            public DateTime End { get; set; }
            public string Subject { get; set; }
            public int Label { get; set; }
            public int Status { get; set; }
            public string RecurrenceInfo { get; set; }
            public string ReminderInfo { get; set; }
        }
    
        class AppointmentData {
            static Random rnd = new Random();
    
            void CreateAppointments() {
                int appointmentId = 1;
                DateTime start;
                TimeSpan duration;
    
                ObservableCollection<ReminderAppointment> result = 
                                            new ObservableCollection<ReminderAppointment>();
    
                for (int i = 0; i < 5; i++) {
                    ReminderAppointment appointmentWithReminder = new ReminderAppointment();
                    start = DateTime.Now.AddMinutes(1);
                    duration = TimeSpan.FromMinutes(rnd.Next(30, 50));
                    appointmentWithReminder.Id = appointmentId;
                    appointmentWithReminder.Start = start;
                    appointmentWithReminder.End = start.Add(duration);
                    appointmentWithReminder.Subject = "Test Appointment";
                    appointmentWithReminder.Label = rnd.Next(1, 10);
                    appointmentWithReminder.Status = rnd.Next(0, 4);
                    result.Add(appointmentWithReminder);
                    appointmentId++;
                }
                AppointmentRepository = result;
            }
    
            public ObservableCollection<ReminderAppointment> AppointmentRepository { get; private set; }
    
            public AppointmentData() {
                CreateAppointments();
            }
        }
    }
    
  2. Create the ViewModel class that provides content the scheduler should display.

    using System.Collections.Generic;
    using System.ComponentModel;
    
    namespace Scheduler_Reminders {
        public class ViewModel : INotifyPropertyChanged {
            readonly AppointmentData data;
    
            public event PropertyChangedEventHandler PropertyChanged;
            public IReadOnlyList<ReminderAppointment> AppointmentRepository 
                                        { get => data.AppointmentRepository; }
    
            public ViewModel() {
                data = new AppointmentData();
            }
    
            protected void RaisePropertyChanged(string name) {
                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs(name));
            }
        }
    }
    

Bind Scheduler to Data

In the MainPage.xaml file:

  1. Assign the ViewModel object to the content page’s BindingContext property.
  2. Set the DayView.DataStorage property to a SchedulerDataStorage object and use the SchedulerDataStorage.DataSource property to bind the day view to a data source.
  3. Set the DataSource.AppointmentsSource property to the collection of appointment objects.
  4. Use the DataSource.AppointmentMappings property to map appointment properties to the ReminderAppointment class properties.
<ContentPage 
    xmlns="http://xamarin.com/schemas/2014/forms"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:dxsch="http://schemas.devexpress.com/xamarin/2014/forms/scheduler"
    xmlns:local="clr-namespace:Scheduler_Reminders"
    x:Class="Scheduler_Reminders.MainPage">
    <ContentPage.BindingContext>
        <local:ViewModel/>
    </ContentPage.BindingContext>
    <dxsch:DayView SnapToCellsMode="Never">
        <dxsch:DayView.DataStorage>
            <dxsch:SchedulerDataStorage x:Name="storage">
                <dxsch:SchedulerDataStorage.DataSource>
                    <dxsch:DataSource AppointmentsSource="{Binding AppointmentRepository}" >
                        <dxsch:DataSource.AppointmentMappings>
                            <dxsch:AppointmentMappings 
                                AllDay="AllDay"
                                Start="Start"
                                End="End"
                                Subject="Subject"
                                LabelId="Label"
                                StatusId="Status"
                                RecurrenceInfo="RecurrenceInfo"
                                Reminder="ReminderInfo"/>
                        </dxsch:DataSource.AppointmentMappings>
                    </dxsch:DataSource>
                </dxsch:SchedulerDataStorage.DataSource>
            </dxsch:SchedulerDataStorage>
        </dxsch:DayView.DataStorage>
    </dxsch:DayView>
</ContentPage>

The scheduler now displays five generated appointments in a day view.

Reminders Example - Appointments

Enable the Edit Appointment Form

  1. Subscribe to the view’s Tap event.

    <dxsch:DayView SnapToCellsMode="Never" Tap="OnTap">
        <dxsch:DayView.DataStorage>
            <dxsch:SchedulerDataStorage x:Name="storage">
                <!-- ... -->
            </dxsch:SchedulerDataStorage>
        </dxsch:DayView.DataStorage>
    </dxsch:DayView>
    
  2. In the event handler:

    1. Create an AppointmentEditPage instance. Use one of the following constructors:

    2. Call the Navigation.PushAsync method and pass the created page as a parameter.

    using System;
    using Xamarin.Forms;
    using DevExpress.XamarinForms.Scheduler;
    
    namespace Scheduler_Reminders {
        public partial class MainPage : ContentPage {
            readonly RemindersNotificationCenter remindersNotificationCenter = 
                                                    new RemindersNotificationCenter();
    
            public MainPage() {
                DevExpress.XamarinForms.Scheduler.Initializer.Init();
                InitializeComponent();
            }
    
            private void OnTap(object sender, SchedulerGestureEventArgs e) {
                if (e.AppointmentInfo == null) {
                    ShowNewAppointmentEditPage(e.IntervalInfo);
                    return;
                }
                AppointmentItem appointment = e.AppointmentInfo.Appointment;
                ShowAppointmentEditPage(appointment);
            }
    
            private void ShowAppointmentEditPage(AppointmentItem appointment) {
                AppointmentEditPage appEditPage = new AppointmentEditPage(appointment, this.storage);
                Navigation.PushAsync(appEditPage);
            }
    
            private void ShowNewAppointmentEditPage(IntervalInfo info) {
                AppointmentEditPage appEditPage = new AppointmentEditPage(info.Start, info.End,
                                                                          info.AllDay, this.storage);
                Navigation.PushAsync(appEditPage);
            }
        }
    }
    
  3. In the App.xaml.cs file, assign a NavigationPage instance to the Application.MainPage property and add the MainPage content page to the navigation stack (the application’s root page):

    namespace Scheduler_Reminders {
        public partial class App : Application {
            readonly MainPage page;
            public App() {
                InitializeComponent();
    
                // MainPage = new MainPage();
                page = new MainPage();
                MainPage = new NavigationPage(page);
            }
            // ...
        }
    }
    

The scheduler now displays the Edit Appointment form when you tap an appointment or empty cell within the day view. In this form, you can tap Add a reminder and select the time before the appointment starts when you want to get a reminder.

Plug Reminders into Platform-Specific Alarm Systems

The iOS and Android notification mechanisms require different code to integrate scheduler reminders. Use the Xamarin.Forms DependencyService to provide different implementation for each platform and invoke it from shared code. To do this, declare an interface for the functionality in the shared project, implement this interface for iOS and Android in platform-specific projects, and then resolve platform implementations from the shared code.

  1. In the shared project, create the INotificationCenter interface with the UpdateNotifications method that should request the specified number of reminders from the scheduler storage.

    using Xamarin.Forms;
    using System.Collections.Generic;
    using DevExpress.XamarinForms.Scheduler;
    
    namespace Scheduler_Reminders {
        public interface INotificationCenter
        {
            void UpdateNotifications(IList<TriggeredReminder> reminders, int maxCount);
        }
    }
    
  2. Implement the interface on each platform.

    • In the iOS project:

      1. Add the DevExpress.XamarinForms.Scheduler.dll assembly reference.
      2. Create the NotificationCenter class that implements the INotificationCenter interface. Use the UNUserNotificationCenter system to send notifications.
      3. Register the iOS implementation to enable the DependencyService to find the class and supply it instead of the interface at runtime. To do this, declare the Dependency attribute above the namespace.

        using Foundation;
        using System;
        using System.Collections.Generic;
        using System.Diagnostics;
        using System.Threading.Tasks;
        using UserNotifications;
        using Xamarin.Forms;
        using DevExpress.XamarinForms.Scheduler;
        
        [assembly: Dependency(typeof(Scheduler_Reminders.iOS.NotificationCenter))]
        
        namespace Scheduler_Reminders.iOS {
            public class NotificationCenter : INotificationCenter {
                public class ReminderIdentifier {
                    public Guid Guid { get; private set; }
                    public int RecurrenceIndex { get; private set; }
        
                    public ReminderIdentifier(Guid guid, int recurrenceIndex) {
                        Guid = guid;
                        RecurrenceIndex = recurrenceIndex;
                    }
                }
                public static string SerializeReminder(TriggeredReminder reminder) {
                    return reminder.Id + ":" + reminder.Appointment.RecurrenceIndex.ToString();
                }
                public static ReminderIdentifier DeserializeReminder(string reminderIdentifier) {
                    string[] splitData = reminderIdentifier.Split(':');
                    string recurrenceId = splitData[0];
                    int recurrenceIndex = Int32.Parse(splitData[1]);
                    Guid reminderGuid = Guid.Parse(recurrenceId);
                    return new ReminderIdentifier(reminderGuid, recurrenceIndex);
                }
        
                readonly ReminderNotificationCenterVersion notificationsCore;
        
                public NotificationCenter() {
                    notificationsCore = new ReminderNotificationCenterVersion();
                }
        
                public void UpdateNotifications(IList<TriggeredReminder> reminders, int maxCount) {
                    notificationsCore.UpdateRemindersNotifications(reminders);
                }
            }
        
        
            // Send notifications via the UNUserNotificationCenter system.
            public class ReminderNotificationCenterVersion {
                readonly UNUserNotificationCenter notificationCenter = 
                                                        UNUserNotificationCenter.Current;
        
                string CreateMessageContent(TriggeredReminder reminder) {
                    return reminder.Appointment.Interval.ToString("{0:g} - {1:g}", null);
                }
        
                Task<Tuple<bool, NSError>> RequestUserAccess() {
                    UNAuthorizationOptions options = UNAuthorizationOptions.Alert | 
                                                     UNAuthorizationOptions.Sound | 
                                                     UNAuthorizationOptions.Badge;
                    return notificationCenter.RequestAuthorizationAsync(options);
                }
                async void ScheduleReminderNotification(TriggeredReminder reminder, int badge) {
                    UNMutableNotificationContent content = new UNMutableNotificationContent() {
                        Title = reminder.Appointment.Subject,
                        Body = CreateMessageContent(reminder),
                        Sound = UNNotificationSound.Default,
                        Badge = badge,
                    };
                    NSDateComponents dateComponents = new NSDateComponents() {
                        Second = reminder.AlertTime.Second,
                        Minute = reminder.AlertTime.Minute,
                        Hour = reminder.AlertTime.Hour,
                        Day = reminder.AlertTime.Day,
                        Month = reminder.AlertTime.Month,
                        Year = reminder.AlertTime.Year,
                        TimeZone = NSTimeZone.SystemTimeZone,
                    };
                    UNCalendarNotificationTrigger trigger = 
                        UNCalendarNotificationTrigger.CreateTrigger(dateComponents, false);
                    string identifier = NotificationCenter.SerializeReminder(reminder);
                    UNNotificationRequest request = 
                        UNNotificationRequest.FromIdentifier(identifier, content, trigger);
                    await notificationCenter.AddNotificationRequestAsync(request);
                }
        
                public async void UpdateRemindersNotifications(IList<TriggeredReminder> featureReminders) {
                    Tuple<bool, NSError> authResult = await RequestUserAccess();
                    if (!authResult.Item1) {
                        Debug.WriteLine("User denied access to notifications");
                        return;
                    }
                    notificationCenter.RemoveAllPendingNotificationRequests();
                    for (int i = 0; i < featureReminders.Count; i++) {
                        ScheduleReminderNotification(featureReminders[i], i + 1);
                    }
                }
            }
        }
        
    • In the Android project:

      1. Add the DevExpress.XamarinForms.Scheduler.dll assembly reference.
      2. Create the NotificationCenter class that implements the INotificationCenter interface. Use AlarmManager that provides access to the Android alarm services.
      3. Register the Android implementation to enable the DependencyService to find the class and supply it instead of the interface at runtime. To do this, declare the Dependency attribute above the namespace.

        using Android.App;
        using Android.Content;
        using AndroidX.Core.App;
        using Java.Sql;
        using Scheduler_Reminders.Droid;
        using System;
        using System.Collections.Generic;
        using Xamarin.Forms;
        using AAplication = Android.App.Application;
        using DevExpress.XamarinForms.Scheduler;
        
        [assembly: Dependency(typeof(NotificationCenter))]
        
        namespace Scheduler_Reminders.Droid {
            public class NotificationCenter : INotificationCenter {
                static Date ToNativeDate(DateTime dateTime) {
                    long dateTimeUtcAsMilliseconds = 
                        (long)dateTime.ToUniversalTime().
                        Subtract(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalMilliseconds;
                    return new Date(dateTimeUtcAsMilliseconds);
                }
        
                void INotificationCenter.UpdateNotifications(IList<TriggeredReminder> reminders, 
                                                            int maxCount) {
                    AlarmManager alarm = 
                            (AlarmManager)AAplication.Context.GetSystemService(Context.AlarmService);
                    for (int i = 0; i < maxCount; i++) {
                        if (i < reminders.Count) {
                            TriggeredReminder reminder = reminders[i];
                            var pendingIntent = PendingIntent.GetBroadcast(AAplication.Context, i, 
                                                            CreateIntent(reminder), 
                                                            PendingIntentFlags.UpdateCurrent);
                            alarm.Cancel(pendingIntent);
                            AlarmManagerCompat.SetExactAndAllowWhileIdle(alarm, 
                                                            (int)AlarmType.RtcWakeup, 
                                                            ToNativeDate(reminder.AlertTime).Time, 
                                                            pendingIntent);
                        }
                        else {
                            var pendingIntent = PendingIntent.GetBroadcast(AAplication.Context, i, 
                                                            CreateIntent(), 
                                                            PendingIntentFlags.UpdateCurrent);
                            alarm.Cancel(pendingIntent);
                        }
                    }
                }
        
                Intent CreateIntent() {
                    return new Intent(AAplication.Context, typeof(NotificationAlarmHandler)).
                                SetAction(NotificationAlarmHandler.NotificationHandler);
                }
                Intent CreateIntent(string id, int recurrenceIndex, string subject, string interval) {
                    return CreateIntent()
                        .PutExtra(NotificationAlarmHandler.ReminderId, id)
                        .PutExtra(NotificationAlarmHandler.RecurrenceIndex, recurrenceIndex)
                        .PutExtra(NotificationAlarmHandler.Subject, subject)
                        .PutExtra(NotificationAlarmHandler.Interval, interval);
                }
                Intent CreateIntent(TriggeredReminder reminder) {
                    AppointmentItem appointment = reminder.Appointment;
                    return CreateIntent(reminder.Id.ToString(), 
                                        appointment.RecurrenceIndex, 
                                        appointment.Subject, 
                                        appointment.Interval.ToString("{0:g} - {1:g}", null));
                }
            }
        }
        
      4. Create the NotificationAlarmHandler class inherited from BroadcastReceive and implement the OnReceive method. Use NotificationManager to show reminder notifications.

        Note

        You should provide an application icon for the Android notification template. Copy the icon to the drawable directories of the Android project’s Resources directory, and pass it to the NotificationCompat.Builder‘s SetSmallIcon method.

        using System;
        using Android.App;
        using Android.Content;
        using Android.OS;
        using AndroidX.Core.App;
        
        namespace Scheduler_Reminders.Droid {
            [BroadcastReceiver(Enabled = true, Exported = true)]
            [IntentFilter(new[] { NotificationHandler })]
            public class NotificationAlarmHandler : BroadcastReceiver {
                public const string NotificationHandler = "NotificationAlarmHandler";
                public const string ReminderId = "ReminderId";
                public const string RecurrenceIndex = "RecurrenceIndex";
                public const string Subject = "Subject";
                public const string Interval = "Interval";
        
                static Intent GetLauncherActivity() {
                    var packageName = Application.Context.PackageName;
                    return Application.Context.PackageManager.GetLaunchIntentForPackage(packageName);
                }
        
                string CurrentPackageName => Application.Context.PackageName;
                string ReminderChannelId => $"{CurrentPackageName}.reminders";
                NotificationManager NotificationManager => (NotificationManager)Application.Context.
                                                GetSystemService(Context.NotificationService);
        
                public NotificationAlarmHandler() {
                    if (Build.VERSION.SdkInt >= BuildVersionCodes.O)
                        NotificationManager.CreateNotificationChannel(
                            new NotificationChannel(ReminderChannelId, "Reminders", 
                                                    NotificationImportance.High));
                }
        
                public override void OnReceive(Context context, Intent intent) {
                    Guid reminderId = intent.GetReminderId();
                    if (reminderId == Guid.Empty)
                        return;
        
                    int notificationId = reminderId.GetHashCode();
        
                    Intent resultIntent = GetLauncherActivity().PutExtras(intent.Extras).
                                          SetFlags(ActivityFlags.SingleTop | ActivityFlags.ClearTop);
        
                    PendingIntent resultPendingIntent = PendingIntent.GetActivity(
                                                            context, 
                                                            notificationId,
                                                            resultIntent, 
                                                            PendingIntentFlags.UpdateCurrent);
        
                    NotificationCompat.Builder builder = 
                                                new NotificationCompat.Builder(Application.Context);
                    builder.SetContentIntent(resultPendingIntent);
                    builder.SetDefaults((int)NotificationDefaults.All);
                    builder.SetContentTitle(intent.GetStringExtra(Subject));
                    builder.SetContentText(intent.GetStringExtra(Interval));
                    builder.SetSmallIcon(Resource.Mipmap.app_icon);
                    builder.SetChannelId(ReminderChannelId);
                    builder.SetPriority((int)NotificationPriority.High);
                    builder.SetAutoCancel(true);
                    builder.SetVisibility((int)NotificationVisibility.Public);
                    NotificationManager.Notify(notificationId, builder.Build());
                }
            }
        
            public static class IntentExtensions {
                public static Guid GetReminderId(this Intent intent) {
                    string guidString = intent.GetStringExtra(NotificationAlarmHandler.ReminderId);
                    try {
                        return Guid.Parse(guidString);
                    }
                    catch {
                        return Guid.Empty;
                    }
                }
        
                public static int GetRecurrenceIndex(this Intent intent) {
                    return intent.GetIntExtra(NotificationAlarmHandler.RecurrenceIndex, -1);
                }
            }
        }
        
  3. In the shared project:

    1. Create the RemindersNotificationCenter class. In its constructor, call the DependencyService.Get<T> method with the INotificationCenter interface to identify the platform and execute the corresponding implementation.
    2. Define the UpdateNotifications method that requests for the specified number of the next reminders from the scheduler storage. Use the SchedulerDataStorage.GetNextReminders method to get the list of the TriggeredReminder objects.

      using Xamarin.Forms;
      using System.Collections.Generic;
      using DevExpress.XamarinForms.Scheduler;
      
      namespace Scheduler_Reminders {
          public interface INotificationCenter {
              void UpdateNotifications(IList<TriggeredReminder> reminders, int maxCount);
          }
      
          public class RemindersNotificationCenter {
              const int MaxNotificationsCount = 32;
      
              readonly INotificationCenter notificationCenter;
      
              public RemindersNotificationCenter() {
                  this.notificationCenter = DependencyService.Get<INotificationCenter>();
              }
      
              public void UpdateNotifications(SchedulerDataStorage storage) {
                  IList<TriggeredReminder> futureReminders = 
                                                  storage.GetNextReminders(MaxNotificationsCount);
                  notificationCenter.UpdateNotifications(futureReminders, MaxNotificationsCount);
              }
          }
      }
      
    3. Subscribe to the SchedulerDataStorage.RemindersChanged event.

      <dxsch:DayView>
          <dxsch:DayView.DataStorage>
              <dxsch:SchedulerDataStorage x:Name="storage" RemindersChanged="OnRemindersChanged">
                  <!-- ... -->
              </dxsch:SchedulerDataStorage>
          </dxsch:DayView.DataStorage>
      </dxsch:DayView>
      
    4. In the MainPage.xaml.cs file, create the RemindersNotificationCenter class instance and call its UpdateNotifications method with the passed scheduler storage in the RemindersChanged event handler.

      using System;
      using Xamarin.Forms;
      using DevExpress.XamarinForms.Scheduler;
      
      namespace Scheduler_Reminders {
          public partial class MainPage : ContentPage {
              readonly RemindersNotificationCenter remindersNotificationCenter = 
                                                              new RemindersNotificationCenter();
              // ...
              void OnRemindersChanged(object sender, EventArgs e) {
                  remindersNotificationCenter.UpdateNotifications(storage);
              }
              // ...
          }
      }
      

Appointment reminders now appear as iOS and Android native notifications.

Start the Edit Appointment Form from a Notification

Configure the notification behavior so that the appointment edit form is opened when a notification is tapped.

  • In the shared project:

    1. In the MainPage.xaml.cs file, define the ShowAppointmentEditPage method with the reminderId and recurrenceIndex parameters. It should identify an appointment by the passed reminder (SchedulerDataStorage.FindAppointmentByReminder) and show the Edit Appointment form for this appointment.

      using System;
      using Xamarin.Forms;
      using DevExpress.XamarinForms.Scheduler;
      
      namespace Scheduler_Reminders {
          public partial class MainPage : ContentPage {
              readonly RemindersNotificationCenter remindersNotificationCenter = 
                                                      new RemindersNotificationCenter();
      
              public MainPage() {
                  DevExpress.XamarinForms.Scheduler.Initializer.Init();
                  InitializeComponent();
              }
      
              // ... 
      
              private void ShowAppointmentEditPage(AppointmentItem appointment) {
                  AppointmentEditPage appEditPage = new AppointmentEditPage(appointment, this.storage);
                  Navigation.PushAsync(appEditPage);
              }
      
              public void ShowAppointmentEditPage(Guid reminderId, int recurrenceIndex) {
                  AppointmentItem appointment = storage.FindAppointmentByReminder(reminderId);
                  if (appointment != null && recurrenceIndex >= 0)
                      appointment = storage.GetOccurrenceOrException(appointment, recurrenceIndex);
                  if (appointment != null)
                      ShowAppointmentEditPage(appointment);
              }
          }
      }
      
    2. In the App.xaml.cs file, define the ProcessNotification method that calls the page’s ShowAppointmentEditPage method.

      using System;
      using Xamarin.Forms;
      
      [assembly: XamlCompilation(XamlCompilationOptions.Compile)]
      namespace Scheduler_Reminders {
          public partial class App : Application {
              readonly MainPage page;
      
              public App() {
                  InitializeComponent();
      
                  page = new MainPage();
                  MainPage = new NavigationPage(page);
              }
      
              // ...
      
              public void ProcessNotification(Guid reminderId, int recurrenceIndex) {
                  if (reminderId == Guid.Empty)
                      return;
                  page.ShowAppointmentEditPage(reminderId, recurrenceIndex);
              }
          }
      }
      
  • In the iOS project’s AppDelegate.cs file:

    1. Create the CustomUserNotificationCenterDelegate class that implements the UNUserNotificationCenterDelegate protocol.
    2. Assign the CustomUserNotificationCenterDelegate object to the shared UNUserNotificationCenter object’s Delegate property.

      using System;
      using Foundation;
      using UIKit;
      using UserNotifications;
      
      namespace Scheduler_Reminders.iOS {
          [Register("AppDelegate")]
          public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate {
              public override bool FinishedLaunching(UIApplication app, NSDictionary options) {
                  global::Xamarin.Forms.Forms.Init();
                  DevExpress.XamarinForms.Scheduler.iOS.Initializer.Init();
                  App formsApplication = new App();
                  UNUserNotificationCenter.Current.Delegate = 
                                          new CustomUserNotificationCenterDelegate(formsApplication);
                  LoadApplication(formsApplication);
      
                  return base.FinishedLaunching(app, options);
              }
          }
      
          public class CustomUserNotificationCenterDelegate : UNUserNotificationCenterDelegate {
              readonly App app;
      
              public CustomUserNotificationCenterDelegate(App app) {
                  this.app = app;
              }
      
              public override void DidReceiveNotificationResponse(UNUserNotificationCenter center, 
                                                                  UNNotificationResponse response, 
                                                                  Action completionHandler) {
                  UIApplication.SharedApplication.ApplicationIconBadgeNumber = 0;
                  string identifier = response.Notification.Request.Identifier;
                  string recurrenceId = identifier.Split(':')[0];
                  int recurrenceIndex = Int32.Parse(identifier.Split(':')[1]);
                  Guid reminderGuid = Guid.Parse(recurrenceId);
                  app.ProcessNotification(reminderGuid, recurrenceIndex);
                  completionHandler();
              }
              public override void WillPresentNotification(
                  UNUserNotificationCenter center, UNNotification notification, 
                  Action<UNNotificationPresentationOptions> completionHandler) {
                  completionHandler(UNNotificationPresentationOptions.Alert | 
                                    UNNotificationPresentationOptions.Badge | 
                                    UNNotificationPresentationOptions.Badge);
              }
          }
      }
      
  • In the Android project’s MainActivity.cs file, override the main activity’s OnNewIntent method to handle a notification when it is tapped.

    using System;
    using Android.App;
    using Android.Content.PM;
    using Android.Runtime;
    using Android.OS;
    using Android.Content;
    using Xamarin.Essentials;
    
    namespace Scheduler_Reminders.Droid {
        [Activity(Label = "Scheduler_Reminders", Icon = "@mipmap/icon", 
              Theme = "@style/MainTheme", MainLauncher = true, 
              ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | 
                                     ConfigChanges.UiMode | ConfigChanges.ScreenLayout | 
                                     ConfigChanges.SmallestScreenSize )]
        public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity {
            protected override void OnCreate(Bundle savedInstanceState) {
                TabLayoutResource = Resource.Layout.Tabbar;
                ToolbarResource = Resource.Layout.Toolbar;
                base.OnCreate(savedInstanceState);
                Xamarin.Essentials.Platform.Init(this, savedInstanceState);
                global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
                LoadApplication(new App());
            }
    
            public override void OnRequestPermissionsResult(int requestCode, string[] permissions, 
                                                            [GeneratedEnum] Permission[] grantResults) {
                Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);
                base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
            }
    
            protected override void OnNewIntent(Intent intent) {
                base.OnNewIntent(intent);
                App application = Xamarin.Forms.Application.Current as App;
                if (application == null) {
                    LoadApplication(new App());
                    return;
                }
                Guid reminderId = intent.GetReminderId();
                if (reminderId != Guid.Empty)
                    application.ProcessNotification(reminderId, intent.GetRecurrenceIndex());
            }
        }
    }
    

The edit form is now shown for an appointment with a reminder when a user taps a notification.