Eventide provides a easy-to-use flutter interface to access & modify native device calendars (iOS & Android).
- Features
- Getting Started
- Quick Start
- API Reference
- Platform-Specific Features
- Exception Handling
- License
- Feedback
Eventide | |
---|---|
✅ | Automatic permission handling |
✅ | Create/retrieve/delete calendars |
✅ | Create/retrieve/delete events |
✅ | Create/delete reminders |
✅ | Custom exceptions |
🚧 | Recurring events |
✅ | Attendees |
🚧 | Streams |
Note: Eventide handles timezones as UTC. Make sure the right data is fed to the plugin with a timezone aware DateTime class.
Nothing to add on your side. All permissions are already declared in eventide's AndroidManifest.xml
The following are the lines you need to add to your info.plist
file:
<key>NSCalendarsUsageDescription</key>
<string>We need access to your calendar to add information about your trip.</string>
Starting iOS 17+, it depends whether you want full or write-only access from your user.
<key>NSCalendarsWriteOnlyAccessUsageDescription</key>
<string>We need access to your calendar to add information about your trip.</string>
<key>NSCalendarsFullAccessUsageDescription</key>
<string>We need access to your calendar to add information about your trip.</string>
Note that write-only AND full access will result on your app asking for both.
import 'package:eventide/eventide.dart';
final eventide = Eventide();
// Create a calendar
final calendar = await eventide.createCalendar(
title: 'Work',
color: Colors.red,
localAccountName: "My Company",
);
// Create an event in a specific calendar with reminders
final event = await eventide.createEvent(
calendarId: calendar.id,
title: 'Meeting',
startDate: DateTime.now(),
endDate: DateTime.now().add(Duration(hours: 1)),
reminders: [
const Duration(hours: 1),
const Duration(minutes: 15),
],
);
// Create an event in the default calendar (iOS write-only access)
await eventide.createEventInDefaultCalendar(
title: 'Important Meeting',
startDate: DateTime.now().add(Duration(days: 1)),
endDate: DateTime.now().add(Duration(days: 1, hours: 1)),
reminders: [
const Duration(minutes: 15),
],
);
// Delete a reminder
final updatedEvent = await eventide.deleteReminder(
durationBeforeEvent: Duration(minutes: 15),
eventId: event.id,
);
You can find more examples in the example app.
Future<ETCalendar> createCalendar({
required String title,
required Color color,
required String localAccountName,
})
Creates a new calendar with the specified title, color, and account name.
final calendar = await eventide.createCalendar(
title: 'Personal',
color: Colors.blue,
localAccountName: 'My App',
);
Future<List<ETCalendar>> retrieveCalendars({
bool onlyWritableCalendars = true,
String? fromLocalAccountName,
})
Retrieves a list of calendars, optionally filtered by account name and writability.
// Get all writable calendars
final calendars = await eventide.retrieveCalendars();
// Get calendars from specific account
final myCalendars = await eventide.retrieveCalendars(
fromLocalAccountName: 'My App',
);
Future<void> deleteCalendar({
required String calendarId,
})
Deletes a calendar by its ID.
await eventide.deleteCalendar(calendarId: calendar.id);
Future<ETEvent> createEvent({
required String calendarId,
required String title,
required DateTime startDate,
required DateTime endDate,
bool isAllDay = false,
String? description,
String? url,
List<Duration>? reminders,
})
Creates a new event in the specified calendar.
final event = await eventide.createEvent(
calendarId: calendar.id,
title: 'Team Meeting',
startDate: DateTime.now(),
endDate: DateTime.now().add(Duration(hours: 1)),
description: 'Weekly team sync',
isAllDay: false,
reminders: [Duration(minutes: 15)],
);
Future<void> createEventInDefaultCalendar({
required String title,
required DateTime startDate,
required DateTime endDate,
bool isAllDay = false,
String? description,
String? url,
List<Duration>? reminders,
})
Creates a new event in the default calendar. On iOS, this method will prompt the user for write-only permission and insert the event in the user's default calendar.
await eventide.createEventInDefaultCalendar(
title: 'Important Meeting',
startDate: DateTime.now().add(Duration(days: 1)),
endDate: DateTime.now().add(Duration(days: 1, hours: 1)),
description: 'Weekly team sync',
isAllDay: false,
reminders: [Duration(minutes: 15)],
);
Future<List<ETEvent>> retrieveEvents({
required String calendarId,
DateTime? startDate,
DateTime? endDate,
})
Retrieves events from a calendar within the specified date range.
final events = await eventide.retrieveEvents(
calendarId: calendar.id,
startDate: DateTime.now().subtract(Duration(days: 7)),
endDate: DateTime.now().add(Duration(days: 7)),
);
Future<void> deleteEvent({
required String eventId,
})
Deletes an event by its ID.
await eventide.deleteEvent(eventId: event.id);
Future<ETEvent> createReminder({
required String eventId,
required Duration durationBeforeEvent,
})
Adds a reminder to an existing event.
final updatedEvent = await eventide.createReminder(
eventId: event.id,
durationBeforeEvent: Duration(minutes: 30),
);
Future<ETEvent> deleteReminder({
required String eventId,
required Duration durationBeforeEvent,
})
Removes a specific reminder from an event.
final updatedEvent = await eventide.deleteReminder(
eventId: event.id,
durationBeforeEvent: Duration(minutes: 30),
);
Note: Reminders with durations in seconds are not supported on Android due to API limitations.
Future<ETEvent> createAttendee({
required String eventId,
required String name,
required String email,
required ETAttendeeType type,
})
Adds an attendee to an event.
final eventWithAttendee = await eventide.createAttendee(
eventId: event.id,
name: 'John Doe',
email: '[email protected]',
type: ETAttendeeType.requiredPerson,
);
Future<ETEvent> deleteAttendee({
required String eventId,
required ETAttendee attendee,
})
Removes an attendee from an event.
final eventWithoutAttendee = await eventide.deleteAttendee(
eventId: event.id,
attendee: eventWithAttendee.attendees.first,
);
Available ETAttendeeType
values:
ETAttendeeType.unknown
ETAttendeeType.requiredPerson
ETAttendeeType.optionalPerson
ETAttendeeType.resource
ETAttendeeType.organizer
iOS and Android attendee APIs are quite different and thus required some conversion logic. Here's the mapping table that eventide currently supports:
ETAttendeeType | iOS (EKParticipantType) | iOS (EKParticipantRole) | Android (ATTENDEE_TYPE) | Android (ATTENDEE_RELATIONSHIP) |
---|---|---|---|---|
unknown | unknown | unknown | TYPE_NONE | RELATIONSHIP_NONE |
requiredPerson | person | required | TYPE_REQUIRED | RELATIONSHIP_ATTENDEE |
optionalPerson | person | optional | TYPE_OPTIONAL | RELATIONSHIP_ATTENDEE |
resource | resource | required | TYPE_RESOURCE | RELATIONSHIP_ATTENDEE |
organizer | person | chair | TYPE_REQUIRED | RELATIONSHIP_ORGANIZER |
Platform specific values will be treated as follow when fetched from existing system calendar:
ETAttendeeType | iOS (EKParticipantType) | iOS (EKParticipantRole) | Android (ATTENDEE_TYPE) | Android (ATTENDEE_RELATIONSHIP) |
---|---|---|---|---|
optionalPerson | person | nonParticipant | ||
resource | group | required | ||
resource | room | required | ||
requiredPerson | TYPE_REQUIRED | RELATIONSHIP_PERFORMER | ||
requiredPerson | TYPE_REQUIRED | RELATIONSHIP_SPEAKER |
A calendar belongs to an account, such as a Google account or a local on-device account. You must provide a localAccountName
when creating a calendar with Eventide.
const myAppCalendarIdentifier = "My Company";
await eventide.createCalendar(
title: 'Personal',
color: Colors.blue,
localAccountName: myAppCalendarIdentifier,
);
await eventide.createCalendar(
title: 'Work',
color: Colors.red,
localAccountName: myAppCalendarIdentifier,
);
final myCompanyCalendars = await eventide.retrieveCalendars(
onlyWritableCalendars: true,
fromLocalAccountName: myAppCalendarIdentifier,
);
Note: Users might need to allow your custom account in their calendar app to display your calendars & events.
As of iOS 17, Apple introduced a new write-only access permission for calendar data, providing users with more granular control over app permissions. This feature allows apps to add events to calendars without being able to read existing calendar data.
When you call createEventInDefaultCalendar()
on iOS 17+, the system will prompt the user for write-only access if full access hasn't been granted. This method directly creates an event in the user's default calendar without requiring you to retrieve the calendar first.
// Will prompt user for write only access on iOS 17+
await eventide.createEventInDefaultCalendar(
title: 'New Meeting',
startDate: DateTime.now(),
endDate: DateTime.now().add(Duration(hours: 1)),
description: 'Weekly team sync',
reminders: [Duration(minutes: 15)],
);
print('Event created: ${event.title}');
- No reading capabilities: You cannot retrieve events from calendars when using write-only access
- Create only: You can only create new events, not modify or read existing ones
- No calendar enumeration: You cannot list or retrieve calendar information
// ❌ This will fail with write-only access
try {
final calendars = await eventide.retrieveCalendars();
final events = await eventide.retrieveEvents(
calendarId: calendars.first.id,
);
} catch (e) {
// Will throw ETPermissionException on iOS with write-only access
print('Cannot read calendars/events with write-only access: $e');
}
// ✅ This works with write-only access
await eventide.createEventInDefaultCalendar(
title: 'Team Meeting',
startDate: DateTime.now().add(Duration(days: 1)),
endDate: DateTime.now().add(Duration(days: 1, hours: 1)),
description: 'Weekly team sync',
reminders: [Duration(minutes: 15)],
);
The system will automatically handle the permission flow:
- First call to
createEventInDefaultCalendar()
→ Shows write-only permission prompt - User grants write-only access → Creates event in default calendar
- User denies access → Throws
ETPermissionException
- Handle
ETPermissionException
when attempting operations that require read access - Consider offering full calendar access for apps that need to read existing events
- Use write-only access for apps that only need to add events (like booking confirmations, reminders, etc.)
- Use
createEventInDefaultCalendar()
for simple event creation without calendar management
Eventide provides custom exceptions for better error handling:
ETPermissionException
: User denied calendar permissionsETNotFoundException
: Calendar or event not foundETNotEditableException
: Calendar is not editableETGenericException
: General errors during operations
try {
final calendar = await eventide.createCalendar(
title: 'My Calendar',
color: Colors.blue,
localAccountName: 'My App',
);
} on ETPermissionException catch (e) {
print('Permission denied: ${e.message}');
} on ETGenericException catch (e) {
print('Error creating calendar: ${e.message}');
} catch (e) {
print('Unexpected error: $e');
}
Copyright © 2025 SNCF Connect & Tech. This project is licensed under the MIT License - see the LICENSE file for details.
Please file any issues, bugs or feature requests as an issue on the Github page.