eventide 0.10.2
eventide: ^0.10.2 copied to clipboard
Provides a easy-to-use flutter interface to access & modify native device calendars (iOS & Android)
📆 Eventide #
Eventide provides a easy-to-use flutter interface to access & modify native device calendars (iOS & Android).
📋 Table of Contents #
- Features
- Getting Started
- Quick Start
- API Reference
- Platform-Specific Features
- Exception Handling
- License
- Feedback
🔥 Features #
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.
🔨 Getting Started #
Platform Setup #
Android
Nothing to add on your side. All permissions are already declared in eventide's AndroidManifest.xml
iOS
The following are the lines you need to add to your info.plist
file:
Versions below iOS 17
<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.
Write-only
<key>NSCalendarsWriteOnlyAccessUsageDescription</key>
<string>We need access to your calendar to add information about your trip.</string>
Full access
<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.
🚀 Quick Start #
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.
📚 API Reference #
Calendars #
Create Calendar
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',
);
Retrieve Calendars
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',
);
Delete Calendar
Future<void> deleteCalendar({
required String calendarId,
})
Deletes a calendar by its ID.
await eventide.deleteCalendar(calendarId: calendar.id);
Events #
Create Event
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)],
);
Create Event in Default Calendar
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)],
);
Retrieve Events
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)),
);
Delete Event
Future<void> deleteEvent({
required String eventId,
})
Deletes an event by its ID.
await eventide.deleteEvent(eventId: event.id);
Reminders #
Create Reminder
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),
);
Delete Reminder
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.
Attendees #
⚠️ Platform Limitation: Attendee creation and deletion are only supported on Android due to iOS EventKit API restrictions. However, attendees can be retrieved on both platforms.
Create Attendee (Android Only)
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: 'john.doe@gmail.com',
type: ETAttendeeType.requiredPerson,
);
Delete Attendee (Android Only)
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,
);
Attendee Types
Available ETAttendeeType
values:
ETAttendeeType.unknown
ETAttendeeType.requiredPerson
ETAttendeeType.optionalPerson
ETAttendeeType.resource
ETAttendeeType.organizer
Platform Mapping Tables
Common attendees types mapping
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 attendees types mapping
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 |
Accounts #
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.
Creating Calendars with Accounts
const myAppCalendarIdentifier = "My Company";
await eventide.createCalendar(
title: 'Personal',
color: Colors.blue,
localAccountName: myAppCalendarIdentifier,
);
await eventide.createCalendar(
title: 'Work',
color: Colors.red,
localAccountName: myAppCalendarIdentifier,
);
Filtering by Account
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.
🔧 Platform-Specific Features #
iOS Write-Only Access #
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.
How it works
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}');
Important limitations
⚠️ Key restrictions when using write-only access:
- 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)],
);
Permission handling
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
Best practices
- 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
⚠️ Exception Handling #
Eventide provides custom exceptions for better error handling:
Exception Types #
ETPermissionException
: User denied calendar permissionsETNotFoundException
: Calendar or event not foundETNotEditableException
: Calendar is not editableETGenericException
: General errors during operations
Example Usage #
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');
}
License #
Copyright © 2025 SNCF Connect & Tech. This project is licensed under the MIT License - see the LICENSE file for details.
Feedback #
Please file any issues, bugs or feature requests as an issue on the Github page.