Создание уведомлений на языке Kotlin

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

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

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

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

1e0ceca1eb16558eae25a1e80189093e.png

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

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

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

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

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

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

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

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

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


    
    
        ...
    

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

  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 Другие уровни важности:

    a49c10c847419c1fd955bf3305869ec6.png
  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 показывает диалоговое окно, в котором пользователь может предоставить или отклонить разрешение.

a4506e4a3625b336546ff14628045115.png

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

В документации есть раздел об этом Display time-sensitive notifications  |  Views  |  Android Developers, но я не нашла в нем ничего нового. Поэтому представляю разбор собственного кода.

private fun scheduleNotification() {
    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)
        }
    }

    val delay = calendar.timeInMillis - System.currentTimeMillis()
    Handler(Looper.getMainLooper()).postDelayed({
        sendNotification()
    }, delay)
}

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

val delay = calendar.timeInMillis - System.currentTimeMillis()

Вычисляем задержку между текущим временем и запланированным временем. Используем Handler для запланирования выполнения sendNotification через задержку.

Полный код

class MainActivity : AppCompatActivity() {

    private val CHANNEL_ID = "channel_id"

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

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)
            }
        }


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

    private fun create_notification(){
        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 sendNotification(){

        val intent = Intent(this, MainActivity::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)

        var builder = NotificationCompat.Builder(this, CHANNEL_ID)
            .setSmallIcon(R.drawable.icon)
            .setContentTitle("Hello world!")
            .setContentText("Hi!")
            .setPriority(NotificationCompat.PRIORITY_DEFAULT)
            .setContentIntent(pendingIntent)
            .setAutoCancel(true)

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

    }

    private fun scheduleNotification() {
        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)
            }
        }

        val delay = calendar.timeInMillis - System.currentTimeMillis()
        Handler(Looper.getMainLooper()).postDelayed({
            sendNotification()
        }, delay)
    }
}

Заключение

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

© Habrahabr.ru