eventide 0.8.1
eventide: ^0.8.1 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 feed to the plugin with a timezone aware DateTime class.
🔨 Getting Started #
Installation #
Add Eventide to your pubspec.yaml
:
dependencies:
eventide: ^0.8.1
Platform Setup #
Android
Nothing to add on your side. All permissions are already declared in eventide's AndroidManifest.xml
iOS
To read/write calendar data, your app must include the following permissions in its info.plist
file:
<key>NSCalendarsUsageDescription</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>
<key>NSCalendarsWriteOnlyAccessUsageDescription</key>
<string>We need access to your calendar to add information about your trip.</string>
🚀 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 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),
],
);
// 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',
);
Retrieve Default Calendar
Future<ETCalendar?> retrieveDefaultCalendar()
Retrieves the default calendar. On iOS 17+, this prompts for write-only access.
final defaultCalendar = await eventide.retrieveDefaultCalendar();
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)],
);
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 retrieveDefaultCalendar()
on iOS 17+, the system will prompt the user for write-only access if full access hasn't been granted. This returns a special virtual calendar that can only be used for creating events.
// Will prompt user for write only access on iOS 17+
final defaultCalendar = await eventide.retrieveDefaultCalendar();
if (defaultCalendar != null) {
// You can create events in this calendar
final event = await eventide.createEvent(
calendarId: defaultCalendar.id,
title: 'New Meeting',
startDate: DateTime.now(),
endDate: DateTime.now().add(Duration(hours: 1)),
);
print('Event created: ${event.title}');
}
Important limitations
⚠️ Key restrictions when using write-only access:
- No reading capabilities: Attempting to retrieve events from a write-only calendar will throw an
ETGenericException
- Virtual calendar: The returned calendar is a virtual representation and doesn't correspond to a real user calendar
- Create only: You can only create new events, not modify or read existing ones
// ❌ This will fail with write-only access
try {
final events = await eventide.retrieveEvents(
calendarId: defaultCalendar.id,
);
} catch (e) {
// Will throw ETGenericException on iOS with write-only access
print('Cannot read events with write-only access: $e');
}
// ✅ This works with write-only access
final newEvent = await eventide.createEvent(
calendarId: defaultCalendar.id,
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
retrieveDefaultCalendar()
→ Shows write-only permission prompt - User grants write-only access → Returns virtual calendar for event creation
- User denies access → Throws
ETPermissionException
Best practices
- Always check if the returned calendar is not null before using it
- Handle
ETGenericException
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.)
⚠️ 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.