eventide 0.8.1 copy "eventide: ^0.8.1" to clipboard
eventide: ^0.8.1 copied to clipboard

Provides a easy-to-use flutter interface to access & modify native device calendars (iOS & Android)

📆 Eventide #

pub package License: MIT Tests codecov

Eventide provides a easy-to-use flutter interface to access & modify native device calendars (iOS & Android).


📋 Table of Contents #


🔥 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.

Google Calendar App > Parameters > Manage accounts


🔧 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:

  1. First call to retrieveDefaultCalendar() → Shows write-only permission prompt
  2. User grants write-only access → Returns virtual calendar for event creation
  3. 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 permissions
  • ETNotFoundException: Calendar or event not found
  • ETNotEditableException: Calendar is not editable
  • ETGenericException: 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.

12
likes
160
points
147
downloads

Publisher

verified publisherconnect-tech.sncf

Weekly Downloads

Provides a easy-to-use flutter interface to access & modify native device calendars (iOS & Android)

Repository (GitHub)
View/report issues
Contributing

Topics

#flutter #calendar #api #native #eventide

Documentation

API reference

License

MIT (license)

Dependencies

flutter, plugin_platform_interface

More

Packages that depend on eventide