Інтеграція Calendar Provider (системний календар) в Android-додатки
Системний календар Android доступний через CalendarProvider—стандартний Content Provider, доступний з API 14. Більшість додатків використовують один з двох сценаріїв: читання існуючих подій або створення нових. Обидва вимагають дозволів під час виконання та правильної роботи з URI.
Дозволи
Для читання використовуйте READ_CALENDAR; для запису — WRITE_CALENDAR. Обидва є dangerous permissions, запитуються через ActivityCompat.requestPermissions() або ActivityResultContracts.RequestPermission(). Без явних запитів під час виконання на Android 6+ ви отримуєте SecurityException.
Читання подій
События зберігаються в таблиці CalendarContract.Events. Запит через ContentResolver:
val projection = arrayOf(
CalendarContract.Events._ID,
CalendarContract.Events.TITLE,
CalendarContract.Events.DTSTART,
CalendarContract.Events.DTEND,
CalendarContract.Events.CALENDAR_ID
)
val selection = "${CalendarContract.Events.DTSTART} >= ? AND ${CalendarContract.Events.DTEND} <= ?"
val selectionArgs = arrayOf(
startMillis.toString(),
endMillis.toString()
)
val cursor = context.contentResolver.query(
CalendarContract.Events.CONTENT_URI,
projection,
selection,
selectionArgs,
"${CalendarContract.Events.DTSTART} ASC"
)
cursor?.use {
while (it.moveToNext()) {
val title = it.getString(it.getColumnIndexOrThrow(CalendarContract.Events.TITLE))
val dtStart = it.getLong(it.getColumnIndexOrThrow(CalendarContract.Events.DTSTART))
// обробка
}
}
Важливо: використовуйте getColumnIndexOrThrow() замість getColumnIndex()—якщо колонка відсутня в проекції, це негайно впадає з чіткою винятковою ситуацією замість ArrayIndexOutOfBoundsException десь у бізнес-логіці.
Створення події
val values = ContentValues().apply {
put(CalendarContract.Events.CALENDAR_ID, calendarId)
put(CalendarContract.Events.TITLE, "Зустріч із командою")
put(CalendarContract.Events.DTSTART, startMillis)
put(CalendarContract.Events.DTEND, endMillis)
put(CalendarContract.Events.EVENT_TIMEZONE, TimeZone.getDefault().id)
put(CalendarContract.Events.DESCRIPTION, "Обговорення версії v2.1")
}
val uri = context.contentResolver.insert(CalendarContract.Events.CONTENT_URI, values)
val eventId = uri?.lastPathSegment?.toLong()
EVENT_TIMEZONE — це обов'язкове поле. Без нього подія створюється в UTC, і користувачі бачать неправильний час після зміни часового поясу. Це класична помилка, що потрапляє на виробництво й проявляється у користувачів з інших регіонів.
Додавання нагадування
val reminderValues = ContentValues().apply {
put(CalendarContract.Reminders.EVENT_ID, eventId)
put(CalendarContract.Reminders.MINUTES, 15)
put(CalendarContract.Reminders.METHOD, CalendarContract.Reminders.METHOD_ALERT)
}
context.contentResolver.insert(CalendarContract.Reminders.CONTENT_URI, reminderValues)
Відкриття системного UI
Якщо вашому додатку не потрібен прямий доступ до даних, а тільки відкриття стандартного інтерфейсу створення подій, використовуйте Intent без дозволів:
val intent = Intent(Intent.ACTION_INSERT).apply {
data = CalendarContract.Events.CONTENT_URI
putExtra(CalendarContract.Events.TITLE, "Назва події")
putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, startMillis)
putExtra(CalendarContract.EXTRA_EVENT_END_TIME, endMillis)
}
startActivity(intent)
Це простіше, безпечніше й не вимагає дозволів. Це підходить для більшості випадків, коли додаток не веде свій власний список подій.
Інтеграція CalendarProvider займає 1–3 дні залежно від обсягу функцій: читання подій, їх створення, синхронізація кількох облікових записів. Вартість розраховується індивідуально.







