Приветствие, о чем статья

Всем привет! Недавно мне нужно было добавить в мое мобильное приложение уведомление, которое писало бы определенный текст в 9 утра. Я потратила некоторое время на чтение документации и разговор с чатом, чтоб понять как и что делать, и я думаю таким же начинающим программистам будет интересно или полезно почитать мои заметки и разбор кода.

Классы для создание уведомлений

Класс NotificationCompat.Builder, используется для создания уведомлений в Android. Основной конструктор:

Основные методы можно посмотреть: NotificationCompat.Builder  |  Android Developers

Класс NotificationChannel - представление настроек, которые применяются к коллекции уведомлений с аналогичной тематикой.

Больше о нем: NotificationChannel  |  Android Developers

Создание каналов уведомлений и управление ими 

Начиная с Android 8.0 (уровень API 26), все уведомления необходимо назначать каналу. Для каждого канала вы можете настроить визуальное и звуковое поведение, которое будет применяться ко всем уведомлениям в этом канале. Пользователи могут изменить эти настройки и решить, какие каналы уведомлений из вашего приложения могут быть навязчивыми или видимыми.

Метод createNotificationChannel используется для создания канала уведомлений. Этот метод позволяет вам определить параметры канала, такие как имя, описание, важность, звук, вибрацию и свет. После создания канала, вы можете использовать его для отправки уведомлений.

Внимание: если вы используете Android 8.0 (уровень API 26) или более позднюю версию и публикуете уведомление без указания канала уведомления, уведомление не отображается, и система регистрирует ошибку.

Создать канал уведомлений. Код

Разрешение, которое вам необходимо объявить в файле манифеста вашего приложения, отображается в следующем фрагменте кода:

<manifest ...>
    <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
    <application ...>
        ...
    </application>
</manifest>

Чтобы создать канал уведомлений, выполните следующие действия:

  1. Создайте объект NotificationChannel с уникальным идентификатором канала, видимым пользователем именем и уровнем важности.

  2. При желании укажите описание, которое пользователь увидит в настройках системы, с помощью setDescription() .

  3. Зарегистрируйте канал уведомлений, передав его в createNotificationChannel() .

В следующем примере показано, как создать и зарегистрировать канал уведомлений:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    val name = getString(R.string.channel_name)
    val descriptionText = getString(R.string.channel_description)
    val importance = NotificationManager.IMPORTANCE_DEFAULT
    val mChannel = NotificationChannel(CHANNEL_ID, name, importance)
    mChannel.description = descriptionText
    val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
    notificationManager.createNotificationChannel(mChannel)
}

Пошаговое объяснение:

  1. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

    1.2 if: Условный оператор, который выполняет код внутри блока, если условие истинно.

    1.3 Класс Build в Android используется для получения информации о текущей версии операционной системы и устройства. Конкретно Build.VERSION.SDK_INT - возвращает текущую версию SDK устройства, а Build.VERSION_CODES.O - константа, представляющая версию Android 8.0.

    1.4 Если текущая версия больше (>=) Android 8.0, то уведомления могут быть отображены на устройствах.

  2. val name = getString(R.string.channel_name)

    2.1 Задает название канала, может быть заменена на val name = "just_name_of_string", т.е. любое стринговое значение.

  3. val descriptionText = getString(R.string.channel_description)

    3.1 задает описание канала, можно также заменить на строку, или вообще не писать.

  4. val importance = NotificationManager.IMPORTANCE_DEFAULT

    4.1 Эта строка устанавливает уровень важности канала уведомлений.

    4.2 Другие уровни важности:

  5. val mChannel = NotificationChannel(CHANNEL_ID, name, importance)

    5.1 Эта строка создает объект канала уведомлений с уникальным идентификатором CHANNEL_ID, именем name и уровнем важности importance.

    5.2 Идентификатор канала (CHANNEL_ID) — это уникальная строка, которую вы определяете самостоятельно, например private val CHANNEL_ID = "first_channel_id"

  6. mChannel.description = descriptionText

    6.1 Эта строка устанавливает описание канала уведомлений. Можно не писать.

  7. val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager

    7.1 Метод getSystemService используется для получения системных служб в Android. Этот метод принимает строку, представляющую имя службы, и возвращает объект, который предоставляет доступ к этой службе. В нашем случае, службе управления уведомлениями.

    7.2 NOTIFICATION_SERVICE — это константа, которая используется для получения экземпляра NotificationManager. Это позволяет взаимодействовать с системной службой уведомлений и управлять уведомлениями в приложении. 

    7.3 Использование as NotificationManager в Kotlin является способом приведения типа объекта, возвращаемого методом getSystemService, к типу NotificationManager. Это необходимо, потому что метод getSystemService возвращает объект типа Object

  8. notificationManager.createNotificationChannel(mChannel)

    8.1 Эта строка регистрирует канал уведомлений в системе.

Когда и где создавать канал уведомления

Создание канала уведомлений — это операция, которая должна быть выполнена один раз, так как после создания канала его параметры (например, важность, звук, вибрация) не могут быть изменены. 

Канал должен быть создан при первом запуске приложения, дальше можно проверить, был ли канал уже создан, и создать его только в том случае, если он еще не существует. Обычно код по созданию канала уведомлений размещается в методе onCreate основного Activity или в классе Application, чтобы гарантировать, что канал будет создан при первом запуске приложения.

Можно поставить проверку на существование канала по его ID, метод getNotificationChannel возвращает канал, если он пуст, следовательно канаkа нет и его можно создать.

val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val existingChannel = notificationManager.getNotificationChannel(CHANNEL_ID)
if (existingChannel == null)
    create_notification()

Важно! Метод getNotificationChannel был добавлен в Android 8.0, и его использование требует минимальной версии API 26. При появлении ошибки
Call requires API level 26 (current min is 24): android.app.NotificationManager#getNotificationChannel`

Необходимо проверить настройки gradle, значение minSdk = 26.

Установите содержимого уведомления

Для начала задайте содержимое и канал уведомления с помощью объекта NotificationCompat.Builder. В следующем примере показано, как создать уведомление со следующим содержанием:

  • Небольшой значок, установленный с помощью setSmallIcon() . Это единственный необходимый контент, видимый пользователю.

  • Заголовок, заданный с помощью setContentTitle() .

  • Основной текст, заданный с помощью setContentText() .

  • Приоритет уведомления, установленный setPriority() . Приоритет определяет, насколько навязчивым будет уведомление на Android 7.1 и более ранних версиях. Для Android 8.0 и более поздних версий вместо этого установите важность канала, как показано в следующем разделе.

var builder = NotificationCompat.Builder(this, CHANNEL_ID)
        .setSmallIcon(R.drawable.notification_icon)
        .setContentTitle(textTitle)
        .setContentText(textContent)
        .setPriority(NotificationCompat.PRIORITY_DEFAULT)

У NotificationCompat.Builder есть много методов, рассмотрим основные:

  • setSmallIcon(int icon) - устанавливает малую иконку уведомления, обязательный.

  • setContentTitle(CharSequence title) - Устанавливает заголовок уведомления, не обязательный, но рекомендуемый.

  • setContentText(CharSequence text) - Устанавливает текст уведомления, не обязательный, но рекомендуемый.

  • setPriority(int priority) - Устанавливает приоритет уведомления, не обязательный, но рекомендуемый.

  • setContentIntent(PendingIntent intent) - Устанавливает PendingIntent, который будет выполнен при нажатии на уведомление. Каждое уведомление должно реагировать на нажатие, обычно для открытия действия в вашем приложении, соответствующего уведомлению. Не обязательный, но рекомендуемый.

  • setAutoCancel(boolean autoCancel) - Устанавливает, должно ли уведомление автоматически удаляться после нажатия на него.

  • setLargeIcon(Bitmap icon) - Устанавливает большую иконку уведомления.

  • setStyle(NotificationCompat.Style style) - Устанавливает стиль уведомления.

  • setSound(Uri sound) - Устанавливает звук, который будет воспроизводиться при получении уведомления.

  • setVibrate(long[] pattern) - Устанавливает паттерн вибрации для уведомления.

  • addAction(NotificationCompat.Action action) - Добавляет действие к уведомлению. Уведомление может содержать до трех кнопок действий, которые позволяют пользователю быстро реагировать, например отложить напоминание или ответить на текстовое сообщение.

val intent = Intent(this, AlertDetails::class.java).apply {
    flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
}
val pendingIntent: PendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE)

val builder = NotificationCompat.Builder(this, CHANNEL_ID)
        .setSmallIcon(R.drawable.notification_icon)
        .setContentTitle("My notification")
        .setContentText("Hello World!")
        .setPriority(NotificationCompat.PRIORITY_DEFAULT)
        .setContentIntent(pendingIntent)
        .setAutoCancel(true)

Показать уведомление

Чтобы уведомление появилось, вызовите NotificationManagerCompat.notify() , передав ему уникальный идентификатор уведомления и результат NotificationCompat.Builder.build() . Это показано в следующем примере:

with(NotificationManagerCompat.from(this)) {
    if (ActivityCompat.checkSelfPermission(
            this@MainActivity,
            Manifest.permission.POST_NOTIFICATIONS
        ) != PackageManager.PERMISSION_GRANTED
    ) {
       return@with
    }
    notify(NOTIFICATION_ID, builder.build())
}

Если вы встретили ошибку Unresolved reference: POST_NOTIFICATIONS, добавьте в код import android.Manifest

Разрешение

Если вы попробовали запустить код, но не увидели уведомления, то вот решение.
Начиная с Android 13, разрешение POST_NOTIFICATIONS требует явного запроса у пользователя.

Добавьте константу. REQUEST_CODE_POST_NOTIFICATIONS — это константа, которая используется для идентификации запроса разрешений. Она помогает отличить запрос на разрешение для уведомлений от других запросов на разрешения.

private val REQUEST_CODE_POST_NOTIFICATIONS = 1

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
    if (ActivityCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
        ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.POST_NOTIFICATIONS), REQUEST_CODE_POST_NOTIFICATIONS)
    }
}

Этот блок кода проверяет, есть ли у приложения разрешение на отправку уведомлений. ActivityCompat.checkSelfPermission возвращает PackageManager.PERMISSION_GRANTED, если разрешение предоставлено, и PackageManager.PERMISSION_DENIED, если разрешение не предоставлено.

Если разрешение не предоставлено, этот блок кода запрашивает его у пользователя. ActivityCompat.requestPermissions показывает диалоговое окно, в котором пользователь может предоставить или отклонить разрешение.

Уведомление ко времени

Для установки уведомления необходимо создать “будильник”. Который будет выполнять определенную задачу в определенный момент.

AlarmManager — это класс в Android, который предоставляет доступ к системному сервису будильников. Этот сервис позволяет приложениям планировать выполнение задач в определенное время или через определенный интервал времени. AlarmManager часто используется для выполнения фоновых задач, таких как обновление данных, напоминания или будильники.

AlarmReceiver — это класс, который обычно наследуется от BroadcastReceiver и используется для обработки событий, инициированных AlarmManager. Когда будильник срабатывает, система отправляет широковещательное сообщение (broadcast), которое принимается AlarmReceiver.

BroadcastReceiver — это компонент Android, который позволяет приложениям принимать и обрабатывать широковещательные сообщения (broadcasts) от системы или других приложений. Широковещательные сообщения могут быть использованы для различных целей, таких как уведомления о системных событиях (например, изменение уровня заряда батареи), обновления данных или выполнения фоновых задач.

Также для работы в фоновом режиме необходимо выдать разрешения в Manifest

<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>
<uses-permission android:name="android.permission.USE_EXACT_ALARM"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>

Создание класса.

class AlarmReceiver : BroadcastReceiver() {
  override fun onReceive(context: Context, intent: Intent) {
    //TODO
  }
}

Переопределяя метод onReceive, вы можете определить, что должно произойти, когда будильник срабатывает.

Признаться здесь я долго пыталась с помощью компаньона передать метод sendNotification мз MainActivity, но в итоге решила что проще будет его перенести и вызвать сразу в классе AlarmReceiver.

Возвращаясь в MainActivity, вызовем метод, устанавливающим тот самый будильник. (Не забудьте его вызвать в onCreate).

fun setAlarm(context: Context) {
        val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
        val intent = Intent(context, AlarmReceiver::class.java)
        val pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
        val calendar: Calendar = Calendar.getInstance().apply {
            set(Calendar.HOUR_OF_DAY, 9)
            set(Calendar.MINUTE, 0)
            set(Calendar.SECOND, 0)
            set(Calendar.MILLISECOND, 0)
            if (before(Calendar.getInstance())) {
                add(Calendar.DATE, 1)
            }
        }
        alarmManager.setExact(
            AlarmManager.RTC_WAKEUP,
            calendar.timeInMillis,
            pendingIntent
        )
    }
  1. val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager

    Здесь мы получаем экземпляр AlarmManager, который используется для управления будильниками и таймерами.

  2. val intent = Intent(context, AlarmReceiver::class.java)

    Создается Intent, который будет отправлен, когда будильник сработает. AlarmReceiver — это класс, который будет обрабатывать этот Intent.

  3. val pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)

    Создается PendingIntent, который будет использоваться для запуска AlarmReceiver в будущем. PendingIntent.FLAG_IMMUTABLE указывает, что PendingIntent не может быть изменен после создания.

  4. Создаем экземпляр Calendar и используем apply для выполнения блока кода на этом экземпляре. Calendar.getInstance() возвращает текущую дату и время, далее устанавливаем время на 9 утра. Если установленное время уже прошло before(Calendar.getInstance()) (т.е. текущее время больше, чем установленное время), добавляем один день к текущей дате.

  5. Будильник устанавливается с использованием метода setExact, который запускает PendingIntent в точное время, указанное в calendar. AlarmManager.RTC_WAKEUP указывает, что устройство должно проснуться, чтобы выполнить этот будильник.

Полный код

class MainActivity : AppCompatActivity() {
    private val CHANNEL_ID = "channel_id"

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        val existingChannel = notificationManager.getNotificationChannel(CHANNEL_ID)
        if (existingChannel == null) {
            createNotificationChannel()
        }

        setAlarm(this)
    }

    private fun createNotificationChannel() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val name = "name notification"
            val descriptionText = "some description"
            val importance = NotificationManager.IMPORTANCE_DEFAULT
            val mChannel = NotificationChannel(CHANNEL_ID, name, importance)
            mChannel.description = descriptionText
            val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
            notificationManager.createNotificationChannel(mChannel)
        }
    }

    fun setAlarm(context: Context) {
        val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
        val intent = Intent(context, AlarmReceiver::class.java)
        val pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
        val calendar: Calendar = Calendar.getInstance().apply {
            set(Calendar.HOUR_OF_DAY, 9)
            set(Calendar.MINUTE, 0)
            set(Calendar.SECOND, 0)
            set(Calendar.MILLISECOND, 0)
            if (before(Calendar.getInstance())) {
                add(Calendar.DATE, 1)
            }
        }
        alarmManager.setExact(
            AlarmManager.RTC_WAKEUP,
            calendar.timeInMillis,
            pendingIntent
        )
    }
}

class AlarmReceiver : BroadcastReceiver() {
    private val CHANNEL_ID = "channel_id"

    override fun onReceive(context: Context, intent: Intent) {
        sendNotification(context)
    }

    private fun sendNotification(context: Context) {
        val intent = Intent(context, MainActivity::class.java).apply {
            flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
        }
        val pendingIntent: PendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
        val builder = NotificationCompat.Builder(context, CHANNEL_ID)
            .setSmallIcon(R.drawable.icon)
            .setContentTitle("Всем привет")
            .setContentText("Hello")
            .setPriority(NotificationCompat.PRIORITY_DEFAULT)
            .setContentIntent(pendingIntent)
            .setAutoCancel(true)
            .setStyle(NotificationCompat.BigTextStyle())

        with(NotificationManagerCompat.from(context)) {
            if (ActivityCompat.checkSelfPermission(
                    context,
                    Manifest.permission.POST_NOTIFICATIONS
                ) != PackageManager.PERMISSION_GRANTED
            ) {
                return@with
            }
            notify(123, builder.build())
        }
    }
}

Заключение

Спасибо всем кто прочитал до конца! Если есть идеи по оптимизации или какие-то вопросы, буду рада со всеми пообсуждать.

Комментарии (1)


  1. maestro7it
    01.10.2024 05:03

    Создание уведомлений на языке Kotlin для Android — это простой и эффективный процесс.

    Вы можете использовать класс NotificationCompat.Builder для создания уведомления.

    Вот пример:

    val notificationManager = NotificationManagerCompat.from(context)
    val builder = NotificationCompat.Builder(context, "CHANNEL_ID")
        .setSmallIcon(R.drawable.ic_notification)
        .setContentTitle("Заголовок уведомления")
        .setContentText("Текст уведомления")
        .setPriority(NotificationCompat.PRIORITY_DEFAULT)
    
    notificationManager.notify(notificationId, builder.build())

    Не забудьте создать канал уведомлений для Android 8.0 и выше. Это добавит вашему приложению профессиональный вид и улучшит взаимодействие с пользователями.