Korzystanie z powiadomień w Androidzie

Te warsztaty są częścią kursu Zaawansowany Android w Kotlinie. Najwięcej korzyści z tego kursu uzyskasz, jeśli przejdziesz wszystkie ćwiczenia w kolejności, ale nie jest to obowiązkowe. Wszystkie ćwiczenia z tego kursu znajdziesz na stronie docelowej ćwiczeń z zaawansowanego Androida w Kotlinie.

Wprowadzenie

Powiadomienia to wiadomości wyświetlane użytkownikowi poza interfejsem aplikacji. Powiadomienia są wyświetlane u góry ekranu, gdy urządzenie jest odblokowane, lub na ekranie blokady, gdy urządzenie jest zablokowane (w zależności od ustawień zabezpieczeń).

Typowe powiadomienie składa się z tytułu, opisu i ikony. Powiadomienie może też zawierać klikalne działania, szybką odpowiedź, rozwijane treści i obrazy.

Powiadomienia mogą dostarczać aktualne informacje i zawierać przyciski umożliwiające użytkownikowi szybkie wykonywanie działań, takich jak wysyłanie odpowiedzi czy wyłączanie alarmu. Kliknięcie powiadomienia przenosi użytkownika do widoku w aplikacji związanego z treścią powiadomienia.

Powiadomienia to przydatny sposób na przypominanie użytkownikom o ważnym zadaniu, informowanie ich o tym, co się wydarzyło, lub przekazywanie ważnych informacji, których potrzebują natychmiast, gdy aplikacja działa w tle. Oszczędnie korzystaj z powiadomień. W ten sposób nie tylko szanujesz użytkowników, ale też zwiększasz prawdopodobieństwo, że powiadomienie z Twojej aplikacji przyciągnie ich uwagę.

Z tego ćwiczenia w Codelabs dowiesz się, jak tworzyć i używać powiadomień w aplikacji na Androida.

Co warto wiedzieć

Musisz znać:

  • Jak tworzyć aplikacje na Androida w Kotlinie. W szczególności pracuj z pakietem Android SDK.
  • Jak projektować aplikacje przy użyciu komponentów architektury i powiązania danych.
  • Podstawowa znajomość BroadcastReceiver
  • Podstawowa znajomość AlarmManager

Czego się nauczysz

  • Jak utworzyć, ostylować i wysłać powiadomienie.
  • Jak anulować powiadomienia.
  • Jak tworzyć kanały powiadomień.
  • Jak dodawać szybkie działania do powiadomień.
  • Jak wyświetlać plakietki powiadomień na ikonie aplikacji.

Jakie zadania wykonasz

  • Dodaj powiadomienie do aplikacji początkowej.
  • anulować wysłane wcześniej powiadomienie;
  • Twórz kanały dla różnych typów powiadomień.
  • Dostosuj powiadomienia w aplikacji startowej.
  • Dodaj szybkie działania, aby powiadomienie było interaktywne.
  • Wyłącz plakietki powiadomień.

Gotowanie jajek jest proste, ale może być trudne, jeśli nie będziesz śledzić czasu. W tym module z kodem będziesz pracować nad aplikacją do odmierzania czasu gotowania jajek i dopracowywać ją, aby była tak idealna jak Twoje przyszłe jajka. Zaczniesz od działającej aplikacji do odmierzania czasu gotowania jajek, która umożliwia użytkownikowi ustawianie różnych czasów gotowania dla różnych rodzajów jajek. Timer odlicza czas od wybranego przedziału czasu i wyświetla komunikat, gdy jajka są gotowe.

Może się to wydawać funkcjonalne, ale jest dalekie od doskonałości i nie jest zbyt przyjazne dla użytkownika. Po pierwsze, komunikat toast jest wyświetlany tylko przez krótki czas, więc łatwo go przeoczyć. Po drugie, jeśli aplikacja nie jest na pierwszym planie lub urządzenie jest zablokowane, po zniknięciu komunikatu toast nie ma wizualnego wskaźnika stanu timera.

Najlepiej, aby minutnik używał powiadomień, aby informować użytkowników o upływie czasu. Użytkownik musi natychmiast wiedzieć, że jajka są gotowe, w przeciwnym razie będą rozgotowane. Powiadomienia są wizualne, mogą zawierać dźwięki i powodować wibracje urządzenia – wszystko to ma na celu przyciągnięcie uwagi użytkownika. W ten sposób uzyskasz idealne jajka i zadowolonych, najedzonych użytkowników.

Aby pobrać przykładową aplikację, możesz wykonać jedną z tych czynności:

Sklonuj repozytorium z GitHub i przejdź do gałęzi starter.

$  git clone https://github.com/googlecodelabs/android-kotlin-notifications


Możesz też pobrać repozytorium jako plik ZIP, rozpakować go i otworzyć w Android Studio.

Pobierz plik ZIP

  1. Otwórz i uruchom aplikację w Android Studio.

Zobaczysz obrazek jajka i menu z listą wstępnie zdefiniowanych przedziałów czasu gotowania jajka. Kliknij trójkąt w menu Jajko na miękko. Pierwsza opcja na liście służy do celów testowych i ustawia alarm na 10 sekund. Obok listy znajduje się przełącznik, który uruchamia minutnik. Możesz go używać do włączania i wyłączania minutnika w dowolnym momencie. Kod początkowy jest w pełni funkcjonalny, co oznacza, że możesz ustawić minutnik i obserwować, jak odlicza czas do 0. Po zakończeniu odliczania wyświetli się komunikat w formie wyskakującego okienka, jak pokazano poniżej.

  1. Sprawdź kod źródłowy. Aplikacja początkowa składa się z jednej aktywności o nazwie MainActivity. Istnieją 3 pakiety podrzędne o nazwach receiver, uiutil.

  • /receiver – receiver pakiet zawiera 2 odbiorniki transmisji o nazwach AlarmReceiverSnoozeReceiver. AlarmReceiver jest wywoływany przez AlarmManager, aby wysłać powiadomienie po upływie czasu określonego przez użytkownika. SnoozeReceiver obsługuje kliknięcie użytkownika, aby odłożyć powiadomienie.
  • /ui – zawiera EggTimerFragment, który jest częścią interfejsu aplikacji. EggTimerViewModel odpowiada za uruchamianie i anulowanie timera oraz inne zadania aplikacji związane z cyklem życia.
  • /util – w tym pakiecie znajdują się 2 pliki. BindingUtils.kt ma adaptery powiązań, które umożliwiają powiązanie danych między interfejsem aplikacji a ViewModel. NotificationUtils.kt ma metody rozszerzeń w NotificationManager.

Powiadomienia to świetny sposób na zwrócenie uwagi użytkowników na Twoją aplikację. Niezależnie od tego, czy aplikacja jest uruchomiona, czy nie, powiadomienie wyświetli wyskakujące okienko u góry ekranu i może zawierać dźwięk lub wibracje. Aby utworzyć powiadomienie, musisz użyć narzędzia do tworzenia powiadomień i podać tekst tytułu, tekst treści oraz ikonę. Gdy kreator będzie miał wszystkie niezbędne pola, NotificationManager, czyli usługa systemowa, pomoże Ci wyświetlić te treści w formie powiadomienia. NotificationManager odpowiada za wysyłanie powiadomień, aktualizowanie ich treści i anulowanie powiadomień. W kolejnych krokach dodasz do NotificationManager metody rozszerzające. Dzięki temu za każdym razem, gdy będziesz potrzebować NotificationManager, możesz użyć tych funkcji rozszerzających, aby uzyskać potrzebną funkcjonalność.

Krok 1. Utwórz podstawowe powiadomienie

W tym zadaniu utworzysz nowe powiadomienie, ustawisz wiadomość dla użytkownika i wyślesz powiadomienie.

  1. Otwórz zajęcia NotificationUtils.kt i znajdź TODO: Step 1.1. W tym ćwiczeniu i w kodzie aplikacji znajdziesz pasujące zadania do wykonania.
  2. Sprawdź podaną funkcję sendNotification(). Rozszerzasz tę funkcję rozszerzenia na NotificationManager, aby wysyłać powiadomienia.
//NotificationUtils.kt
// TODO: Step 1.1 extension function to send messages (GIVEN)
/**
 * Builds and delivers a notification.
 *
 * @param messageBody, notification text.
 * @param context, activity context.
 */
fun NotificationManager.sendNotification(messageBody: String, applicationContext: Context) {
  1. Uzyskaj instancję narzędzia do tworzenia powiadomień, przekaż kontekst aplikacji i identyfikator kanału. Identyfikator kanału to wartość ciągu znaków dla kanału.

Kanały powiadomień to sposób grupowania powiadomień. Grupując podobne typy powiadomień, deweloperzy i użytkownicy mogą zarządzać wszystkimi powiadomieniami na kanale. Po utworzeniu kanału można go używać do dostarczania dowolnej liczby powiadomień.

//NotificationUtils.kt
// TODO: Step 1.2 get an instance of NotificationCompat.Builder
val builder = NotificationCompat.Builder(
        applicationContext,
        applicationContext.getString(R.string.egg_notification_channel_id)
)
  1. Ustaw ikonę powiadomienia, która będzie reprezentować Twoją aplikację, tytuł i tekst treści wiadomości, którą chcesz przekazać użytkownikowi. W tym samouczku znajdziesz więcej opcji dostosowywania powiadomień, ale to minimalna ilość danych, które musisz ustawić, aby wysłać powiadomienie.
//NotificationUtils.kt
   // TODO: Step 1.3 set title, text and icon to builder
   .setSmallIcon(R.drawable.cooked_egg)
   .setContentTitle(applicationContext.getString(R.string.notification_title))
   .setContentText(messageBody)
  1. Następnie musisz wywołać funkcję notify() z unikalnym identyfikatorem powiadomienia i obiektem Notification z konstruktora.

Ten identyfikator reprezentuje bieżącą instancję powiadomienia i jest potrzebny do jego zaktualizowania lub anulowania. Aplikacja będzie mieć w danym momencie tylko jedno aktywne powiadomienie, więc możesz używać tego samego identyfikatora dla wszystkich powiadomień. W tym celu w pliku NotificationUtils.kt masz już stałą o nazwie NOTIFICATION_ID. Zwróć uwagę, że możesz bezpośrednio wywołać funkcję notify(), ponieważ wywołujesz ją z funkcji rozszerzenia w tej samej klasie.

//NotificationUtils.kt
   // TODO: Step 1.4 call notify to send the notification
    // Deliver the notification
    notify(NOTIFICATION_ID, builder.build())
  1. Otwórz ui/EggTimerViewModel.kt i znajdź funkcję startTimer(). Ta funkcja tworzy alarm z wybranym interwałem czasowym, gdy użytkownik włączy minutnik.
  2. W tej funkcji wywołasz powiadomienie, gdy użytkownik uruchomi stoper. Aby wywołać zaimplementowaną wcześniej funkcję sendNotification(), potrzebujesz instancji NotificationManager. NotificationManager to usługa systemowa, która udostępnia wszystkie funkcje interfejsu API powiadomień, w tym dodaną przez Ciebie funkcję rozszerzenia. Za każdym razem, gdy chcesz wysłać, anulować lub zaktualizować powiadomienie, musisz poprosić system o instancję NotificationManager. Wywołaj funkcję sendNotification()| z wiadomością powiadomienia i kontekstem.
// EggTimerViewModel.kt
// TODO: Step 1.5 get an instance of NotificationManager 
// and call sendNotification

val notificationManager = ContextCompat.getSystemService(
    app, 
    NotificationManager::class.java
) as NotificationManager
                notificationManager.sendNotification(app.getString(R.string.timer_running), app)

Już prawie gotowe. Jeśli jednak uruchomisz teraz aplikację i ustawisz minutnik, nie otrzymasz powiadomienia.

  1. Otwórz logcat i wyszukaj "No Channel found". Powinien pojawić się komunikat o błędzie informujący, że egg_channel nie istnieje. W kolejnych krokach dowiesz się więcej o kanałach powiadomień i rozwiążesz ten problem.

Krok 2. Kanały powiadomień

Począwszy od interfejsu API na poziomie 26, wszystkie powiadomienia muszą być przypisane do kanału. Jeśli naciśniesz i przytrzymasz ikonę programu uruchamiającego aplikacje, wybierzesz informacje o aplikacji i klikniesz powiadomienia, zobaczysz listę kanałów powiadomień powiązanych z aplikacją. Obecnie lista jest pusta, ponieważ aplikacja nie utworzyła jeszcze żadnych kanałów.

Kanały reprezentują „rodzaj” powiadomienia – na przykład minutnik może wysłać powiadomienie, gdy jajko będzie gotowe, a także użyć innego kanału, aby wysyłać codzienne powiadomienia przypominające o zjedzeniu jajek na śniadanie. Wszystkie powiadomienia w kanale są zgrupowane, a użytkownicy mogą konfigurować ustawienia powiadomień dla całego kanału. Dzięki temu użytkownicy mogą dostosowywać ustawienia powiadomień do rodzaju powiadomień, które ich interesują. Użytkownicy mogą na przykład wyłączyć powiadomienia o śniadaniu, ale nadal otrzymywać powiadomienia z timera.

Deweloperzy ustawiają początkowe ustawienia, ważność i zachowanie, które mają być stosowane do wszystkich powiadomień na kanale. Po skonfigurowaniu ustawień początkowych użytkownicy mogą je zastąpić.

W kroku 1.1 jako kanału powiadomień użyto egg_notification_channel_id, więc teraz musisz utworzyć i dostosować ustawienia powiadomień oraz działanie tego kanału.

  1. Otwórz EggTimerFragment.kt i znajdź funkcję createChannel().
  2. Przekaż unikalny identyfikator kanału do konstruktora NotificationChannel.
  3. Przekaż nazwę kanału powiadomień, która będzie widoczna dla użytkowników na ekranie Ustawienia.
  4. Jako ostatni parametr przekaż poziom ważności kanału powiadomień. Poziomy ważności omówimy w dalszej części tego laboratorium, więc na razie możesz użyć wartości NotificationManager.IMPORTANCE_LOW.
  5. W obiekcie notificationChannel ustaw wartość enableLights na true. To ustawienie włącza światła, gdy wyświetlane jest powiadomienie.
  6. W obiekcie notificationChannel ustaw kolor lightColor na czerwony, aby wyświetlać czerwone światło, gdy pojawi się powiadomienie.
  7. W obiekcie notificationChannel ustaw wartość enableVibration na true, aby włączyć wibracje.
  8. W obiekcie notificationChannel ustaw opis kanału na ‘Time for breakfast'.
  9. Uzyskaj instancję NotificationManager, dzwoniąc pod numer getSystemService().
  10. Wywołaj funkcję createNotificationChannel() w obiekcie NotificationManager i przekaż obiekt notificationChannel utworzony w poprzednim kroku.
//EggTimerFragment.kt
private fun createChannel(channelId: String, channelName: String) {
    // TODO: Step 1.6 START create a channel
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        val notificationChannel = NotificationChannel(
            channelId,
            channelName,
            // TODO: Step 2.4 change importance
            NotificationManager.IMPORTANCE_LOW
        )
        // TODO: Step 2.6 disable badges for this channel

        notificationChannel.enableLights(true)
        notificationChannel.lightColor = Color.RED
        notificationChannel.enableVibration(true)
        notificationChannel.description = "Time for breakfast"

        val notificationManager = requireActivity().getSystemService(
            NotificationManager::class.java
        )
        notificationManager.createNotificationChannel(notificationChannel)
    }
    // TODO: Step 1.6 END create channel
}
  1. Następnie, aby utworzyć kanał, musisz wywołać napisaną wcześniej funkcję createChannel() (krok 1.7). Ta funkcja przyjmuje 2 parametry: identyfikator kanału i nazwę kanału. Musisz wyszukać identyfikator i nazwę kanału w zasobach ciągów tekstowych, które są już dostępne w Twoim projekcie.
// EggTimerFragment.kt
    // TODO: Step 1.7 call createChannel
    createChannel(
          getString(R.string.egg_notification_channel_id),
          getString(R.string.egg_notification_channel_name)
    )
  1. Musisz przekazać identyfikator kanału do narzędzia do tworzenia powiadomień. Zostało to już zrobione w kroku 1.2. Ustawienie nieprawidłowej wartości jako identyfikatora kanału spowoduje niepowodzenie powiadomienia. Otwórz NotificationUtils.kt, aby sprawdzić, czy wcześniej ustawiony identyfikator kanału jest prawidłowy.
// NotificationUtils.kt
val builder = NotificationCompat.Builder(
        applicationContext,
       // TODO: Step 1.8 verify the notification channel name
        applicationContext.getString(R.string.egg_notification_channel_id)
)
  1. Uruchom aplikację. Zobaczysz, że za każdym razem, gdy włączysz minutnik, aplikacja wysyła powiadomienie.
  2. Pociągnij pasek stanu i sprawdź, czy tytuł, treść i ikona powiadomienia są takie, jak ustawiono w poprzednich krokach.
  3. Aby sprawdzić nowo utworzony kanał, zamknij aplikację i znajdź jej ikonę. Przytrzymaj ikonę aplikacji i wybierz Informacje o aplikacji.

  1. Na liście ustawień wybierz Powiadomienia. Pod ustawieniem Wyświetlaj powiadomienia powinien pojawić się nowy kanał o nazwie Egg.

Gdy uruchomisz aplikację, powiadomienie zostanie wyświetlone. Zarówno Ty jako deweloper aplikacji, jak i użytkownicy możecie dostosowywać ustawienia i zachowanie wszystkich powiadomień wysyłanych na tym kanale. Gratulujemy, udało Ci się utworzyć powiadomienie.

Krok 3. Dodaj powiadomienia do aplikacji

To pokazuje podstawowe użycie interfejsu Notifications API, ale wysyłanie powiadomienia zaraz po uruchomieniu timera nie ma większego sensu. Użytkownicy prawdopodobnie wolą otrzymywać powiadomienia, gdy jajko jest gotowe. W dalszej części tego kursu naprawisz ten problem i zmienisz komunikat toast na powiadomienie.

Powiadomienie zostało już wysłane i możesz obserwować, jak jest wyświetlane użytkownikom. To jednak dopiero pierwszy krok do tworzenia świetnych powiadomień. W tym kroku zmienisz godzinę wysyłania powiadomienia na bardziej odpowiednią.

Twoja aplikacja używa AlarmManager do ustawiania alarmu. Kod związany z AlarmManager jest już podany w kodzie początkowym i używany do wyświetlania wiadomości toast. AlarmManager śledzi wybrany czas i po jego upływie uruchamia funkcję onReceive() elementu AlarmReceiver.kt. Jeśli otworzysz AlarmReceiver.kt i przejdziesz do onReceive(), zobaczysz komunikat, który wyświetla się za każdym razem, gdy ustawiasz minutnik.

  1. Otwórz AlarmReceiver.kt, instancję NotificationManager, i wywołaj funkcję sendNotification() z tekstem wiadomości i parametrami kontekstu.
// AlarmReceiver.kt
   // TODO: Step 1.9 add call to sendNotification
   val notificationManager = ContextCompat.getSystemService(
       context, 
       NotificationManager::class.java
   ) as NotificationManager
             
   notificationManager.sendNotification(
       context.getText(R.string.eggs_ready).toString(), 
       context
   )
  1. Opcjonalnie możesz usunąć wyskakujące okienko, ponieważ aplikacja będzie wysyłać powiadomienie po upływie czasu.
// AlarmReceiver.kt
     // TODO: Step 1.10 [Optional] remove toast
//   Toast.makeText(
//       context, 
//       context.getText(R.string.eggs_ready),
//       Toast.LENGTH_SHORT
//   ).show()
  1. Uruchom aplikację . Powiadomienie powinno pojawiać się za każdym razem, gdy włączysz minutnik, i za każdym razem, gdy upłynie czas.

Nie jest to idealne rozwiązanie. Nie chcesz wysyłać zbyt wielu powiadomień do użytkowników. Możesz usunąć pierwsze powiadomienie, które jest wysyłane, gdy użytkownik uruchomi stoper.

  1. Otwórz EggTimerFragment.kt i usuń kod powiadomienia z kroku 1.5.
// EggTimeViewModel.kt

// TODO: Step 1.5 get an instance of NotificationManager 
// and call sendNotification
// val notificationManager = ContextCompat.getSystemService(
//      app,
//      NotificationManager::class.java
// ) as NotificationManager
// notificationManager.sendNotification(app.getString(R.string.eggs_ready), app)
  1. Ponownie uruchom aplikację.
  2. Ustaw minutnik, włącz go w tle i poczekaj, aż czas się skończy. Zobaczysz powiadomienie. To znacznie bardziej przydatne powiadomienie.

Krok 4. Dodaj intencję dotyczącą treści

  1. Uruchom ponownie aplikację, jeśli nie jest jeszcze uruchomiona.
  2. Kliknij powiadomienie. Nic się nie dzieje.

Wyświetlanie powiadomienia i informowanie użytkownika to świetna sprawa, ale gdy użytkownik kliknie powiadomienie, oczekuje, że wróci do odpowiedniej aplikacji. W tej części laboratorium dodasz do powiadomienia intencję, aby użytkownik mógł wrócić do ekranu timera.

Intent to obiekt wiadomości, którego możesz użyć, aby poprosić inny komponent aplikacji o wykonanie działania. Intencje mogą służyć do uruchamiania aktywności lub usługi albo do dostarczania transmisji. W takim przypadku używasz tego zamiaru, aby poinformować system, że po kliknięciu powiadomienia przez użytkownika ma się otworzyć aplikacja MainActivity. Twoja aplikacja składa się tylko z jednego widoku, więc nie masz tu wielu opcji. W przypadku większej aplikacji powiadomienie powinno zapewniać spójne wrażenia, przenosząc użytkownika na ekran, który ma sens w momencie interakcji z powiadomieniem.

  1. Otwórz NotificationUtils.kt i znajdź funkcję rozszerzenia sendNotification().
  2. Utwórz Intent z użyciem applicationContext i aktywności, która ma zostać uruchomiona, MainActivity::class.java.
// NotificationUtils.kt

fun NotificationManager.sendNotification(messageBody: String, applicationContext: Context) {
    // Create the content intent for the notification, which launches
    // this activity
   // TODO: Step 1.11 create intent
    val contentIntent = Intent(applicationContext, MainActivity::class.java)

Został utworzony zamiar, ale powiadomienie jest wyświetlane poza aplikacją. Aby zamiar działał poza aplikacją, musisz utworzyć nowy PendingIntent.

PendingIntent przyznaje innej aplikacji lub systemowi uprawnienia do wykonywania operacji w imieniu Twojej aplikacji. PendingIntent to po prostu odwołanie do tokena utrzymywanego przez system, który opisuje oryginalne dane użyte do jego pobrania. Oznacza to, że nawet jeśli proces aplikacji, do której należy PendingIntent, zostanie zakończony, będzie można go używać w innych procesach, w których został udostępniony. W takim przypadku system użyje oczekującego zamiaru, aby otworzyć aplikację w Twoim imieniu, niezależnie od tego, czy aplikacja minutnika jest uruchomiona.

  1. Utwórz PendingIntentapplicationContext, NOTIFICATION_ID, contentIntent utworzonym w poprzednim kroku i flagą PendingIntent. Flaga PendingIntent określa opcję utworzenia nowego PendingIntent lub użycia istniejącego. Musisz ustawić flagę PendingIntent.FLAG_UPDATE_CURRENT, ponieważ nie chcesz tworzyć nowego powiadomienia, jeśli istnieje już powiadomienie. W ten sposób zmodyfikujesz bieżący PendingIntent, który jest powiązany z dostarczanym przez Ciebie sygnałem o zamiarze.
// NotificationUtils.kt
   // TODO: Step 1.12 create PendingIntent
    val contentPendingIntent = PendingIntent.getActivity(
        applicationContext, 
        NOTIFICATION_ID,
        contentIntent,
        PendingIntent.FLAG_UPDATE_CURRENT
    )
  1. Przekaż PendingIntent do powiadomienia. Aby to zrobić, zadzwoń pod numer setContentIntent() na NotificationBuilder. Teraz, gdy klikniesz powiadomienie, uruchomi się PendingIntent i otworzy się MainActivity.
  2. Ustaw też wartość setAutoCancel() na true, aby po kliknięciu powiadomienia przez użytkownika zostało ono zamknięte, a użytkownik został przekierowany do aplikacji.
// NotificationUtils.kt
    // TODO: Step 1.13 set content intent
    .setContentIntent(contentPendingIntent)
    .setAutoCancel(true)
  1. Uruchom ponownie aplikację.
  2. Ustaw licznik czasu, umieść aplikację w tle i poczekaj na wyświetlenie powiadomienia.
  3. Gdy zobaczysz powiadomienie, kliknij je, przeciągając w dół pasek stanu, i zobacz, jak aplikacja przechodzi na pierwszy plan.

Krok 5. Anuluj powiadomienie

Masz działający minutnik z powiadomieniami, ale występuje drobny problem. Jeśli ustawisz minutnik, otrzymasz powiadomienie i ustawisz go ponownie, poprzednie powiadomienie pozostanie na pasku stanu, a nowy minutnik będzie działać. Może to wprowadzać zamieszanie, jeśli aplikacja działa w tle, i skutkować niedogotowaniem jajek.

Aby to naprawić, musisz wyczyścić poprzednie powiadomienie, gdy uruchamiasz nowy minutnik. Zacznij od utworzenia kolejnej funkcji rozszerzenia w pliku NotificationUtils.kt. NotificationManager ma interfejs API do anulowania wszystkich aktywnych powiadomień o nazwie cancelAll().

  1. Otwórz pokój NotificationsUtil.kt.
  2. Dodaj funkcję rozszerzenia w NotificationManager, która wywołuje cancelAll().
// NotificationUtils.kt

// TODO: Step 1.14 Cancel all notifications
/**
 * Cancels all notifications.
 *
 */
fun NotificationManager.cancelNotifications() {
    cancelAll()
}
  1. Otwórz EggTimerViewModel.kt i przejdź do funkcji startTimer().
  2. startTimer() pobierz instancję NotificationManager z systemu i wywołaj cancelNotifications().
//  EggTimerViewModel.kt
   //TODO Step 1.15 call cancel notification
    val notificationManager =
       ContextCompat.getSystemService(
            app,
            NotificationManager::class.java
        ) as NotificationManager
    notificationManager.cancelNotifications()       
  1. Uruchom aplikację i włącz timer.
  2. Po wyświetleniu powiadomienia ponownie uruchom stoper i obserwuj, jak nasza aplikacja automatycznie usuwa poprzednie powiadomienie z paska stanu.

Platforma powiadomień udostępnia deweloperom różne opcje dostosowywania, dzięki którym mogą oni ustawiać niestandardowe działania i w razie potrzeby zmieniać styl powiadomień. Z tego zadania dowiesz się, jak dostosować powiadomienia minutnika.

Krok 1. Nadaj powiadomieniu styl

Dostosowanie stylu powiadomienia do Twoich potrzeb i jego treści sprawi, że będzie się ono wyróżniać i wyglądać bardziej jak rozszerzenie aplikacji. Platforma powiadomień ma kilka wbudowanych stylów, które mogą Ci pomóc, ale zawsze możesz utworzyć własne.

NotificationCompat oferuje wbudowane style dla:

  • BigTextStyle, który może wyświetlać duży blok tekstu, np. zawartość e-maila po rozwinięciu.
  • BigPictureStyle, który wyświetla powiadomienia w dużym formacie z dużym załącznikiem w postaci obrazu.
  • InboxStyle, który zawiera tekst w formie rozmowy.
  • MediaStyle, w którym znajdują się elementy sterujące odtwarzaniem multimediów.
  • MessagingStyle, w którym wyświetlane są powiadomienia w dużym formacie zawierające wiele wiadomości od dowolnej liczby osób.

Więcej informacji o innych stylach znajdziesz w dokumentacji tworzenia rozwijanego powiadomienia. W tym kroku użyjesz NotificationCompat.BigPictureStyle, aby utworzyć rozwijane powiadomienie, które po rozwinięciu wyświetla duży obraz jajka.

  1. Otwórz NotificationUtils.kt i znajdź funkcję sendNotification().
  2. Zacznij od wczytania obrazu z resources za pomocą BitmapFactory.
// NotificationUtils.kt

// TODO: Step 2.0 add style
val eggImage = BitmapFactory.decodeResource(
     applicationContext.resources, 
     R.drawable.cooked_egg
)
  1. Utwórz nowy BigPictureStyle i ustaw obraz.
  2. Ustaw wartość bigLargeIcon() na null, aby duża ikona znikała po rozwinięciu powiadomienia.
// NotificationUtils.kt

// TODO: Step 2.0 add style
val eggImage = BitmapFactory.decodeResource(
     applicationContext.resources, 
     R.drawable.cooked_egg
)
val bigPicStyle = NotificationCompat.BigPictureStyle()
        .bigPicture(eggImage)
        .bigLargeIcon(null)
  1. Ustaw styl za pomocą setStyle() na bigPicStyle.
  2. Ustaw dużą ikonę z symbolem setLargeIcon() na eggImage, aby obraz był wyświetlany jako mniejsza ikona po zwinięciu powiadomienia.
// NotificationUtils.kt
// TODO: Step 2.1 add style to builder
.setStyle(bigPicStyle)
.setLargeIcon(eggImage)
  1. Uruchom aplikację i ustaw minutnik. Gdy powiadomienie pojawi się po raz pierwszy, będzie zwinięte w panelu powiadomień. Jeśli rozwiniesz powiadomienie, w obszarze rozwiniętego powiadomienia pojawi się duży obraz.

Krok 2. Działania powiadomień

Działania związane z powiadomieniami to kolejne dostosowanie, które możesz dodać do powiadomień. Obecnie powiadomienia przekierowują użytkowników do Twojej aplikacji, gdy klikną oni powiadomienie. Oprócz tego domyślnego działania powiadomienia możesz dodać przyciski poleceń, które wykonują zadanie związane z aplikacją z poziomu powiadomienia.

Powiadomienie może zawierać maksymalnie 3 przyciski działań, które umożliwiają użytkownikowi szybką reakcję, np. odłożenie przypomnienia lub odpowiedź na wiadomość tekstową. Te przyciski nie powinny powielać działania wykonywanego po kliknięciu powiadomienia przez użytkownika.

Aby dodać przycisk działania, przekaż PendingIntent do funkcji addAction() w narzędziu do tworzenia. Działa to podobnie jak ustawianie domyślnego działania po kliknięciu powiadomienia przez wywołanie metody setContentIntent(), z tym że zamiast uruchamiać aktywność, możesz wykonać różne inne czynności, np. uruchomić BroadcastReceiver, która wykonuje zadanie w tle, dzięki czemu działanie nie przerywa pracy już otwartej aplikacji.

W tym laboratorium masz już BoadcastReceiver o nazwie SnoozeReceiver. Aby otrzymać kliknięcie działania powiadomienia przez użytkownika, użyj SnoozeReceiver. W kolejnych krokach dodasz kod, który spowoduje odłożenie powiadomienia o zakończeniu odliczania czasu przez minutnik na 60 sekund, gdy użytkownik kliknie przycisk odkładania. Gdy klikniesz opcję odłożenia, SnoozeReceiver otrzyma intencję i utworzy nowy alarm, aby po 60 sekundach wysłać nowe powiadomienie.

  1. Otwórz pokój SnoozeReceiver.kt. Ta klasa jest podobna do klasy AlarmReceiver, której używasz. W kolejnych krokach dodasz kod, który wywoła funkcję onReceive()SnoozeReceiver. Krótko mówiąc, kod w SnoozeReceiver utworzy nowy alarm, który za minutę wyśle nowe powiadomienie. Przewiń w dół do funkcji onReceive, pobierz z systemu instancję notificationManager i wywołaj cancelAll.
// SnoozeReceiver.kt
        val notificationManager = ContextCompat.getSystemService(
            context,
            NotificationManager::class.java
        ) as NotificationManager
        notificationManager.cancelAll()
  1. Aby użyć SnoozeReceiver, otwórz NotificationUtils.kt.
  2. Utwórz nowy element Intent snoozeIntent dla elementu SnoozeReceiver tuż po stylu w funkcji sendNotification().
  3. Utwórz oczekujący zamiar, wywołując metodę getBroadcast() w obiekcie PendingIntent, która oczekuje parametrów opisanych w kolejnych krokach. Ten PendingIntent będzie używany przez system do ustawiania nowego alarmu, który po 60 sekundach od naciśnięcia przez użytkownika przycisku drzemki wyśle nowe powiadomienie.
  4. Pierwszy parametr to kontekst aplikacji, w którym PendingIntent ma rozpocząć działanie.
  5. Drugi parametr to kod żądania, czyli kod żądania dla tego oczekującego zamiaru. Jeśli chcesz zaktualizować lub anulować tę oczekującą intencję, musisz użyć tego kodu, aby uzyskać do niej dostęp.
  6. Następnie dodaj obiekt snoozeIntent, który jest intencją działania, które ma zostać uruchomione.
  7. Na koniec dodaj wartość flagi #FLAG_ONE_SHOT, ponieważ intencja będzie używana tylko raz. Szybka czynność i powiadomienie znikną po pierwszym kliknięciu, dlatego intencji można użyć tylko raz.
// NotificationUtils.kt

// TODO: Step 2.2 add snooze action
val snoozeIntent = Intent(applicationContext, SnoozeReceiver::class.java)
val snoozePendingIntent: PendingIntent = PendingIntent.getBroadcast(
    applicationContext, 
    REQUEST_CODE, 
    snoozeIntent, 
    FLAGS
)
  1. Następnie wywołaj funkcję addAction() na obiekcie notificationBuilder. Ta funkcja oczekuje ikony i tekstu opisującego działanie dla użytkownika. Musisz też dodać snoozeIntent. Ta intencja będzie używana do wywoływania odpowiedniego boadcastReceiver po kliknięciu działania.
// NotificationUtils.kt
// TODO: Step 2.3 add snooze action
.addAction(
    R.drawable.egg_icon, 
    applicationContext.getString(R.string.snooze),
    snoozePendingIntent
)
  1. Uruchom aplikację z minutnikiem, aby przetestować funkcję drzemki.
  2. Uruchom stoper i przełącz aplikację w tryb tła. Gdy czasomierz się wyłączy, rozwiń powiadomienie. Zobaczysz, że zawiera ono teraz przycisk odkładania, który odłoży czasomierz na kolejną minutę.

Krok 3. Ważność powiadomień

Ważność określa, w jakim stopniu powiadomienie powinno wizualnie i dźwiękowo przeszkadzać użytkownikowi. Powiadomienia o większym znaczeniu będą bardziej przeszkadzać użytkownikom.

Poziom ważności musisz określić w konstruktorze NotificationChannel. Dla aplikacji minutnika ustawiono pierwotnie niski poziom ważności. Możesz użyć jednego z 5 poziomów ważności w zakresie od IMPORTANCE_NONE(0) do IMPORTANCE_HIGH(4). Poziom ważności przypisany do kanału dotyczy wszystkich wiadomości z powiadomieniami, które na nim publikujesz.

Poziomy ważności kanału

Poziom ważności widoczny dla użytkownika

Ważność (Android 8.0 lub nowszy)

Priorytet (Android 7.1 i starsze)

Sygnalizacja dźwiękiem i wyświetlenie powiadomienia w formie wyskakującego okienka u góry ekranu

IMPORTANCE_HIGH

PRIORITY_HIGH / PRIORITY_MAX

Wydaje dźwięk

IMPORTANCE_DEFAULT

PRIORITY_DEFAULT

Brak dźwięku

IMPORTANCE_LOW

PRIORITY_LOW

Brak dźwięku i nie pojawia się na pasku stanu

IMPORTANCE_MIN

PRIORITY_MIN

Informacje o wybieraniu odpowiedniego poziomu priorytetu znajdziesz w sekcji „Poziomy priorytetu” w przewodniku po projektowaniu powiadomień. Przy wyborze poziomu ważności powiadomień w aplikacji należy zachować ostrożność. Ważność kanału powinna być wybierana z uwzględnieniem czasu i uwagi użytkownika. Gdy nieistotne powiadomienie jest maskowane jako pilne, może wywoływać niepotrzebny alarm i rozpraszać uwagę. Użytkownicy mają pełną kontrolę nad poziomem ważności powiadomień, więc jeśli utworzysz irytujące powiadomienie, mogą całkowicie wyłączyć Twój kanał powiadomień.

Gdy w kroku 1.6 utworzono powiadomienie, minutnik został ustawiony tak, aby wysyłać powiadomienia o niskim priorytecie, ponieważ miał nie przeszkadzać użytkownikowi. Warto jednak zwrócić uwagę użytkownika, zanim jajko się przegotuje. Aby zmienić poziom ważności powiadomienia, zacznij od ustawień kanału. Ważność kanału wpływa na poziom przerwania wszystkich powiadomień publikowanych w kanale i musi być określona w konstruktorze NotificationChannel.

  1. Aby zmienić poziom ważności kanału powiadomień aplikacji, otwórz EggTimerFragment.kt i przejdź do createChannel(). Zmień poziom ważności z IMPORTANCE_LOW na IMPORTANCE_HIGH.
// EggTimerFragment.kt
    val notificationChannel = NotificationChannel(
        channelId,
        channelName,
        // TODO: Step 2.4 change importance
        NotificationManager.IMPORTANCE_HIGH
    )

Aby obsługiwać urządzenia z Androidem 7.1 (poziom interfejsu API 25) lub starszym, musisz też wywołać setPriority() dla każdego powiadomienia, używając stałej priorytetu z klasy NotificationCompat.

  1. Otwórz NotificationUtils.kt i dodaj do obiektu kreatora powiadomień te elementy:
// NotificationUtils.kt
   .addAction(
       R.drawable.common_google_signin_btn_icon_dark,
       applicationContext.getString(R.string.snooze),
       snoozePendingIntent
    )
   // TODO: Step 2.5 set priority
    .setPriority(NotificationCompat.PRIORITY_HIGH)
  1. Przed uruchomieniem aplikacji przytrzymaj jej ikonę na urządzeniu lub emulatorze i kliknij Odinstaluj, aby wyczyścić poprzednie ustawienia kanału. Jeśli nie uda Ci się odinstalować aplikacji, ustawienia priorytetu kanału nie ulegną zmianie, co spowoduje, że po opublikowaniu powiadomienia nie nastąpi żadna zmiana w działaniu.
  2. Uruchom ponownie aplikację i włącz minutnik. Gdy powiadomienie zostanie dostarczone, u góry ekranu powinno pojawić się wyskakujące okienko, niezależnie od tego, czy aplikacja działa na pierwszym planie, czy w tle.

Krok 4. Kropki powiadomień

Kropki powiadomień to małe kropki, które pojawiają się na ikonie aplikacji w launcherze, gdy aplikacja ma aktywne powiadomienie. Użytkownicy mogą nacisnąć i przytrzymać ikonę aplikacji, aby wyświetlić powiadomienia.

Te kropki, zwane plakietkami, pojawiają się domyślnie i nie wymagają żadnych działań ze strony aplikacji. Mogą jednak wystąpić sytuacje, w których plakietki nie będą odpowiednie dla powiadomień. Możesz je wyłączyć dla poszczególnych kanałów, wywołując metodę setShowBadge(false) na obiekcie NotificationChannel. Ponieważ minutnik może mieć tylko jedno aktywne powiadomienie w danym momencie, plakietka na ikonie aplikacji nie przynosi użytkownikom wielu korzyści. W kolejnych krokach wyłączysz plakietkę i będziesz wyświetlać tylko powiadomienie o minutniku.

  1. Dodaj znak setShowBadge(false) do kodu tworzenia kanału dla minutnika, aby wyłączyć plakietki.
// EggTimerFragment.kt

    ).apply {
        // TODO: Step 2.6 disable badges for this channel
        setShowBadge(false)
    }
  1. Uruchom ponownie aplikację, włącz minutnik i obserwuj ikonę aplikacji. Na ikonie aplikacji nie powinny być widoczne żadne plakietki.

Kod rozwiązania znajduje się w gałęzi głównej pobranego kodu.

Kurs Udacity:

Dokumentacja dla deweloperów aplikacji na Androida:

Linki do innych ćwiczeń z tego kursu znajdziesz na stronie docelowej ćwiczeń z zaawansowanego Androida w Kotlinie.