device_calendar_plus_android 0.9.1
device_calendar_plus_android: ^0.9.1 copied to clipboard
Android implementation of the device_calendar_plus plugin.
0.9.1 - 2026-10-01 #
Fixed #
updateRecurringrefuses a start move with no new rule unless the existing rule generates the series' new start. It compared only the weekday, day of month and month the rule pins, so an ordinal BYDAY or a BYSETPOS series (the 4th Thursday of November, say) could move to another occurrence of its weekday (the 3rd Thursday), leaving DTSTART on a day the rule never generates (#189). A move onto another day the rule generates (Monday to Wednesday of a Mon/Wed/Fri rule) is now allowed throughallEvents; withthisAndFollowinga move to another day still throws while the rule pins days, until iOS can split such a series (#194).
0.9.0 - 2026-09-30 #
Changed #
- Breaking:
updateEventanddeleteEventwithout atimestamprefuse a recurring series withINVALID_ARGUMENTSand write nothing; series-wide changes go throughupdateRecurring/deleteRecurring. This also closes the path that wroteDTENDonto a recurring master (#175, #125).deleteEventon a bare ID that is gone or only aDELETED=1tombstone reportsNOT_FOUND; collecting a tombstoned series isdeleteRecurring(allEvents)'s job. updateRecurringforwards areminderspatch (#175).
Fixed #
updateRecurringon an all-day series converts the newstartto the stored UTC midnight before comparing it, asupdateEventalready did. 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 a one-day move counted as zero days and the series stayed put (#144).- Modal plumbing (#123): a second
showEventModal/showCreateEventModalwhile one is showing failsOPERATION_FAILEDinstead of orphaning the first reply; a pending modal survives a configuration change (rotation) and resolves when the activity goes away for good; no Activity repliesOPERATION_FAILEDrather than an unconvertederror; andshowEventModallooks the event up first and failsNOT_FOUNDfor one that doesn't exist, matching iOS. updateEventordeleteEventon a single occurrence of a recurring event on a synced calendar (Google, Exchange) no longer hides the whole series when the series hasn't synced yet. Until the calendar's first sync of the series, it lists as it was, and the per-occurrence change shows once the calendar syncs (#163, the synced-calendar side of #153).createEventandupdateRecurringrefuse a recurrence rule whoseFREQisn'tDAILY/WEEKLY/MONTHLY/YEARLY(or that is malformed) withINVALID_ARGUMENTSbefore writing, matching iOS.FREQ=HOURLYused to be stored as an hourly series, and malformed input failed asOPERATION_FAILED(#125).
0.8.0 - 2026-09-26 #
Fixed #
- Event times are now written at whole seconds, as on iOS. A sub-second
start was stored with its milliseconds, and some providers (Samsung,
Android 11) expand a series' occurrences at whole seconds, so
getEventon the series and its listed occurrences disagreed by up to a second.createEvent,updateEventandupdateRecurringnow floor start and end to the second, and a series re-anchored byupdateRecurringdrops any milliseconds it was stored with (#165). updateRecurringwiththisAndFollowingnow carries an occurrence that was edited on its own, and falls on or after the split, into the new series, as iOS does. It keeps its own title, time, reminders and other edits, and the new series no longer lists a second copy beside it. When the split moves the start, the occurrence's slot moves by the same number of days. An occurrence deleted on its own stays deleted if the start stays put, and comes back if the start moves, matching iOS. On a synced calendar the occurrence is re-created on the new series and the old one is deleted, since the server ties it to the old series (#158).- Deletes and recurring edits on a synced calendar (Google, Exchange, any
account but local) now reach the server. The plugin wrote them as the
calendar's own sync adapter, which the provider takes as the server's word:
the row was changed or removed locally, never marked for upload, and the
next sync brought the event back — one duplicate per delete-and-sync cycle.
deleteEvent,deleteRecurring(whole series, one occurrence, orthisAndFollowing, including the detached occurrences it sweeps) andupdateRecurringnow write as the app they are, so the adapter finds aDELETED/DIRTYrow to upload; local calendars, which have no adapter, keep the direct deletes (#132, #161). deleteRecurringwiththisAndFollowingnow removes a detached occurrence that falls on or after the split. Truncating the series' rule left an occurrence that had been edited on its own behind as an orphan row, which came back inlistEventsonce the provider next rebuilt its Instances cache; iOS removes it, so Android does too.updateCalendaranddeleteCalendarrefuse a calendar whose access level is below contributor — the same calendarslistCalendarsreports asreadOnly— withREAD_ONLYbefore writing, instead of renaming or deleting any row they were handed (#126).createCalendarrefuses a non-localaccountTypewithREAD_ONLY, aslistSourcesalready reports, instead of sync-adapter-inserting a phantom calendar into another account's namespace (#126).listCalendarsreads a NULL display name as empty instead of handing Dart a null it casts (#126).getEventresolves the instance ID of an all-day recurring occurrence. The lookup went through the all-day date filter with a two-second window, which collapses to an empty date range in every timezone, so it always returned null; it now matches the Instances row onEVENT_IDandBEGIN(#122).getEventwith a bare recurring ID returns the master's real end date. A recurring row storesDURATIONwith noDTEND, and the end used to fall back to the start (#122).listEventssorts on the start date it reports, so an all-day event lands at its local midnight among timed events in non-UTC zones instead of at its stored UTC-midnight instant. Matches iOS (#122).- Editing or deleting a single occurrence of a recurring event on a local
calendar no longer makes the rest of the series disappear. The Calendar
Provider keys a series' exceptions by
_sync_id, which a local calendar never gets, so the exception insert wiped the master's occurrences from its Instances cache — the earlier ones for good. A local series is now given a_sync_idbefore its first exception is written, and deleting the series removes its detached occurrences with it (#153). getEvent,updateEvent,updateRecurring, anddeleteEvent/deleteRecurringfor anything short of the whole series no longer see an event that another app has deleted but the provider only tombstoned (DELETED=1, the fate of any event with a_sync_iddeleted outside a sync adapter, which now includes a local series edited per occurrence). Such an event reads as not found instead of accepting edits against a row that never shows inlistEvents. On a local calendar a whole-series delete still collects the tombstone.updateRecurringwith a new rule anchors the series on the first day that rule generates, as iOS does. 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 withINVALID_ARGUMENTSand the series is left untouched (#140).listEventsreturns an all-day event when the window is a sub-day slice of its date (e.g. 10:00–11:00). The all-day date filter mapped both window edges to the same UTC midnight, so the range collapsed to nothing; the end edge now rounds up to the next UTC midnight when it isn't on a local midnight. Matches iOS.
Changed #
- Migrated to Flutter's built-in Kotlin: the plugin no longer applies the
Kotlin Gradle Plugin itself, which silences the KGP deprecation warning on
every
flutter buildand keeps the plugin building once Flutter rejects plugins that apply KGP themselves (#133). - Minimum supported SDK is now Flutter 3.44 / Dart 3.12, as the migration requires.
0.7.2 - 2026-09-21 #
Fixed #
showCreateEventModalno longer requiresREAD_CALENDAR.ACTION_INSERTneeds no permission, so the gate only blocked the one path that still works after a denial (#121, #141).listCalendars,listSources,listEventsandgetEventthrowpermissionDeniedwhenREAD_CALENDARisn't held instead of returning an empty result (#121).- Full-tier operations (calendar mutations, event update/delete, recurring
update/delete) require
READ_CALENDARandWRITE_CALENDAR;WRITE_CALENDARalone is the write-only tier and could previously mutate calendars that iOS rejects. The error message names the tier that's missing (#121). showCreateEventModalwritesEXTRA_EVENT_ALL_DAYas a boolean extra, so the all-day prefill is honoured (#121).
Changed #
- Permission gates are consolidated in
PermissionGates.kt; event cursor projections go throughEventColumnspresets (#119). No behaviour change.
0.7.1 - 2026-07-22 #
Added #
- Event maps include
colorHexread fromEvents.EVENT_COLORwhen the event has a custom color; absent otherwise (#117).
0.7.0 - 2026-06-17 #
Added #
- Write-only access: a
writeOnlyrequest asks forWRITE_CALENDARonly.READ_CALENDARandWRITE_CALENDARshare theCALENDARgroup, so a later full request escalates to read access with no dialog (#89). createEventwith nocalendarIdresolves a default calendar — the primary writable calendar, falling back to the first writable one (#88).- Event reminders via
CalendarContract.Remindersrows andEvents.HAS_ALARM(#87).
Fixed #
- Permanent-denial detection keys off
WRITE_CALENDAR, and a cancelled permission dialog no longer reportsdenied(#108). createEventwith nocalendarIdreportspermissionDeniedrather than "no writable calendar" when it can't read the calendar list to resolve a default (#112).
0.6.0 - 2026-06-16 #
Changed #
- Breaking:
updateRecurringtakes the anchored occurrence's new start (newStartMillis) instead ofstartMinuteOfDay.shiftDatetranslates the series anchor by the wall-clock delta (calendar-day count + time-of-day) in the event'sEVENT_TIMEZONE, so a move can change the day and time together and stays correct across DST (#103).
Fixed #
- A time-only
allEventsedit now re-writes the unchanged RRULE so the CalendarProvider re-expands the Instances cache; without it a moved DTSTART could read back as a single occurrence.
Behaviour #
updateRecurringrejects astartthat moves the day of a series whose RRULE pins it (BYDAY/BYMONTHDAY/BYMONTH) unless a new rule is also supplied (dayMoveConflictsWithRule).
0.5.2 - 2026-06-15 #
Fixed #
listEventsnow returns a zero-duration (instantaneous) event that sits exactly on the query's start time; the half-open overlap check previously excluded it (#416)
0.5.1 - 2026-06-15 #
- No functional changes; version aligned with the rest of the suite for the 0.5.1 release
0.5.0 - 2026-06-11 #
Changed #
- Breaking: recurring-edit split —
updateRecurring/deleteRecurringaccept onlyallEventsandthisAndFollowing; single occurrences go throughupdateEvent/deleteEventwith an occurrence timestamp (detached exception rows; deletes viaSTATUS_CANCELEDexceptions) updateRecurringtime changes preserve each occurrence's date, replacing only the time-of-day;thisAndFollowingtruncates the master withUNTILto match iOS'sEKSpan.futureEventssplit
Fixed #
- Calendar Provider work now runs on a background thread. Method-channel handlers were doing blocking provider queries on the main thread, which could ANR on large calendars (#73). Thanks @mauriziopinotti for the report and a working proof-of-fix.
- A NULL
STATUScolumn reads back asnone; it was defaulted to0, which isSTATUS_TENTATIVE, so status-less events came back tentative (#70) — thanks @mauriziopinotti
0.4.0 - 2026-05-25 #
Added #
updateRecurring()— series-level recurring-event edits withEventSpan(allEvents / thisAndFollowing / thisInstance).thisAndFollowingtruncates the master withUNTILand starts a new series;thisInstancewrites a detached exception event.deleteRecurring()—allEventsdeletes the master;thisAndFollowingtruncates viaUNTIL;thisInstanceappends to the master'sEXDATEcolumn (no separate exception event needed).urlfield on events viaEvents.CUSTOM_APP_URIPatch<T>support inupdateEvent()— null leaves a field unchanged,Patch.setwrites,Patch.clearwrites the empty string to removeeditflag onshowEvent()— firesIntent.ACTION_EDITinstead ofACTION_VIEW
Fixed #
- Event deletion now uses sync-adapter context so EventKit-equivalent listEvents calls stop returning the deleted row immediately
Changed #
- Extracted all-day date-conversion helpers; no behaviour change
0.3.5 - 2026-04-20 #
Fixed #
- All-day events appearing in wrong day's query in non-UTC timezones (#20)
PermissionServiceacceptsContext—hasPermissions()works without an Activity (#31)
0.3.4 - 2026-02-08 #
Version sync with other packages. No functional changes.
0.3.3 - 2025-12-21 #
Fixed #
- Fixed parsing of
instanceIdfor events with@in their event ID (e.g., Google Calendar IDs likeabc123@google.com)
0.3.2 - 2025-12-19 #
Added #
CreateCalendarOptionsAndroidfor specifying custom account name when creating calendarscreateCalendar()now accepts optionalaccountNameparameter via platform options
0.3.1 - 2025-11-07 #
Fixed #
showEvent()now usesstartActivityForResult()to properly await until the calendar activity is dismissed
0.3.0 - 2024-11-05 #
Changed #
- BREAKING:
deleteEvent()now always deletes entire series for recurring events (removeddeleteAllInstancesparameter) - BREAKING:
updateEvent()now always updates entire series for recurring events (removedupdateAllInstancesparameter) - Native code now extracts event ID from instance ID format automatically
Removed #
- BREAKING:
NOT_SUPPORTEDerror code (no longer needed as single-instance operations are not attempted)
0.2.0 - 2024-11-05 #
0.1.1 - 2024-11-04 #
Added #
- ProGuard/R8 rules to prevent code stripping in release builds
- Automatic consumer ProGuard rules configuration
0.1.0 - 2024-11-04 #
Initial release.