How to group appointments in agenda view on Flutter Calendar?
The month view of Flutter Event Calendar (Sf Flutter Calendar) used to display entire dates of the specific month and current month by default initially. The calendar month view displays a divided agenda view that is used to show the selected date’s appointments below the month one by one based on the appointments stored in the appointment list. You can show the agenda view by setting the showAgenda property to true in MonthViewSettings. But grouping the appointments of the selected date’s by their subjects and sorting by their start times is not available at present.
In this article we have described how to create a custom appointment view where appointments are grouped by their subjects and sorted by their start times. We’ll leverage the createAppointmentGroups method to group and sort appointments, and then display them in a ListView when a date is tapped in the calendar. To provide a structured and readable layout, we have added a header for each appointment group. These headers will display the group names, making it easy for users to identify different types of appointments. Each group of appointments will be represented by an item in a list, which includes the group name and a list of appointments.
The following steps explains how to group appointments in agenda view based on their subjects [Business Meetings, Support Meetings, General Meetings]:
Step 1: Initialized the SfCalendar, configured with the CalendarView.month view.
class ScheduleExample extends State<CustomAgendaHeight> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[
Expanded(
child: SfCalendar(
view: CalendarView.month,
),
),
],
),
);
}
}
Step 2: Create a list of appointments and assign them to a dataSource. This dataSource is then linked to the calendar, allowing it to display the appointments.
class ScheduleExample extends State<CustomAgendaHeight> {
late List<AppointmentGroup> appointmentGroups;
@override
void initState() {
super.initState();
appointmentGroups = createAppointmentGroups(); // Initialize appointment groups
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[
Expanded(
child: SfCalendar(
view: CalendarView.month,
dataSource: _DataSource(appointmentGroups), // Assign data source
),
),
],
),
);
}
List<AppointmentGroup> createAppointmentGroups() {
final List<Appointment> appointments = <Appointment>[
Appointment(
startTime: DateTime(2024, 7, 13, 5),
endTime: DateTime(2024, 7, 13, 6),
subject: 'Support Update',
),
Appointment(
startTime: DateTime(2024, 7, 13, 5),
endTime: DateTime(2024, 7, 13, 6),
subject: 'Business Meeting',
),
Appointment(
startTime: DateTime(2024, 7, 12, 3),
endTime: DateTime(2024, 7, 12, 4),
subject: 'Support Update',
),
Appointment(
startTime: DateTime(2024, 7, 12, 1),
endTime: DateTime(2024, 7, 12, 2),
subject: 'Birthday Update',
),
Appointment(
startTime: DateTime(2024, 7, 12, 10),
endTime: DateTime(2024, 7, 12, 12),
subject: 'Business Meeting',
),
Appointment(
startTime: DateTime(2024, 7, 12, 10),
endTime: DateTime(2024, 7, 12, 12),
subject: 'Support Update',
),
Appointment(
startTime: DateTime(2024, 7, 12, 7),
endTime: DateTime(2024, 7, 12, 8),
subject: 'Support Update',
),
Appointment(
startTime: DateTime(2024, 7, 12, 5),
endTime: DateTime(2024, 7, 12, 6),
subject: 'Business Meeting',
),
Appointment(
startTime: DateTime(2024, 7, 13, 1),
endTime: DateTime(2024, 7, 13, 2),
subject: 'Business Meeting',
),
Appointment(
startTime: DateTime(2024, 7, 13, 10),
endTime: DateTime(2024, 7, 13, 11),
subject: 'Birthday Update',
),
Appointment(
startTime: DateTime(2024, 7, 13, 1),
endTime: DateTime(2024, 7, 13, 2),
subject: 'Birthday Update',
),
];
List<AppointmentGroup> appointmentGroups = [];
appointmentGroups.add(AppointmentGroup('Meetings', appointments));
return appointmentGroups;
}
}
class AppointmentGroup {
final String groupName;
final List<Appointment> appointments;
AppointmentGroup(this.groupName, this.appointments);
}
class _DataSource extends CalendarDataSource {
final List<AppointmentGroup> appointmentGroups;
_DataSource(this.appointmentGroups) {
appointments = appointmentGroups
.expand((group) => group.appointments)
.toList(growable: false);
}
}
Step 3: Group the appointments by their subject and sort them by their start time. Within the createAppointmentGroups method, we iterate through the list of appointments and categorize them into separate lists based on their subjects (e.g., Support Meetings, Business Meetings, General Meetings). We achieve this by checking the subject of each appointment using conditions (if-else) and sorting them by their start times using sort method. This grouping and sorting help organize the appointments for better presentation and management within the calendar and the subsequent list view.
List<AppointmentGroup> createAppointmentGroups() {
final List<Appointment> appointments = <Appointment>[
Appointment(
startTime: DateTime(2024, 7, 13, 5),
endTime: DateTime(2024, 7, 13, 6),
subject: 'Support Update',
),
Appointment(
startTime: DateTime(2024, 7, 13, 5),
endTime: DateTime(2024, 7, 13, 6),
subject: 'Business Meeting',
),
Appointment(
startTime: DateTime(2024, 7, 12, 3),
endTime: DateTime(2024, 7, 12, 4),
subject: 'Support Update',
),
// Add more appointments here
];
List<Appointment> supportMeetings = [];
List<Appointment> businessMeetings = [];
List<Appointment> birthdays = [];
for (var appointment in appointments) {
if (appointment.subject.contains('Support')) {
supportMeetings.add(appointment);
} else if (appointment.subject.contains('Business')) {
businessMeetings.add(appointment);
} else {
birthdays.add(appointment);
}
}
supportMeetings.sort((a, b) => a.startTime.compareTo(b.startTime));
businessMeetings.sort((a, b) => a.startTime.compareTo(b.startTime));
birthdays.sort((a, b) => a.startTime.compareTo(b.startTime));
return [
AppointmentGroup('Support Meetings', supportMeetings),
AppointmentGroup('Business Meetings', businessMeetings),
AppointmentGroup('General Meetings', birthdays),
];
}
Step 4: Add headers to visually separate different groups of appointments. This helps in easily identifying the category of each appointment. The _buildHeader method constructs a Container with a background color and a Text widget displaying the group name (e.g., ‘Support Meetings’). This header is displayed at the beginning of each group of appointments in the list view, providing clear separation and context for the appointments listed below it.
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[
Expanded(
child: SfCalendar(
view: CalendarView.month,
dataSource: _DataSource(appointmentGroups),
),
),
Expanded(
child: selectedAppointmentGroups.isNotEmpty
? ListView.builder(
itemCount: _createItemCount(),
itemBuilder: (BuildContext context, int index) {
final groupInfo = _createGroupInfo(index);
if (groupInfo.isHeader) {
return _buildHeader(groupInfo.group!);
} else {
final appointment = groupInfo.appointment!;
return Container(
margin: const EdgeInsets.symmetric(
vertical: 2.0, horizontal: 4.0),
padding: const EdgeInsets.all(8.0),
color: appointment.color,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
appointment.subject,
style: const TextStyle(
color: Colors.white,
fontSize: 14.0,
),
),
Text(
'${appointment.startTime.hour}:${appointment.startTime.minute.toString().padLeft(2, '0')}'
' - '
'${appointment.endTime.hour}:${appointment.endTime.minute.toString().padLeft(2, '0')}',
style: const TextStyle(
color: Colors.white,
fontSize: 14.0,
),
),
],
),
);
}
},
)
: const Center(child: Text('No appointments')),
),
],
),
);
}
int _createItemCount() {
int count = 0;
for (var group in selectedAppointmentGroups) {
count += group.appointments.length + 1;
}
return count;
}
GroupInfo _createGroupInfo(int index) {
int currentIndex = 0;
for (var group in selectedAppointmentGroups) {
if (index == currentIndex) {
return GroupInfo(isHeader: true, group: group);
}
currentIndex += 1;
if (index < currentIndex + group.appointments.length) {
return GroupInfo(
isHeader: false,
appointment: group.appointments[index - currentIndex]);
}
currentIndex += group.appointments.length;
}
throw RangeError.index(index, selectedAppointmentGroups);
}
Widget _buildHeader(AppointmentGroup group) {
return Container(
padding: const EdgeInsets.all(8.0),
color: const Color.fromARGB(192, 0, 0, 0),
child: Text(
group.groupName,
style: const TextStyle(
color: Colors.white,
fontSize: 18.0,
fontWeight: FontWeight.bold,
),
),
);
}
Step 5: When a date cell on the calendar (SfCalendar) is tapped (onTap event), the _showAppointmentsForDate method is triggered. This method filters the appointmentGroups list based on the selected date (details.date). It creates a new list (selectedAppointmentGroups) containing only those AppointmentGroup objects whose appointments fall on the selected date. This filtered list is then used to update the UI, displaying only the relevant appointments for the selected date in the list view below the calendar.
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[
Expanded(
child: SfCalendar(
view: CalendarView.month,
onTap: (CalendarTapDetails details) {
if (details.targetElement == CalendarElement.calendarCell) {
_showAppointmentsForDate(details.date!); // Show appointments for selected date
}
},
dataSource: _DataSource(appointmentGroups),
),
),
Expanded(
child: selectedAppointmentGroups.isNotEmpty
? ListView.builder(
itemCount: _createItemCount(),
itemBuilder: (BuildContext context, int index) {
final groupInfo = _createGroupInfo(index);
if (groupInfo.isHeader) {
return _buildHeader(groupInfo.group!);
} else {
final appointment = groupInfo.appointment!;
return Container(
margin: const EdgeInsets.symmetric(
vertical: 2.0, horizontal: 4.0),
padding: const EdgeInsets.all(8.0),
color: appointment.color,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
appointment.subject,
style: const TextStyle(
color: Colors.white,
fontSize: 14.0,
),
),
Text(
'${appointment.startTime.hour}:${appointment.startTime.minute.toString().padLeft(2, '0')}'
' - '
'${appointment.endTime.hour}:${appointment.endTime.minute.toString().padLeft(2, '0')}',
style: const TextStyle(
color: Colors.white,
fontSize: 14.0,
),
),
],
),
);
}
},
)
: const Center(child: Text('No appointments')),
),
],
),
);
}
void _showAppointmentsForDate(DateTime date) {
setState(
() {
selectedAppointmentGroups = appointmentGroups
.map((group) => AppointmentGroup(
group.groupName,
group.appointments.where((appointment) {
return appointment.startTime.year == date.year &&
appointment.startTime.month == date.month &&
appointment.startTime.day == date.day;
}).toList(),
))
.where((group) => group.appointments.isNotEmpty)
.toList();
},
);
}
Now, the appointments are grouped by subject in agenda view has been implemented as shown below.
View the sample here: Grouping appointments in agenda view on Flutter Calendar
Conclusion
I hope you enjoyed learning about how to group appointments in agenda view based on some categories or types.
You can refer to our Flutter Calendar feature tour page to know about its other groundbreaking feature representations. You can also explore our Flutter Calendar documentation to understand how to create and manipulate data.
For current customers, you can check out our components from the License and Downloads page. If you are new to Syncfusion®, you can try our 30-day free trial to check out our other controls.
If you have any queries or require clarifications, please let us know in the comments section below. You can also contact us through our support forums, Direct-Trac, or feedback portal. We are always happy to assist you!