How to Create a Scheduling Application Using Flutter Event Calendar | Syncfusion Blogs
Detailed Blog page Skeleton loader
Download PDF
How to Create a Scheduling Application Using Flutter Event Calendar

A good scheduling application can be better than having a human assistant. When it comes to developing a scheduling mobile application, the Syncfusion Flutter Event Calendar widget should be your component of choice. The Flutter Event Calendar can be used to schedule, manage, and represent appointments through an intuitive user interface that is similar to the native device calendar.

This blog post explains how the Flutter Event Calendar widget enables you to develop a scheduling application.

Configuring the Event Calendar

Browse our documentation to learn more about the Syncfusion Event Calendar widget and how to configure it in a Flutter application.

Adding a data source

The Syncfusion Event Calendar can handle appointment arrangement internally based on the appointments collection. You need to assign the created collection to the dataSource property of SfCalendar. You can also map custom appointment data to the Event Calendar. A custom appointment must contain two DateTime fields for the start and end times of an appointment.

By default, the Day View will be loaded in the scheduler, and today’s date will be displayed in all views.

Creating a business object

Let’s create a Meeting class that generates meeting details containing the time, subject, and other related information. In a real-world scenario, you can also bind the data from services and databases to render appointments.

class Meeting {
  Meeting(
      {@required this.from,
      @required this.to,
      this.background = Colors.green,
      this.isAllDay = false,
      this.eventName = '',
      this.startTimeZone = '',
      this.endTimeZone = '',
      this.description = ''});

  final String eventName;
  final DateTime from;
  final DateTime to;
  final Color background;
  final bool isAllDay;
  final String startTimeZone;
  final String endTimeZone;
  final String description;
}

Map the properties of the Meeting class with the Syncfusion Event Calendar by using the CalendarDataSource override methods properties.

class DataSource extends CalendarDataSource {
  DataSource(List<Meeting> source) {
    appointments = source;
  }

  @override
  bool isAllDay(int index) => appointments[index].isAllDay;

  @override
  String getSubject(int index) => appointments[index].eventName;

  @override
  String getStartTimeZone(int index) => appointments[index].startTimeZone;

  @override
  String getNotes(int index) => appointments[index].description;

  @override
  String getEndTimeZone(int index) => appointments[index].endTimeZone;

  @override
  Color getColor(int index) => appointments[index].background;

  @override
  DateTime getStartTime(int index) => appointments[index].from;

  @override
  DateTime getEndTime(int index) => appointments[index].to;
}

Now, schedule meetings for a day by setting the From and To properties of the Meeting class. Create a collection of Meeting objects and assign it to the appointments property of CalendarDataSource.

final DateTime today = DateTime.now();
final Random random = Random();
for (int month = -1; month < 2; month++) {
  for (int day = -5; day < 5; day++) {
    for (int hour = 9; hour < 18; hour += 5) {
      meetingCollection.add(Meeting(
        from: today
            .add(Duration(days: (month * 30) + day))
            .add(Duration(hours: hour)),
        to: today
            .add(Duration(days: (month * 30) + day))
            .add(Duration(hours: hour + 2)),
        background: _colorCollection[random.nextInt(9)],
        startTimeZone: '',
        endTimeZone: '',
        description: '',
        isAllDay: false,
        eventName: eventNameCollection[random.nextInt(7)],
      ));
    }
  }
}

return SfCalendar(
    view: _calendarView,
    dataSource: _calendarDataSource,
    onTap: calendarTapCallback,
    initialDisplayDate: DateTime(DateTime.now().year, DateTime.now().month,
        DateTime.now().day, 0, 0, 0),
    monthViewSettings: MonthViewSettings(
        appointmentDisplayMode: MonthAppointmentDisplayMode.appointment),
    timeSlotViewSettings: TimeSlotViewSettings(
        minimumAppointmentDuration: const Duration(minutes: 60)));

To learn more about appointments and the different types of appointments, refer to our documentation.

Now, a calendar is configured in the application. Running the sample after following the previous steps will render a calendar with appointments.

Designing a custom appointment editor

In the AppointmentEditor class, let’s create a custom appointment editor with the required data fields. This custom appointment editor will be used for both displaying the appointment details and for adding a new appointment to the Event Calendar.

Widget _getAppointmentEditor(BuildContext context) {
  return Container(
      color: Colors.white,
      child: ListView(
        padding: const EdgeInsets.all(0),
        children: <Widget>[
          ListTile(
            contentPadding: const EdgeInsets.fromLTRB(5, 0, 5, 5),
            leading: const Text(''),
            title: TextField(
              controller: TextEditingController(text: _subject),
              onChanged: (String value) {
                _subject = value;
              },
              keyboardType: TextInputType.multiline,
              maxLines: null,
              style: TextStyle(
                  fontSize: 25,
                  color: Colors.black,
                  fontWeight: FontWeight.w400),
              decoration: InputDecoration(
                border: InputBorder.none,
                hintText: 'Add title',
              ),
            ),
          ),
          const Divider(
            height: 1.0,
            thickness: 1,
          ),
          ListTile(
              contentPadding: const EdgeInsets.fromLTRB(5, 2, 5, 2),
              leading: Icon(
                Icons.access_time,
                color: Colors.black54,
              ),
              title: Row(children: <Widget>[
                const Expanded(
                  child: Text('All-day'),
                ),
                Expanded(
                    child: Align(
                        alignment: Alignment.centerRight,
                        child: Switch(
                          value: _isAllDay,
                          onChanged: (bool value) {
                            setState(() {
                              _isAllDay = value;
                            });
                          },
                        ))),
              ])),
          ListTile(
              contentPadding: const EdgeInsets.fromLTRB(5, 2, 5, 2),
              leading: const Text(''),
              title: Row(
                  crossAxisAlignment: CrossAxisAlignment.center,
                  children: <Widget>[
                    Expanded(
                      flex: 7,
                      child: GestureDetector(
                          child: Text(
                              DateFormat('EEE, MMM dd yyyy')
                                  .format(_startDate),
                              textAlign: TextAlign.left),
                          onTap: () async {
                            final DateTime date = await showDatePicker(
                              context: context,
                              initialDate: _startDate,
                              firstDate: DateTime(1900),
                              lastDate: DateTime(2100),
                            );

                            if (date != null && date != _startDate) {
                              setState(() {
                                final Duration difference =
                                    _endDate.difference(_startDate);
                                _startDate = DateTime(
                                    date.year,
                                    date.month,
                                    date.day,
                                    _startTime.hour,
                                    _startTime.minute,
                                    0);
                                _endDate = _startDate.add(difference);
                                _endTime = TimeOfDay(
                                    hour: _endDate.hour,
                                    minute: _endDate.minute);
                              });
                            }
                          }),
                    ),
                    Expanded(
                        flex: 3,
                        child: _isAllDay
                            ? const Text('')
                            : GestureDetector(
                                child: Text(
                                  DateFormat('hh:mm a').format(_startDate),
                                  textAlign: TextAlign.right,
                                ),
                                onTap: () async {
                                  final TimeOfDay time = await showTimePicker(
                                      context: context,
                                      initialTime: TimeOfDay(
                                          hour: _startTime.hour,
                                          minute: _startTime.minute));

                                  if (time != null && time != _startTime) {
                                    setState(() {
                                      _startTime = time;
                                      final Duration difference =
                                          _endDate.difference(_startDate);
                                      _startDate = DateTime(
                                          _startDate.year,
                                          _startDate.month,
                                          _startDate.day,
                                          _startTime.hour,
                                          _startTime.minute,
                                          0);
                                      _endDate = _startDate.add(difference);
                                      _endTime = TimeOfDay(
                                          hour: _endDate.hour,
                                          minute: _endDate.minute);
                                    });
                                  }
                                })),
                  ])),
          ListTile(
              contentPadding: const EdgeInsets.fromLTRB(5, 2, 5, 2),
              leading: const Text(''),
              title: Row(
                  crossAxisAlignment: CrossAxisAlignment.center,
                  children: <Widget>[
                    Expanded(
                      flex: 7,
                      child: GestureDetector(
                          child: Text(
                            DateFormat('EEE, MMM dd yyyy').format(_endDate),
                            textAlign: TextAlign.left,
                          ),
                          onTap: () async {
                            final DateTime date = await showDatePicker(
                              context: context,
                              initialDate: _endDate,
                              firstDate: DateTime(1900),
                              lastDate: DateTime(2100),
                            );

                            if (date != null && date != _endDate) {
                              setState(() {
                                final Duration difference =
                                    _endDate.difference(_startDate);
                                _endDate = DateTime(
                                    date.year,
                                    date.month,
                                    date.day,
                                    _endTime.hour,
                                    _endTime.minute,
                                    0);
                                if (_endDate.isBefore(_startDate)) {
                                  _startDate = _endDate.subtract(difference);
                                  _startTime = TimeOfDay(
                                      hour: _startDate.hour,
                                      minute: _startDate.minute);
                                }
                              });
                            }
                          }),
                    ),
                    Expanded(
                        flex: 3,
                        child: _isAllDay
                            ? const Text('')
                            : GestureDetector(
                                child: Text(
                                  DateFormat('hh:mm a').format(_endDate),
                                  textAlign: TextAlign.right,
                                ),
                                onTap: () async {
                                  final TimeOfDay time = await showTimePicker(
                                      context: context,
                                      initialTime: TimeOfDay(
                                          hour: _endTime.hour,
                                          minute: _endTime.minute));

                                  if (time != null && time != _endTime) {
                                    setState(() {
                                      _endTime = time;
                                      final Duration difference =
                                          _endDate.difference(_startDate);
                                      _endDate = DateTime(
                                          _endDate.year,
                                          _endDate.month,
                                          _endDate.day,
                                          _endTime.hour,
                                          _endTime.minute,
                                          0);
                                      if (_endDate.isBefore(_startDate)) {
                                        _startDate =
                                            _endDate.subtract(difference);
                                        _startTime = TimeOfDay(
                                            hour: _startDate.hour,
                                            minute: _startDate.minute);
                                      }
                                    });
                                  }
                                })),
                  ])),
          ListTile(
            contentPadding: const EdgeInsets.fromLTRB(5, 2, 5, 2),
            leading: Icon(
              Icons.public,
              color: Colors.black87,
            ),
            title: Text(_timeZoneCollection[_selectedTimeZoneIndex]),
            onTap: () {
              showDialog<Widget>(
                context: context,
                barrierDismissible: true,
                builder: (BuildContext context) {
                  return _TimeZonePicker();
                },
              ).then((dynamic value) => setState(() {}));
            },
          ),
          const Divider(
            height: 1.0,
            thickness: 1,
          ),
          ListTile(
            contentPadding: const EdgeInsets.fromLTRB(5, 2, 5, 2),
            leading: Icon(Icons.lens,
                color: _colorCollection[_selectedColorIndex]),
            title: Text(
              _colorNames[_selectedColorIndex],
            ),
            onTap: () {
              showDialog<Widget>(
                context: context,
                barrierDismissible: true,
                builder: (BuildContext context) {
                  return _ColorPicker();
                },
              ).then((dynamic value) => setState(() {}));
            },
          ),
          const Divider(
            height: 1.0,
            thickness: 1,
          ),
          ListTile(
            contentPadding: const EdgeInsets.all(5),
            leading: Icon(
              Icons.subject,
              color: Colors.black87,
            ),
            title: TextField(
              controller: TextEditingController(text: _notes),
              onChanged: (String value) {
                _notes = value;
              },
              keyboardType: TextInputType.multiline,
              maxLines: null,
              style: TextStyle(
                  fontSize: 18,
                  color: Colors.black87,
                  fontWeight: FontWeight.w400),
              decoration: InputDecoration(
                border: InputBorder.none,
                hintText: 'Add description',
              ),
            ),
          ),
          const Divider(
            height: 1.0,
            thickness: 1,
          ),
        ],
      ));
}

Integrating the custom appointment editor

In the previous section, we designed a custom appointment editor. In this section, we are going to integrate that editor with the Event Calendar widget.

The onTap callback on the Syncfusion Event Calendar returns the tapped element, the date, and the appointment details. We can use these details to create the custom appointment editor and add it to the application.

The following code example illustrates this.

void onCalendarTapped(CalendarTapDetails calendarTapDetails) {
  if (calendarTapDetails.targetElement != CalendarElement.calendarCell &&
      calendarTapDetails.targetElement != CalendarElement.appointment) {
    return;
  }

  setState(() {
    _selectedAppointment = null;
    _isAllDay = false;
    _selectedColorIndex = 0;
    _selectedTimeZoneIndex = 0;
    _subject = '';
    _notes = '';
    if (_calendarView == CalendarView.month) {
      _calendarView = CalendarView.day;
    } else {
      if (calendarTapDetails.appointments != null &&
          calendarTapDetails.appointments.length == 1) {
        final Meeting meetingDetails = calendarTapDetails.appointments[0];
        _startDate = meetingDetails.from;
        _endDate = meetingDetails.to;
        _isAllDay = meetingDetails.isAllDay;
        _selectedColorIndex =
            _colorCollection.indexOf(meetingDetails.background);
        _selectedTimeZoneIndex = meetingDetails.startTimeZone == ''
            ? 0
            : _timeZoneCollection.indexOf(meetingDetails.startTimeZone);
        _subject = meetingDetails.eventName == '(No title)'
            ? ''
            : meetingDetails.eventName;
        _notes = meetingDetails.description;
        _selectedAppointment = meetingDetails;
      } else {
        final DateTime date = calendarTapDetails.date;
        _startDate = date;
        _endDate = date.add(const Duration(hours: 1));
      }
      _startTime =
          TimeOfDay(hour: _startDate.hour, minute: _startDate.minute);
      _endTime = TimeOfDay(hour: _endDate.hour, minute: _endDate.minute);
      Navigator.push<Widget>(
        context,
        MaterialPageRoute(
            builder: (BuildContext context) => AppointmentEditor()),
      );
    }
  });
}

Opening editor - Flutter Event Calendar

Adding appointments using the editor

The custom appointment editor allows existing appointments to be edited and deleted, and new appointments to be added. The changes we make here should be reflected in the Event Calendar’s dataSource. This is achieved using notifiers in the CalendarDataSource.

The following code example illustrates this.

@override
Widget build([BuildContext context]) {
  return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
          appBar: AppBar(
            title: Text(getTile()),
            backgroundColor: _colorCollection[_selectedColorIndex],
            leading: IconButton(
              icon: const Icon(
                Icons.close,
                color: Colors.white,
              ),
              onPressed: () {
                Navigator.pop(context);
              },
            ),
            actions: <Widget>[
              IconButton(
                  padding: const EdgeInsets.fromLTRB(5, 0, 5, 0),
                  icon: const Icon(
                    Icons.done,
                    color: Colors.white,
                  ),
                  onPressed: () {
                    final List<Meeting> meetings = <Meeting>[];
                    if (_selectedAppointment != null) {
                      _events.appointments.removeAt(
                          _events.appointments.indexOf(_selectedAppointment));
                      _events.notifyListeners(CalendarDataSourceAction.remove,
                          <Meeting>[]..add(_selectedAppointment));
                    }
                    meetings.add(Meeting(
                      from: _startDate,
                      to: _endDate,
                      background: _colorCollection[_selectedColorIndex],
                      startTimeZone: _selectedTimeZoneIndex == 0
                          ? ''
                          : _timeZoneCollection[_selectedTimeZoneIndex],
                      endTimeZone: _selectedTimeZoneIndex == 0
                          ? ''
                          : _timeZoneCollection[_selectedTimeZoneIndex],
                      description: _notes,
                      isAllDay: _isAllDay,
                      eventName: _subject == '' ? '(No title)' : _subject,
                    ));

                    _events.appointments.add(meetings[0]);

                    _events.notifyListeners(
                        CalendarDataSourceAction.add, meetings);
                    _selectedAppointment = null;

                    Navigator.pop(context);
                  })
            ],
          ),
          body: Padding(
            padding: const EdgeInsets.fromLTRB(5, 5, 5, 5),
            child: Stack(
              children: <Widget>[_getAppointmentEditor(context)],
            ),
          ),
          floatingActionButton: _selectedAppointment == null
              ? const Text('')
              : FloatingActionButton(
                  onPressed: () {
                    if (_selectedAppointment != null) {
                      _events.appointments.removeAt(
                          _events.appointments.indexOf(_selectedAppointment));
                      _events.notifyListeners(CalendarDataSourceAction.remove,
                          <Meeting>[]..add(_selectedAppointment));
                      _selectedAppointment = null;
                      Navigator.pop(context);
                    }
                  },
                  child:
                      const Icon(Icons.delete_outline, color: Colors.white),
                  backgroundColor: Colors.red,
                )));
}

Adding deleting appointments Editor- Flutter Event Calendar

Summary

In this blog post, we had a quick overview of creating a scheduling application with the Syncfusion Event Calendar widget for Flutter. We also walked through the steps to create custom appointments and handle editor details with the calendar’s dataSource. Feel free to try out the Scheduling-Events sample  on GitHub.

If you are a current Syncfusion user, please download the latest version from the License and Downloads page and try these features for yourself. If you aren’t a customer yet, you can try our 30-day free trial to check them out. Our other samples are available on GitHub.

If you wish to send us feedback or would like to submit any questions, please feel free to post them in the comments section below. Contact us through our support forumDirect-Trac, or feedback portal. We are happy to assist you!

googleplay.png

Be the first to get updates

Nijamudeen

Meet the Author

Nijamudeen

Nijamudeen is the product manager for the Syncfusion Xamarin and Flutter platforms, who build highly scalable custom controls that drive Syncfusion's product vision forward.

Comments (6)

Kareem Hamoud Abdullah Moree Obaid
Kareem Hamoud Abdullah Moree Obaid

i have been looking for such example for the whole month, i have read the documntation,talked to syncfusion team support , non works. today i found it , relly happy to have that. but,
Can we have a full example of the whole above code,source code in gitHub ? if yes ,where to find it or how to get it ?
thank you in advance

NIJAMUDEEN MOHAMED SULAIMAN
NIJAMUDEEN MOHAMED SULAIMAN
@ Kareem Hamoud Abdullah Moree Obaid  

Hi @Kareem,

Thanks for your interest in Syncfusion products.

Please find the complete example in this link [https://github.com/SyncfusionExamples/scheduling-events-flutter-calendar] which is already added to this blog Summary topic.

You can also refer to our PlayStore application – Syncfusion Flutter UI Widgets and web application -https://flutter.syncfusion.com/#/event-calendar/scheduling

Kareem Hamoud Abdullah Moree Obaid
Kareem Hamoud Abdullah Moree Obaid

I’m wondering !!
where are all those classes and functions you are using in the example above?

NIJAMUDEEN MOHAMED SULAIMAN
NIJAMUDEEN MOHAMED SULAIMAN
@ Kareem Hamoud Abdullah Moree Obaid  

@kareem,

Find the complete example code in this link – https://github.com/SyncfusionExamples/scheduling-events-flutter-calendar

Kareem Hamoud Abdullah Moree Obaid
Kareem Hamoud Abdullah Moree Obaid

what if i need to post and get events to/from server using rest APIs?
and what if i need to edit and delete those events? any idea?
a bunch of thanks sir!

Nijamudeen Mohamed Sulaiman
Nijamudeen Mohamed Sulaiman
@ Kareem Hamoud Abdullah Moree Obaid  

Hi,
We have KB documentation for getting the online JSON data with the help of the HTTP package.
Link: https://www.syncfusion.com/kb/11568/how-to-load-the-json-data-online-to-the-flutter-calendar-appointments

We have KB documentation for performing the CRUD operation with Firebase and Firestore databases. Please find the KB documentation from the following link.
KB links:
https://www.syncfusion.com/kb/12661/how-to-perform-the-crud-operations-in-flutter-calendar-using-firestore-database
https://www.syncfusion.com/kb/12623/how-to-perform-the-crud-operations-in-flutter-calendar-using-firebase-database
https://www.syncfusion.com/kb/12616/how-to-add-the-appointments-to-firestore-database-using-flutter-calendar
https://www.syncfusion.com/kb/12067/how-to-work-with-the-firebase-database-and-the-flutter-calendar-for-appointments
https://www.syncfusion.com/kb/12110/how-to-add-the-appointments-to-the-firebase-database-using-appointment-editor-in-the

Also please find the following link for creating the server in typicode link add, delete, and update the data using HTTP package. By using this link, you can create your own API link with the JSON data and do the required operations.
Link: https://jsonplaceholder.typicode.com/
We hope that this helps you. Please let us know if you need further assistance.

Comments are closed.