Skip to main content

device_calendar_plus 0.12.0 copy "device_calendar_plus: ^0.12.0" to clipboard
device_calendar_plus: ^0.12.0 copied to clipboard

A modern, maintained Flutter plugin for reading and writing device calendar events on Android and iOS.

0.12.0 - 2026-10-19 #

Added #

  • Android: write attendees with AttendeeWriteAndroid, through EventOptionsAndroid(attendees:) on createEvent and EventUpdateOptionsAndroid(attendees: Patch.set([...])) on updateEvent and updateRecurring. iOS can't write attendees, so it ignores them. Values are Android's own; they read back through the shared Event.attendees. On a synced calendar the server may email each address an invitation. See Attendees (Android) (#71).
  • Event.organizer: who organized the event, as an Organizer with a name and emailAddress (read-only, null when the event has no attendees or organizer). Event.attendees is unchanged. See Attendees and organizer (#236).

Changed #

  • updateRecurring with thisAndFollowing from a series' first occurrence now returns the series' own ID, since nothing comes before it to split off.
  • updateRecurring and deleteRecurring with thisAndFollowing at a time before the series' start throw notFound on both platforms.
  • A blank attendee email throws ArgumentError, on both platforms.
  • Two Patch.sets holding equal lists are now ==.

Fixed #

  • iOS: an iCloud account listed under its email (e.g. user@me.com) can create calendars again. Thanks @eikob (#233).
  • iOS: thisAndFollowing making an all-day series timed lost an occurrence across a DST change, or doubled the split day (#232).
  • Android: thisAndFollowing edits and deletes from a series' first occurrence left that occurrence behind (#234).
  • Android: an allEvents edit that moves the series' start keeps each detached occurrence once, moved with it.

0.11.0 - 2026-10-18 #

Changed #

  • Breaking: createCalendar's platformOptions: parameter is replaced by separate android: and ios: parameters, and the option classes are renamed: CreateCalendarOptionsAndroid is now CalendarOptionsAndroid, CreateCalendarOptionsIos is now CalendarOptionsIos, and CreateCalendarPlatformOptions is gone. Their fields are now optional, so one call can pass both; each applies only on its own platform. See Migrating createCalendar.

Added #

  • Platform-only options: createEvent takes android: EventOptionsAndroid, and updateEvent / updateRecurring take android: EventUpdateOptionsAndroid. They apply only on Android and are ignored on iOS, so shared code can pass them. Their fields speak Android's own semantics, with no attempt at parity with iOS. See Platform-only options.
  • Android: write an event's status with EventStatusAndroid (tentative, confirmed, canceled, Android's STATUS values). It carries through a single-occurrence edit, both updateRecurring spans and the thisAndFollowing split's carried occurrences. An occurrence edited without a status now keeps the series' status instead of being written as confirmed. Android can't write a NULL status to an existing event, so there's no clear; canceled on a series or one occurrence takes it out of listings, as Android's provider reads it (#70).

0.10.3 - 2026-10-18 #

Fixed #

  • Android: updateRecurring with EventSpan.thisAndFollowing on a series with a CountEnd, keeping its rule, no longer runs the new series past the original's end when occurrences before the split were deleted. Deleted occurrences count towards the CountEnd, as on iOS, except for a rule from another app with parts the plugin can't expand (such as BYHOUR, BYWEEKNO, BYYEARDAY or a daily or weekly BYSETPOS). See Changing the rule (#220).
  • Android: updateRecurring with EventSpan.thisAndFollowing and a sub-day start shift across midnight (23:30 to 00:30, say) no longer moves the occurrences the user had moved past the split a day, matching iOS. See Customised occurrences (#224).
  • Android: updateEvent with isAllDay: false and no dates keeps an all-day event or occurrence on its own days, from local midnight of the first to the last second of the last, as on iOS. West of UTC it read back starting the evening before (#205, #216).
  • Android: updateRecurring with isAllDay: false and no start keeps an all-day series on its own days the same way, on EventSpan.allEvents and EventSpan.thisAndFollowing. On a DST change day an occurrence ends an hour off iOS's 23:59:59, as Android stores a timed series' length in fixed seconds (#219).
  • Android: updateEvent toggling isAllDay with only startDate or only endDate moves the other bound into the new frame as well, as on iOS, instead of leaving it a day off (#204).
  • Android: updateRecurring with isAllDay: true but no duration, called with a later occurrence's instance ID, spans the series' own first occurrence's days, as on iOS, on both EventSpan.allEvents and EventSpan.thisAndFollowing. Across a DST change it spanned the named occurrence's instead (#208, #221).
  • Android: updateRecurring with EventSpan.allEvents that toggles a timed series all-day and moves its start no longer lands the series a day off when a DST change falls between the series start and the named occurrence (#186). The same holds for a toggle back to timed with isAllDay: false.
  • Android: updateRecurring with EventSpan.thisAndFollowing that toggles a timed series all-day carries the occurrences the user had changed past the split onto the new series' days. One past a DST change could land a day off and show beside a generated occurrence (#186).
  • Android: updateRecurring with EventSpan.thisAndFollowing and a new CountEnd rule now counts it from the original series' start, as iOS does: the new series keeps only the occurrences left after the split, and is a one-off when there are none. Android used to keep the full count (#124).

0.10.2 - 2026-10-14 #

Changed #

  • hasPermissions and requestPermissions throw a StateError on a status this version doesn't recognise (a null or an unknown string, which only a mismatched platform package sends), instead of silently reading it as denied (#127).

Fixed #

  • iOS: updateEvent with isAllDay: false and new dates on one occurrence of an all-day series no longer reads back hours off on an iCloud calendar (09:00–10:00 in Los Angeles came back as 02:00–03:00: the hours taken as UTC). EventKit's iCloud store got the hours wrong when it detached and flipped the occurrence in one save, so the plugin now detaches first and flips second, as it already did for the all-day flip (#210); the local store, and so the simulator, got it right (#212). Either flip now checks that its first save detached the occurrence and throws DeviceCalendarException(operationFailed), the occurrence unchanged, when it did not, rather than flipping an attached occurrence in one save.
  • updateRecurring with EventSpan.thisAndFollowing and a start on another day the kept rule generates (the Wednesday of a Mon/Wed/Fri series moved to its Friday) now splits the series there: the old series ends before that occurrence, and the new one starts on the new day and continues on the rule. It used to throw DeviceCalendarException(invalidArguments) whenever the rule pins days (#194).
  • iOS: updateRecurring with EventSpan.allEvents and a start on another day of a rule that pins days (the Monday of a Mon/Wed series moved to its Wednesday) moves the series itself and returns its ID. It left the series where it was, detached the first occurrence onto the new day beside it, and returned that occurrence's /RID= ID (#206).
  • Android: hasPermissions without an Activity (a background context) reports denied when the plugin has recorded a denial, instead of notDetermined (#127). CalendarPermissionStatus.notDetermined now also documents that a denial made via another library isn't seen until one request goes through this plugin.
  • Android: updateRecurring with EventSpan.thisAndFollowing on a CountEnd series that started more than five years before the split no longer gives the new series too many occurrences (#124).
  • Android: updateRecurring with isAllDay: true and no start or duration keeps each occurrence on its own calendar day, spanning every day it touched, as on iOS. The series used to read back as a single occurrence, or on the wrong date (#124).
  • Android: a thisAndFollowing split that fails part-way no longer leaves a DELETED=1 tombstone of the new series on a local calendar (#124).
  • Android: updateEvent with isAllDay: true and no startDate or endDate on a timed event, or on one occurrence of a timed series, keeps it on its own calendar day, spanning every day it touched, as on iOS. It used to read back on the wrong date where the event's UTC date isn't its local one.
  • iOS: updateEvent with isAllDay: true on one occurrence of a timed series no longer reads back a day too long on an iCloud calendar. EventKit's iCloud store put the detached occurrence's end a day late when it detached and flipped the occurrence in one save, so the plugin now detaches first and flips second; the local store, and so the simulator, got it right. With no startDate or endDate, on a one-off or an occurrence, the all-day span is now written explicitly, as Android does, rather than left to EventKit to derive.

0.10.1 - 2026-10-01 #

Fixed #

  • updateRecurring refuses a start move to another day with no new recurrenceRule unless the existing rule generates both the day the occurrence moves to and the series' new start. An ordinal or BYSETPOS series (the 4th Thursday of November, say) could move to another occurrence of its weekday (the 3rd Thursday), leaving the start on a day the rule never generates; that now throws DeviceCalendarException(invalidArguments). A move onto another day the rule generates (Monday to Wednesday of a Mon/Wed/Fri rule), which used to throw, is now allowed through EventSpan.allEvents. With thisAndFollowing a move to another day still throws while the rule pins days (#189, #194).
  • iOS: updateRecurring on an all-day series counts duration (or the kept span, when only start moves) in calendar days, so a multi-day all-day series no longer loses or gains its last day when its span crosses a DST change (#195).
  • iOS: updateEvent and updateRecurring no longer add an extra day when they set the end of an all-day event that already exists. The end was written as midnight after the last day, which EventKit reads as one more day on an all-day event; a two-day duration or end gave three days. It's now written the way EventKit stores it, the last second of the last day (#195).

0.10.0 - 2026-09-30 #

Changed #

  • Breaking: updateEvent and deleteEvent act on one thing — a one-off event or a single occurrence — and take instanceId instead of eventId. A bare ID of a recurring series is refused with DeviceCalendarException(invalidArguments) and nothing is written. Moving a series' start through updateEvent used to shift the whole series and drop every earlier occurrence without an error; series-wide changes now go through updateRecurring / deleteRecurring (#175).

    Migrating:

    • updateEvent(eventId: event.instanceId, …) → updateEvent(instanceId: event.instanceId, …). Same for deleteEvent. For a one-off event instanceId equals eventId, so nothing else changes.
    • updateEvent(eventId: seriesId, …) on a recurring event → updateRecurring(seriesId, EventSpan.allEvents, …). startDate / endDate map to start / duration; the other fields are the same.
    • deleteEvent(eventId: seriesId) on a recurring event → deleteRecurring(seriesId, EventSpan.allEvents).

Added #

  • updateRecurring takes reminders (a Patch<List<Duration>>), to set or clear reminders across a series, as updateEvent does for one event (#175).

Fixed #

Recurring events

  • updateRecurring with thisAndFollowing on an event that doesn't repeat is refused with invalidArguments on both platforms, and nothing is written. iOS matched the timestamp against the event itself, so it edited or deleted the whole event and reported success (#124).
  • Android: moving an all-day series with updateRecurring lands on the day you asked for. The new start was compared in the wrong timezone: west of UTC a same-day start was refused and a day-earlier move onto a day the rule doesn't generate got through; east of UTC (e.g. Sydney) a one-day move did nothing (#144).
  • iOS: switching a timed series to all-day with updateRecurring keeps every occurrence on its calendar day. West of UTC the series gained an extra occurrence the day before its start and lost its last one (#187).
  • iOS: updateRecurring refuses a start move with no new rule when it breaks any day the rule pins, as Android does. A yearly BYMONTH=11;BYDAY=4TH series could be moved to a Thursday in December (#188).
  • createEvent and updateRecurring refuse a recurrence rule whose FREQ isn't DAILY/WEEKLY/MONTHLY/YEARLY, or that is malformed, with invalidArguments and write nothing, on both platforms. iOS createEvent dropped the rule and saved a one-off event; Android stored FREQ=HOURLY as an hourly series (#125).
  • Android: updateEvent or deleteEvent on one occurrence of a series on a synced calendar (Google, Exchange) no longer hides the whole series when the series hasn't synced yet (#163).

Native modals

  • The native modals (showEventModal, showCreateEventModal) always complete; several paths used to leave the await hanging or crash (#123):
    • One modal at a time: a call while another modal is showing throws DeviceCalendarException(operationFailed) straight away. It used to orphan the first call's future and, on iOS, fail to present, so both hung.
    • Android: rotating the device while a modal is open no longer drops the result; the future completes when the calendar app returns.
    • iOS: swiping the view modal down completes the future.
    • iOS: a modal no longer silently fails to appear when the app is already presenting a sheet; it's presented on top.
    • With no Activity (Android) or window (iOS) to present from, the call throws DeviceCalendarException(operationFailed), as openAppSettings does. iOS used to crash, and Android threw an unconverted PlatformException.
    • Android: showEventModal on an event that doesn't exist throws DeviceCalendarException(notFound), as iOS does. It used to open the calendar app on nothing and complete normally.

Permissions

  • iOS 18: answering a full-access prompt with Add Events Only reports writeOnly rather than notDetermined, so a createEvent straight after the prompt passes its permission check (#137).

0.9.0 - 2026-09-26 #

Changed #

  • Minimum supported SDK is now Flutter 3.44 / Dart 3.12. Android migrated to Flutter's built-in Kotlin, so the KGP deprecation warning no longer prints on every flutter build (#133).
  • Event times are stored at whole seconds on both platforms. iOS always did this; Android now floors the times it writes, so a start passed with milliseconds reads back without them (#165).

Fixed #

Recurring events

  • updateRecurring with a new rule anchors the series on the first day that rule generates, on both platforms. Changing a weekly series to another weekday left its start on the old day, so the first occurrence was stranded there. A rule that generates no occurrence at all is refused with invalidArguments and the series is left untouched (#140).
  • Android: updateEvent or deleteEvent on a single occurrence of a recurring event in a local calendar no longer makes the other occurrences disappear, the earlier ones for good (#153).
  • Android: deleteRecurring with thisAndFollowing also removes an occurrence on or after the split that had been edited on its own, matching iOS (#157).
  • Android: updateRecurring with thisAndFollowing carries an occurrence on or after the split that had been edited on its own into the new series, with its edits, as iOS does. It used to stay on the old series, listed next to a duplicate, and on a synced calendar the edit was lost (#158).
  • Android: getEvent resolves all-day recurring instance IDs (it always returned null) and returns a recurring master's real end date instead of a zero-length one (#122).

Synced calendars

  • Android: deletes and recurring edits on a synced calendar (Google, Exchange) now reach the server. The plugin wrote them as the calendar's own sync adapter, so they were never uploaded and the next sync brought a deleted event back, one duplicate per cycle. Per-occurrence cancellations upload too (#132, #161).

Listing events

  • Android: listEvents returns an all-day event when the window is a sub-day slice of its date (e.g. 10:00–11:00), and orders all-day events by their local midnight among timed events in non-UTC zones, as iOS does (#122).
  • listEvents rejects an endDate before startDate with ArgumentError, like the other date-range methods, and answers an empty range (endDate == startDate) with no events on both platforms (#162).

Calendars

  • updateCalendar and deleteCalendar throw the documented readOnly for a calendar that can't be modified, on both platforms. iOS used to surface a refused delete as operationFailed; Android renamed or deleted any row it was handed (#126).
  • Android createCalendar refuses a non-local accountType with readOnly, as listSources already reports and iOS already does for non-creatable sources, instead of leaving a phantom calendar the account's sync adapter can wipe (#126).
  • createCalendar / updateCalendar throw ArgumentError for a colorHex that isn't #RRGGBB (the # optional) instead of storing it silently as black, and forward the canonical #RRGGBB to the platform (#126).
  • deleteCalendar('') throws ArgumentError, like the other mutations (#126).
  • Android listCalendars no longer crashes on a provider row with a NULL display name (#126).

Docs #

  • CalendarSource.supportsCalendarCreation, CreateCalendarOptionsIos and CreateCalendarOptionsAndroid describe what the code actually does: iOS creates under iCloud or local, Android under the local account type (#126).

0.8.1 - 2026-09-21 #

Fixed #

  • iOS: a grant is honoured immediately. The first time a user allowed access, the very next call could still fail with permissionDenied until the app was restarted, because EventKit briefly kept reporting notDetermined after the grant (#134).
  • showCreateEventModal needs no calendar permission on Android or iOS 17+ — the system editor saves with its own access — so it now works as a fallback after a denial, and autoPermissions never prompts for it. On iOS 16 and below the in-process editor still requires full access (#121, #141).
  • Android read endpoints report permissionDenied instead of a silent empty result when READ_CALENDAR isn't held, and full-tier mutations require both READ_CALENDAR and WRITE_CALENDAR, matching iOS (#121).
  • iOS: createEvent with a named calendarId under write-only access reports permissionDenied (with a hint) instead of a misleading notFound (#121).

Docs #

  • CalendarPermissionStatus.denied spells out the difference between the permanent denial hasPermissions reports and the just-declined prompt requestPermissions reports.

0.8.0 - 2026-07-22 #

Added #

  • Event.colorHex — the event's custom color as #RRGGBB, with a parsed Event.color getter (read-only, mirrors Calendar.colorHex). Android reads the event's custom color (EVENT_COLOR), null when the event uses its calendar's color; iOS always reports null (EventKit has no per-event color). No write support (#117).

Changed #

  • Breaking: WeeklyRecurrence's wkst field and constructor parameter are renamed to weekStart, matching the spelled-out naming of the other recurrence fields (daysOfWeek, daysOfMonth, setPositions, …). The emitted RRULE is unchanged (still uses the WKST= token). Update WeeklyRecurrence(wkst: …) call sites and .wkst reads to weekStart.

0.7.1 - 2026-06-17 #

Changed #

  • Add the device pub.dev topic (dropped federated to stay within the five-topic limit).

Docs #

  • Slimmed the README to an overview plus a getting-started snippet, and moved the worked examples into focused topic guides under doc/. Trimmed the API doc comments to the consumer-facing contract and removed native-API implementation details. No code or behavior changes.

0.7.0 - 2026-06-17 #

Added #

  • Write-only calendar access. requestPermissions(level: CalendarAccessLevel.writeOnly) asks for the gentler add-only prompt and a grant reports CalendarPermissionStatus.writeOnly. On iOS this is not a permanent ceiling — a later full request re-prompts and upgrades the app in-app (#89).
  • Automatic permission handling. Set DeviceCalendar.instance.autoPermissions to AutoPermissionMode.asNeeded or .full and methods request the access they need on first use instead of throwing when permission is undetermined (#90).
  • createEvent's calendarId is now optional — omit it to write to the platform's default calendar (iOS defaultCalendarForNewEvents, Android the primary or first writable calendar). Resolving the default on Android reads the calendar list, so that path needs full access (#88).
  • Event reminders. createEvent takes reminders: List<Duration>, updateEvent takes Patch<List<Duration>>, and Event.reminders is read back. Relative before-start offsets, normalized to whole minutes on both platforms (#87).

0.6.0 - 2026-06-16 #

Changed #

  • Breaking: updateRecurring() now takes start: DateTime instead of startTime: EventTimeOfDay. start is the anchored occurrence's new start; the whole scope translates by the wall-clock delta, so a single call can move the time and the day together — a nightshift 11 PM → 1 AM (crosses midnight) or a weekly meeting Monday → Tuesday. The delta is measured in the event's timezone, so it is DST-safe. (#103, thanks @SuperKrallan)
  • Breaking: EventTimeOfDay is removed.
  • Breaking: all-day events now accept start (only its date is used) instead of throwing.

Added #

  • updateRecurring() translates implicit-day rules for free: a WeeklyRecurrence() / MonthlyRecurrence() with no pinned day follows the anchor when you move the day.

Behaviour #

  • Moving the day of a rule that pins it explicitly (daysOfWeek, daysOfMonth, positional) without also passing a recurrenceRule throws DeviceCalendarException(invalidArguments). Moving one day of a multi-day rule is genuinely ambiguous (Mon of Mon/Wed/Fri → Tue could mean Tue/Wed/Fri or Tue/Thu/Sat), so the API hands the decision back to you. Time-only, duration-only and whole-week shifts never throw.

0.5.2 - 2026-06-15 #

Changed #

  • No-op updates are now valid instead of throwing. updateEvent, updateRecurring and updateCalendar return without a platform write when no fields are provided, so "save with no edits" is a harmless no-op rather than an ArgumentError (#95). updateRecurring returns the targeted scope's event id. updateRecurring's duration now accepts zero (an instantaneous event); only a negative duration is rejected.

Fixed #

  • Recurrence parsing accepts a negative BYMONTHDAY (e.g. -1 for the last day of the month) instead of rejecting the rule (#91)
  • Turning a recurring occurrence non-recurring with thisAndFollowing + Patch.clear() now splits the series on iOS instead of collapsing the whole series into one event (#93) — see the device_calendar_plus_ios changelog
  • listEvents returns every event across spans longer than ~4 years without dropping or duplicating recurring instances (iOS, #94), and includes zero-duration events sitting exactly on the query start (Android, #416) — see the platform changelogs
  • createCalendar fails with a clear error on iOS sources that can't hold calendars, instead of an opaque failure (#96) — see the device_calendar_plus_ios changelog
  • iOS EventKit operations run off the main thread, preventing UI stalls on large calendars (#79) — see the device_calendar_plus_ios changelog

Docs #

  • Documented listEvents per-instance expansion and the eventId@timestamp instanceId format (#97)

0.5.1 - 2026-06-15 #

Fixed #

  • iOS: showEventModal(edit: true) no longer crashes (#77) — see the device_calendar_plus_ios 0.5.1 changelog

Docs #

  • Clarified showEventModal docs: the view modal (edit: false) is not read-only — on both iOS and Android the native screen lets the user edit the event, and those edits are saved directly by the OS

0.5.0 - 2026-06-11 #

Changed #

  • Breaking: updateRecurring() is redesigned around series semantics (#69). Times are now expressed as startTime (EventTimeOfDay) plus duration instead of absolute startDate/endDate, so every occurrence keeps its own date — changing a series' time no longer re-anchors the series to the occurrence you happened to edit (#68, thanks @SuperKrallan). The recurrence rule is now a Patch<RecurrenceRule>: Patch.set replaces it, Patch.clear collapses the series into a single event. Returns the event ID of the affected scope.
  • Breaking: EventSpan.thisInstance is gone — EventSpan is now just allEvents and thisAndFollowing. Single occurrences are handled by updateEvent / deleteEvent with an instance ID (below).
  • Breaking: updateEvent() with an instance ID (eventId@timestamp) edits only that occurrence, detaching it from the series; a bare event ID on a recurring event updates the whole series.
  • Breaking: deleteEvent() with an instance ID removes only that occurrence; a bare event ID deletes the event (the whole series when recurring).

Added #

  • EventTimeOfDay — small validating hour/minute value class used by updateRecurring().

Fixed #

  • Occurrence edits with a startDate past the occurrence's untouched end are rejected with invalidArguments on iOS too, matching Android, instead of saving an inverted event.
  • Android: events with no status read back as EventStatus.none instead of EventStatus.tentative — thanks @mauriziopinotti (#70).
  • Android: all Calendar Provider work runs on a background thread; large calendars could ANR — thanks @mauriziopinotti (#73).

0.4.0 - 2026-05-25 #

Added #

  • updateRecurring() — update a recurring event with a span choice: EventSpan.allEvents (whole series), thisAndFollowing (this occurrence and every later one), or thisInstance (only this occurrence). Can change or remove the recurrence rule. Resolves the long-standing limitation that updateEvent() could not edit recurrence. Based on @SuperKrallan (#36)
  • deleteRecurring() — delete part of a recurring event with a span choice: EventSpan.allEvents (whole series), thisAndFollowing (this occurrence and every later one), or thisInstance (only this occurrence). Now supported on both iOS and Android (Android uses EXDATE on the master rather than a cancelled exception event). Based on @SuperKrallan (#43)
  • EventSpan enum for choosing the scope of a recurring-event operation, shared by updateRecurring() and deleteRecurring()
  • url parameter on updateEvent() — based on @SuperKrallan (#38)
  • edit parameter on showEventModal() — when true, opens the native editor directly (EKEventEditViewController on iOS, ACTION_EDIT on Android) instead of the read-only viewer. Based on @xonaman (#45)
  • Calendar.color getter — derived Flutter Color? parsed from colorHex, saving consumers from writing the same hex-parsing helper. Based on @xonaman (#46)

Changed #

  • Breaking: updateEvent() description, location and url now take a Patch<String> instead of a String. null leaves the field unchanged, Patch.set(value) assigns a value, Patch.clear() removes it — clearing an optional field was previously impossible.

Fixed #

  • Missing availability parameter in platform interface test mock — based on @SuperKrallan (#39)

0.3.5 - 2026-04-20 #

Added #

  • listSources() to discover calendar accounts/sources — based on @magic-fit (#14)
  • Source selection on createCalendar — iOS via CreateCalendarOptionsIos(sourceId:), Android via optional accountType
  • supportsCalendarCreation on CalendarSource
  • availability parameter on updateEvent() — thanks @SuperKrallan (#29)
  • url field on events (iOS: EKEvent.url, Android: CUSTOM_APP_URI) — thanks @magic-fit (#32)
  • showCreateEventModal() with optional pre-fill (title, dates, location, description)
  • Read-only attendees on events (name, email, role, status)

Fixed #

  • Android: all-day events appearing in wrong day's query in non-UTC timezones (#20)
  • Android: hasPermissions() now works from background services without an Activity (#31)
  • Android: calendar/event queries use application context for background compatibility — thanks @vitalii-vov (#26)
  • Android: notDetermined permission status correctly distinguished from denied — thanks @Albert221 (#12)
  • iOS: calendar source lookup fallback when default source is unavailable — thanks @zaqwery (#13)
  • iOS: createCalendar default fallback now picks iCloud over Gmail CalDAV (#33)

0.3.4 - 2026-02-08 #

Added #

  • iOS: Swift Package Manager support

0.3.3 - 2025-12-21 #

Fixed #

  • Fixed parsing of instanceId for events with @ in their event ID (e.g., Google Calendar IDs like abc123@google.com)

0.3.2 - 2025-12-19 #

Added #

  • Android: CreateCalendarOptionsAndroid for specifying custom account name when creating calendars
  • createCalendar() now accepts optional platformOptions parameter for platform-specific configuration

0.3.1 - 2025-11-07 #

Fixed #

  • showEventModal() now properly awaits until the modal is dismissed (iOS and Android)

0.3.0 - 2024-11-05 #

Changed #

  • BREAKING: deleteEvent() now requires named parameter eventId and always deletes entire series for recurring events
  • BREAKING: updateEvent() now uses named parameter eventId (renamed from instanceId) and always updates entire series for recurring events
  • BREAKING: Removed deleteAllInstances and updateAllInstances parameters - operations on recurring events now always affect the entire series
  • Renamed getEvent() and showEventModal() parameter from instanceId to id to clarify that both event IDs and instance IDs are accepted

Removed #

  • BREAKING: NOT_SUPPORTED error code (no longer needed)

0.2.0 - 2024-11-05 #

Added #

  • openAppSettings() method to guide users to system settings when permissions are denied
  • Testing status documentation in README

Removed #

  • BREAKING: getPlatformVersion() method (unused boilerplate)

Changed #

  • Updated all platform packages to 0.2.0

0.1.1 - 2024-11-04 #

Added #

  • Android: ProGuard/R8 rules for release build compatibility

0.1.0 - 2024-11-04 #

Initial release.

Added #

  • Calendar permissions management (request/check)
  • List device calendars with metadata (name, color, read-only status, primary flag)
  • Query events by date range with optional calendar filtering
  • Get single event by ID with support for recurring event instances
  • Create events with full metadata support
  • Update events including single-instance and all-instance updates for recurring events
  • Delete events (single or all instances)
  • Show native event modal
  • All-day event support with floating date behavior
  • Timezone handling for timed events
  • Typed exception model with DeviceCalendarException and DeviceCalendarError enum
  • Federated plugin architecture (Android + iOS)
  • Support for Android API 24+ (target/compile 35)
  • Support for iOS 13+