Eventide provides an 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.
Add the following permissions to your android/app/src/main/AndroidManifest.xml based on the features you need:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"...>
<!-- For reading calendars and events -->
<uses-permission android:name="android.permission.READ_CALENDAR" />
<!-- For creating, modifying, or deleting calendars and events -->
<uses-permission android:name="android.permission.WRITE_CALENDAR" />
...
</manifest>Note: createEventInDefaultCalendar() and createEventThroughNativePlatform() do not require declaring these permissions in your AndroidManifest.xml as they use the system calendar app. This privacy-first approach ensures your app doesn't need to access user's calendar data directly. Both methods have identical behavior on Android.
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.
Note: createEventThroughNativePlatform() does not require any calendar usage description in your Info.plist as it uses the native event creation UI, which handles permissions internally.
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),
],
);
// Create an event using native platform UI (no permissions required)
await eventide.createEventThroughNativePlatform(
title: 'Team Standup',
startDate: DateTime.now().add(Duration(hours: 2)),
endDate: DateTime.now().add(Duration(hours: 2, minutes: 30)),
);
// Delete a reminder
final updatedEvent = await eventide.deleteReminder(
durationBeforeEvent: Duration(minutes: 15),
eventId: event.id,
);You can find more examples in the examples 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.
- On Android, this method opens the system calendar app for the user to create the event (no permissions required).
Note: On Android,
createEventThroughNativePlatform()has identical behavior to this method. This behavior may change in the future if Android introduces a native default calendar API.
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<void> createEventThroughNativePlatform({
String? title,
DateTime? startDate,
DateTime? endDate,
bool? isAllDay,
String? description,
String? url,
List<Duration>? reminders,
})Creates a new event using the native platform UI. This method provides a consistent cross-platform experience for event creation without requiring calendar permissions.
Platform Behavior:
- iOS: Opens the native event creation modal where users can create events with write-only permission
- Android: Opens the system calendar app for event creation (identical behavior to
createEventInDefaultCalendar())
Note: All parameters are optional, allowing flexible event creation. The method is fire-and-forget on Android - it doesn't wait for event creation completion.
// Create event with full details
await eventide.createEventThroughNativePlatform(
title: 'Team Standup',
startDate: DateTime.now().add(Duration(hours: 2)),
endDate: DateTime.now().add(Duration(hours: 2, minutes: 30)),
description: 'Daily team synchronization',
isAllDay: false,
reminders: [Duration(minutes: 10)],
);
// Create event with minimal parameters
await eventide.createEventThroughNativePlatform(
title: 'Quick Meeting',
);
// No parameters - opens empty native form
await eventide.createEventThroughNativePlatform();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.unknownETAttendeeType.requiredPersonETAttendeeType.optionalPersonETAttendeeType.resourceETAttendeeType.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.
Eventide is designed with user privacy as a core principle. We believe users should have granular control over their calendar data and only grant the minimum permissions necessary for your app to function.
Our privacy-focused design:
- Minimal permissions: Only request the permissions your app actually needs
- User choice: Support both write-only and full access modes on iOS 17+
- System delegation: On Android,
createEventInDefaultCalendar()andcreateEventThroughNativePlatform()delegate to the system calendar app, ensuring no direct data access - Transparency: Clear documentation about what each method requires and accesses
This approach ensures users maintain control over their personal calendar data while still enabling powerful calendar integration for your app.
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 successfully');- 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)],
);Eventide automatically handles the permission flow for you - no need to manually request permissions.
Example for iOS:
- First call to
createEventInDefaultCalendar()β Shows write-only permission prompt - User grants write-only access β Creates event in default calendar
- User denies access β Throws
ETPermissionException
Another iOS example:
- First call to
retrieveCalendars()β Shows full calendar access prompt - User grants full access β Returns list of calendars
- User denies access β Throws
ETPermissionException
Examples for Android:
- Call to
createEventThroughNativePlatform()β Opens system calendar app directly - No permissions required β User creates event in native calendar UI
- Returns immediately after opening system app
Another Android example:
- First call to
createEvent()β Shows calendar permission dialog - User grants permission β Creates event in specified calendar
- User denies permission β Throws
ETPermissionException
Privacy-first approach - from least to most intrusive:
- No permissions required:
createEventThroughNativePlatform()for simple event creation using native UI - Write-only access:
createEventInDefaultCalendar()for apps that only need to add events (booking confirmations, reminders, etc.) - Full calendar access: For apps that need to read existing events and manage calendars
Error handling:
- Handle
ETPermissionExceptionwhen attempting operations that require read access - Consider graceful fallbacks when permissions are denied
Development approach:
- Start with minimal permissions and only escalate when features require it
- Offer clear value proposition when requesting additional access
Eventide provides several custom exception types for better error handling.
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.
