Appointment reminders in .NET MAUI Scheduler (SfScheduler)
21 Jul 202624 minutes to read
The .NET MAUI Scheduler notifies an appointment reminder by using the EnableReminder property and ReminderAlertOpening event. An appointment can have one or more reminders.
NOTE
- As a restriction in enabling toast notification in .NET MAUI Framework, as of now, add an event to notify appointment reminders.
Handling reminders for future appointments
-
Future normal and recurring appointment reminder alerts will be displayed at the reminder alert time.
-
Reminder alert time will be calculated from the appointment start time substrate with reminder time interval. For example, if the appointment time is 3.30 pm and the reminder time interval is
new Timespan (0,15,0), then the reminder alert time is 3.15 pm.
Handling reminders for overdue appointments
-
If a Non-recurring appointment reminder is in the past, the reminder alert is overdue until dismissed.
-
Recurring appointment overdue from the last occurrence date and time until the last occurrence is dismissed.
-
Recurrence appointment due calculated from current date occurrence and is in due until the next reminder time.
-
If the last occurrence in recurrence in series is dismissed, then the recurring pattern appointment is dismissed.
Handling reminders for changed occurrence appointments
-
Once the changed occurrence is moved to the future, then the pattern reminder value will be used for the changed occurrence.
-
When the changed occurrence is restored to a time in the past, then its reminder will be dismissed.
-
Past Changed occurrence reminders will be dismissed.
Enable reminders
Reminders can be enabled by setting the EnableReminder property to true which will trigger the ReminderAlertOpening event to notify appointment reminders. The reminders can be set by using the Reminders property of SchedulerAppointment.
<ContentPage
. . .
xmlns:scheduler="clr-namespace:Syncfusion.Maui.Scheduler;assembly=Syncfusion.Maui.Scheduler">
<scheduler:SfScheduler x:Name="Scheduler"
View ="Week"
EnableReminder="True" >
</scheduler:SfScheduler>
</ContentPage>Adding Reminders
Configure the appointment reminders with SchedulerReminder. The SchedulerReminder has the following properties for reminder alert.
| Properties | Description |
|---|---|
| Gets or sets the time interval that decides to notify the reminder before the appointment’s start time. | |
| Gets the reminder time that decides when to enable event to the reminder of the appointment. | |
| Gets the appointment details for which the reminder is created. | |
| Gets the reminder data object associated with the | |
| Gets or sets whether the reminder is dismissed. |
<ContentPage
. . .
xmlns:scheduler="clr-namespace:Syncfusion.Maui.Scheduler;assembly=Syncfusion.Maui.Scheduler">
<scheduler:SfScheduler x:Name="Schedule"
AppointmentsSource="{Binding Events}"
EnableReminder="True">
<scheduler:SfScheduler.BindingContext>
<local:SchedulerViewModel/>
</scheduler:SfScheduler.BindingContext>
</scheduler:SfScheduler>
</ContentPage>public class ReminderViewModel
{
...
public ObservableCollection<SchedulerAppointment> Events { get; set; } = new ObservableCollection<SchedulerAppointment>();
public ReminderViewModel()
{
// Normal Appointment
SchedulerAppointment normalAppointment = new SchedulerAppointment()
{
StartTime = DateTime.Now.AddMinutes(5),
EndTime = DateTime.Now.AddHours(1),
Subject = "Normal Appointment",
Background = Brush.SkyBlue,
Reminders = new ObservableCollection<SchedulerReminder>
{
new SchedulerReminder {TimeBeforeStart = new TimeSpan (0,4,0)},
}
};
Events.Add(normalAppointment);
// All Day Appointment
SchedulerAppointment allDayAppointment = new SchedulerAppointment()
{
StartTime = DateTime.Now.AddDays(1).AddMinutes(1),
EndTime = DateTime.Now.AddDays(1).AddHours(1),
Subject = "All Day Appointment",
Background = Brush.SkyBlue,
IsAllDay = true,
Reminders = new ObservableCollection<SchedulerReminder>
{
new SchedulerReminder {TimeBeforeStart = new TimeSpan (0,0,50)},
}
};
Events.Add(allDayAppointment);
// Recurrence Appointment
SchedulerAppointment recurrenceAppointment = new SchedulerAppointment()
{
Id = 1,
StartTime = DateTime.Now.AddDays(2).AddMinutes(1),
EndTime = DateTime.Now.AddDays(2).AddHours(1),
Subject = "Recurrence Appointment",
Background = Brush.LightGray,
RecurrenceRule = "FREQ=DAILY;COUNT=3",
Reminders = new ObservableCollection<SchedulerReminder>
{
new SchedulerReminder {TimeBeforeStart = new TimeSpan (0,0,40)},
}
};
Events.Add(recurrenceAppointment);
}
}Creating business object for reminders
Create a business object class Event with mandatory fields From, To, and EventName. and also business object class Reminder with mandatory fields TimeBeforeStart and IsDismissed.
/// <summary>
/// Represents custom data properties.
/// </summary>
public class Event
{
public DateTime From { get; set; }
public DateTime To { get; set; }
public bool IsAllDay { get; set; }
public string EventName { get; set; }
public string Notes { get; set; }
public string StartTimeZone { get; set; }
public string EndTimeZone { get; set; }
public Brush Color { get; set; }
public object RecurrenceId { get; set; }
public object Id { get; set; }
public string RecurrenceRule { get; set; }
public ObservableCollection<DateTime> RecurrenceExceptions { get; set; }
public ObservableCollection<Reminder> Reminders { get; set; }
}The ReminderMapping provides the mapping information about the SchedulerReminder properties to the DataItem object. ReminderMapping has the following properties.
-
TimeBeforeStart: Maps the property name of a custom class, which is equivalent to the SchedulerReminder.TimeBeforeStart.
-
IsDismissed: Maps the property name of a custom class, which is equivalent to the SchedulerReminder.IsDismissed.
NOTE
When publishing in AOT mode on iOS and macOS, ensure that
[Preserve(AllMembers = true)]is added to the model class to maintain reminder binding.
/// <summary>
/// Represents custom reminder properties.
/// </summary>
public class Reminder
{
/// <summary>
/// Gets or sets a value that decides to notify the reminder before the appointment’s start time.
/// </summary>
public TimeSpan TimeBeforeStart { get; set; }
/// <summary>
/// Gets or sets the value indicating whether the reminder is dismissed or not.
/// </summary>
public bool IsDismissed { get; set; }
}Map those properties of the Event class with the SfScheduler control by using the SchedulerAppointmentMapping and map CustomReminder properties with the SchedulerReminder by using SchedulerReminderMapping.
<ContentPage
. . .
xmlns:scheduler="clr-namespace:Syncfusion.Maui.Scheduler;assembly=Syncfusion.Maui.Scheduler">
<scheduler:SfScheduler x:Name="Schedule"
AppointmentsSource="{Binding Events}"
EnableReminder="True">
<scheduler:SfScheduler.BindingContext>
<local:SchedulerViewModel/>
</scheduler:SfScheduler.BindingContext>
<scheduler:SfScheduler.AppointmentMapping>
<scheduler:SchedulerAppointmentMapping
Subject="EventName"
StartTime="From"
EndTime="To"
Background="Color"
IsAllDay="IsAllDay"
StartTimeZone="StartTimeZone"
EndTimeZone="EndTimeZone"
RecurrenceExceptionDates="RecurrenceExceptionDates"
RecurrenceRule="RecurrenceRule"
RecurrenceId="RecurrenceId"
Reminders="Reminders">
<scheduler:SchedulerAppointmentMapping.ReminderMapping>
<scheduler:SchedulerReminderMapping IsDismissed="IsDismissed"
TimeBeforeStart="TimeBeforeStart"/>
</scheduler:SchedulerAppointmentMapping.ReminderMapping>
</scheduler:SchedulerAppointmentMapping>
</scheduler:SfScheduler.AppointmentMapping>
</scheduler:SfScheduler>
</ContentPage>public class ReminderViewModel
{
...
public ObservableCollection<Event> Events { get; set; } = new ObservableCollection<Event>();
public ReminderViewModel()
{
// Normal Appointment
Event normalAppointment = new Event()
{
From = DateTime.Now.AddMinutes(5),
To = DateTime.Now.AddHours(1),
EventName = "Normal Appointment",
Color = Brush.SkyBlue,
Reminders = new ObservableCollection<Reminder>
{
new Reminder {TimeBeforeStart = new TimeSpan (0,4,0)},
}
};
Events.Add(normalAppointment);
// All Day Appointment
Event allDayAppointment = new Event()
{
From = DateTime.Now.AddDays(1).AddMinutes(1),
To = DateTime.Now.AddDays(1).AddHours(1),
EventName = "All Day Appointment",
Color = Brush.SkyBlue,
IsAllDay = true,
Reminders = new ObservableCollection<Reminder>
{
new Reminder {TimeBeforeStart = new TimeSpan (0,0,50)},
}
};
Events.Add(allDayAppointment);
// Recurrence Appointment
Event recurrenceAppointment = new Event()
{
Id = 1,
From = DateTime.Now.AddDays(2).AddMinutes(1),
To = DateTime.Now.AddDays(2).AddHours(1),
EventName = "Recurrence Appointment",
Color = Brush.LightGray,
RecurrenceRule = "FREQ=DAILY;COUNT=3",
Reminders = new ObservableCollection<Reminder>
{
new Reminder {TimeBeforeStart = new TimeSpan (0,0,40)},
}
};
Events.Add(recurrenceAppointment);
}
}ReminderAlertOpening event
Scheduler notifies the appointment’s reminders by ReminderAlertOpening event before the appointment’s start time.The ReminderAlertOpeningEventArgs has the following property,
- Reminders: Gets a list of reminders that are used to notify the appointment reminders.
using Syncfusion.Maui.Scheduler;
. . .
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
scheduler.ReminderAlertOpening += Scheduler_ReminderAlertOpening;
}
private async void Scheduler_ReminderAlertOpening(object sender, ReminderAlertOpeningEventArgs e)
{
bool snooze = await DisplayAlert("Reminder", e.Reminders[0].Appointment.Subject + " - " + e.Reminders[0].Appointment.StartTime.ToString(" dddd, MMMM dd, yyyy, hh:mm tt"), "Snooze", "Dismiss");
}
}Handle dismiss property of reminders
-
Normal appointment directly dismissed using IsDismissed property
-
For a recurrence appointment, if the current occurrence needs to be dismissed, a changed occurrence must be added for the dismissed reminder occurrence, and then the current occurrence dismissed details are updated in the underlying appointment or data source.
-
If only occurrence dismissed, then the changed icon will not be updated for dismissed changed occurrence
using Syncfusion.Maui.Scheduler;
using System.Collections.ObjectModel;
. . .
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
scheduler.ReminderAlertOpening += Scheduler_ReminderAlertOpening;
}
private async void Scheduler_ReminderAlertOpening(object sender, ReminderAlertOpeningEventArgs e)
{
bool dismiss = await DisplayAlert("Reminder", e.Reminders[0].Appointment.Subject + " - " + e.Reminders[0].Appointment.StartTime.ToString(" dddd, MMMM dd, yyyy, hh:mm tt"), "Snooze", "Dismiss");
if (dismiss)
{
// For a Recurrence appointment, if the current occurrence needs to be dismissed, add a changed occurrence for the dismissed reminder occurrence
if (!string.IsNullOrEmpty(e.Reminders[0].Appointment.RecurrenceRule))
{
ObservableCollection<SchedulerAppointment> appointments = scheduler.AppointmentsSource as ObservableCollection<SchedulerAppointment>;
SchedulerAppointment patternAppointment = appointments.FirstOrDefault(x => x.Id == e.Reminders[0].Appointment.Id);
DateTime changedExceptionDate = e.Reminders[0].Appointment.StartTime;
DateTime endDate = e.Reminders[0].Appointment.EndTime;
patternAppointment.RecurrenceExceptionDates = new ObservableCollection<DateTime>()
{
changedExceptionDate,
};
// Clone parent details
SchedulerAppointment exceptionAppointment = new SchedulerAppointment()
{
Id = 2,
Subject = patternAppointment.Subject,
StartTime = new DateTime(changedExceptionDate.Year, changedExceptionDate.Month, changedExceptionDate.Day, changedExceptionDate.Hour, 0, 0),
EndTime = new DateTime(endDate.Year, endDate.Month, endDate.Day, endDate.Hour, 0, 0),
Background = patternAppointment.Background,
RecurrenceId = 1,
Reminders = patternAppointment.Reminders,
};
if (!appointments.Contains(exceptionAppointment))
{
exceptionAppointment.Reminders[0].IsDismissed = true;
appointments.Add(exceptionAppointment);
}
}
// To dismiss normal reminder
else
{
for (int i = e.Reminders.Count - 1; i >= 0; i--)
{
e.Reminders[i].IsDismissed = true;
}
}
}
}
}Handle Snooze for reminders
If Snooze time is set to 5 minutes, then the value of reminder TimeBeforeStart property is calculated by
-
For Future appointments,
TimeBeforeStart = Appointment.StartTime - AlertTime - snoozeTime. -
For Overdue appointments,
TimeBeforeStart = Appointment.StartTime.AddSeconds(DateTime.Now.Second) - DateTime.Now - snoozeTime. -
For All day appointment,
TimeBeforeStart = Appointment.StartTime.Date.AddSeconds(DateTime.Now.Second) - DateTime.Now - snoozeTime.
using Syncfusion.Maui.Scheduler;
using System.Collections.ObjectModel;
. . .
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
scheduler.ReminderAlertOpening += Scheduler_ReminderAlertOpening;
}
private async void Scheduler_ReminderAlertOpening(object sender, ReminderAlertOpeningEventArgs e)
{
bool snooze = await DisplayAlert("Reminder", e.Reminders[0].Appointment.Subject + " - " + e.Reminders[0].Appointment.StartTime.ToString(" dddd, MMMM dd, yyyy, hh:mm tt"), "Snooze", "Dismiss");
if (snooze)
{
TimeSpan snoozeTime = new TimeSpan(0, 5, 0);
// Future appointment reminder
if (e.Reminders[0].Appointment.ActualStartTime > DateTime.Now && !e.Reminders[0].Appointment.IsAllDay)
{
e.Reminders[0].TimeBeforeStart = e.Reminders[0].Appointment.StartTime - e.Reminders[0].AlertTime - snoozeTime;
}
// Allday appointment reminder
else if (e.Reminders[0].Appointment.IsAllDay)
{
e.Reminders[0].TimeBeforeStart = e.Reminders[0].Appointment.StartTime.Date.AddSeconds(DateTime.Now.Second) - DateTime.Now - snoozeTime;
}
// Overdue appointment reminder
else
{
e.Reminders[0].TimeBeforeStart = e.Reminders[0].Appointment.StartTime.AddSeconds(DateTime.Now.Second) - DateTime.Now - snoozeTime;
}
if (!string.IsNullOrEmpty(e.Reminders[0].Appointment.RecurrenceRule))
{
ObservableCollection<SchedulerAppointment> appointments = scheduler.AppointmentsSource as ObservableCollection<SchedulerAppointment>;
SchedulerAppointment patternAppointment = appointments.FirstOrDefault(x => x.Id == e.Reminders[0].Appointment.Id);
DateTime changedExceptionDate = e.Reminders[0].Appointment.StartTime;
DateTime endDate = e.Reminders[0].Appointment.EndTime;
patternAppointment.RecurrenceExceptionDates = new ObservableCollection<DateTime>()
{
changedExceptionDate,
};
// Clone parent details
SchedulerAppointment exceptionAppointment = new SchedulerAppointment()
{
Id = 2,
Subject = patternAppointment.Subject,
StartTime = new DateTime(changedExceptionDate.Year, changedExceptionDate.Month, changedExceptionDate.Day, changedExceptionDate.Hour, 0, 0),
EndTime = new DateTime(endDate.Year, endDate.Month, endDate.Day, endDate.Hour, 0, 0),
Background = patternAppointment.Background,
RecurrenceId = 1,
Reminders = new ObservableCollection<SchedulerReminder> { new SchedulerReminder { TimeBeforeStart = e.Reminders[0].TimeBeforeStart } },
};
if (!appointments.Contains(exceptionAppointment))
{
appointments.Add(exceptionAppointment);
}
}
}
}
}NOTE