+6

Awesome Notifications - Bài 2: Tùy biến giao diện trên thanh thông báo.

Tiếp tục bài viết tiếp theo trong series Awesome Notifications. Hôm nay mình sẽ mang đến cho các bạn cách tùy biến giao diện trên thanh thông báo sao cho bắt mắt nhé!

Note: Vì mình chưa chuẩn bị kịp tài khoản cá nhân Apple Developer Account (để push notification) nên series này mình test trên thiết bị Android nha. 🥲

1. Tại sao cần tùy biến giao diện Notification?

Thật ra việc tùy biến UI của Notification tùy thuộc vào yêu cầu, mục đích của project. Ở bài trước mình đã biết về khái niệm Rich Notification. Gửi image là ví dụ dễ thấy nhất của Rich Notification. Ngoài ra Rich Notification còn có nhiều tùy biến khác như: các nút CTA (Call to action), thanh text field,.. Những kiểu tùy biến này các bạn có thể dễ dàng nhận thấy trên các ứng dụng như: Messenger, Spotify, ZingMP3,..

2. Emojis

Thêm các emojis vào thanh thông báo cho bớt nhàm chán 🤩.

  • Đối với Local Notification: dùng class Emoji đã hỗ trợ sẵn để concat vào text như ví dụ sau:
    {
        title: 'Emojis are awesome too! '+ Emojis.smille_face_with_tongue + Emojis.smille_rolling_on_the_floor_laughing + Emojis.emotion_red_heart,
    }
    

hoặc dùng unicode text với format sau \u{1f6f8}

  • Đối với Push notification:

    • Copy thẳng icon từ link này rồi paste vào postman như sau:
    "title": "Android! The eagle has landed! 😀",
    
    • Dùng mã Dec/Hex từ link này, ví dụ: &#129319
    "title": "Android! The eagle has landed! &#129319",
    

3. Notification Layout Types

Dùng để quy định layout nào bạn muốn dùng cho từng mục đích cụ thể, gồm các loại sau:

  • Default: Mặc định.
  • BigPicture: hiển thị ảnh lớn và/hoặc ảnh nhỏ đính kèm với thông báo.
  • BigText: hiển thị hơn 2 dòng văn bản.
  • Inbox: liệt kê các thư hoặc mục được phân tách với nhau bằng dòng.
  • ProgressBar: hiển thị thanh tiến trình, ví dụ thanh tiến trình khi bạn download hình ảnh cho app chẳng hạn (thường thấy trong telegram,..).
  • Messaging: hiển thị từng thông báo dưới dạng cuộc trò chuyện trò chuyện 1 vs 1.
  • Messaging Group: hiển thị mỗi thông báo dưới dạng một cuộc trò chuyện nhóm.
  • MediaPlayer: hiển thị bộ điều khiển phương tiện có các nút tác vụ, cho phép user thao tác mà không đưa ứng dụng lên foreground. (thường thấy trên Spotify, ZingMp3, Youtube Music..)

Mình sẽ thử 1 vài ví dụ cho các bạn dễ hình dung nhé. ✌️

Gọi các sự kiện ở màn hình Home (file main.dart):


  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceAround,
          children: [
            ElevatedButton(
              onPressed: () {
                notifController.localNotification();
              },
              child: const Icon(Icons.circle_notifications),
            ),
            ElevatedButton(
              onPressed: () {
                notifController.localDownload();
              },
              child: const Icon(Icons.download),
            ),
            ElevatedButton(
              onPressed: () {
                notifController.localMediaNotif();
              },
              child: const Icon(Icons.music_video),
            ),
          ],
        ),
      ),
    );
  }

BigPicture

Giống như ví dụ đã demo ở Bài 1 mình sẽ config thêm 1 nút reply để reply tin nhắn nhận về. Ví dụ được sử dụng trong các app như Messenger, Zalo, Telegram,..

Future<void> localNotification() async {
    await AwesomeNotifications().createNotification(
        content: NotificationContent(
          id: 1,
          channelKey: 'alerts',
					// Demo cái Emojis nè
          title: 'Emojis are awesome too! ' +
              Emojis.activites_admission_tickets +
              Emojis.activites_balloon +
              Emojis.emotion_red_heart,
          body: "Emojis awesome body",
					// Gắn hình vào thông báo ở đây nè
          bigPicture:
              'https://images.pexels.com/photos/14679216/pexels-photo-14679216.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2',
          notificationLayout: NotificationLayout.BigPicture,
        ),
        actionButtons: [
					// Thêm nút NotificationActionButton để reply tin nhắn trên thanh thông báo 
          NotificationActionButton(
            key: 'REPLY',
            label: 'Reply Message',
            requireInputText: true,
            actionType: ActionType.SilentAction,
          ),
					// Nút này để Dismiss cái notification thôi :v
					NotificationActionButton(
            key: 'DISMISS',
            label: 'Dismiss',
            autoDismissible: true,
          ),
        ]);
  }

ProgressBar

Thêm 1 ví dụ về NotificationLayoutProgressBar. Thường dùng để hiển thị tiến trình download hình ảnh, hay video (có thể dễ dàng bắt gặp ở Telegram,..).

Future<void> localDownload() async {
    await AwesomeNotifications().createNotification(
      content: NotificationContent(
        id: 1,
        channelKey: 'alerts',
        title: 'Downloading...',
        bigPicture:
            'https://images.pexels.com/photos/14679216/pexels-photo-14679216.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2',
        // Đổi cái notificationLayout thành ProgressBar để sử dụng nhé
				notificationLayout: NotificationLayout.ProgressBar,
				// Có thể dùng process để hiện tốc độ tải
				progress: 70, // thay thế giá trị cần thiết ở đây nha
      ),
    );
  }

MediaPlayer

Hiển thị thanh trạng thái, điều khiển cho ứng dụng nghe nhạc, xem phim thử xem nào 🥰

Future<void> localMediaNotif() async {
    await AwesomeNotifications().createNotification(
        content: NotificationContent(
          id: 1,
          channelKey: 'alerts',
          title: 'Đã sai từ lúc đầu',
          body: "Bùi Anh Tuấn ft Trung Quân",
					// Dùng MediaPlayer cho notificationLayout
          notificationLayout: NotificationLayout.MediaPlayer,
          summary: 'Now playing',
          largeIcon:
              'https://avatar-ex-swe.nixcdn.com/song/2022/08/16/f/3/3/4/1660622960262_640.jpg',
        ),
        actionButtons: [
					// Thêm các nút để điều khiển trình nghe nhạc như prev, pause, next
          NotificationActionButton(
            key: 'MEDIA_PREV',
						// thêm icon cho các nút ở đây
            icon: 'resource://drawable/previous',
            label: 'Previous',
            showInCompactView: false,
            enabled: true,
            actionType: ActionType.SilentAction,
          ),
          NotificationActionButton(
            key: 'MEDIA_PAUSE',
            icon: 'resource://drawable/pause',
            label: 'Pause',
            showInCompactView: false,
            enabled: true,
            actionType: ActionType.SilentAction,
          ),
          NotificationActionButton(
            key: 'MEDIA_NEXT',
            icon: 'resource://drawable/next',
            label: 'Next',
            showInCompactView: false,
            enabled: true,
            actionType: ActionType.SilentAction,
          ),
        ]);
  }

Lưu ý: icon trên Android được lưu ở đường dẫn “[project]/android/app/src/main/drawable”. Ví dụ: resource://drawable/res_image-asset.png

4. Handle sự kiện nhận được khi nhấn các button trên thanh thông báo.

Ở file main.dart thêm function startListeningNotificationEvents để lắng nghe các sự kiện trả về:

class _MyAppState extends State<MyApp> {
  final notifController = NotificationController();

  // This widget is the root of your application.
  
  void initState() {
    notifController.checkPermission();
    notifController.requestFirebaseToken();
		// Gọi function startListeningNotificationEvents
    notifController.startListeningNotificationEvents();
    super.initState();
  }

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
      builder: EasyLoading.init(),
    );
  }
}

Ở file notification/notification_controller.dart: thêm function để lắng nghe các sự kiện trả về

Future<void> startListeningNotificationEvents() async {
    AwesomeNotifications().setListeners(
      onActionReceivedMethod: onActionReceivedMethod,
    );
 }

('vm:entry-point')
  static Future<void> onActionReceivedMethod(
      ReceivedAction receivedAction) async {
    if (receivedAction.actionType == ActionType.SilentAction ||
        receivedAction.actionType == ActionType.SilentBackgroundAction) {
			// Lấy tin nhắn đã nhập vào ô input khi nhấn nút reply
			debugPrint('---Input message---${receivedAction.buttonKeyInput}------');
			
			// Handle các sự kiện media player
      switch (receivedAction.buttonKeyPressed) {
        case 'MEDIA_PREV':
          // Handle media prev
          debugPrint("-----------MEDIA_PREV-----------");
          break;
        case 'MEDIA_PAUSE':
          // Handle media pause
          debugPrint("-----------MEDIA_PAUSE-----------");
          break;
        case 'MEDIA_NEXT':
          // Handle media next
          debugPrint("-----------MEDIA_NEXT-----------");
          break;
      }
    } else {
      EasyLoading.showToast("FCM message ne!",
          toastPosition: EasyLoadingToastPosition.bottom);
    }
  }

5. Với Push notification thì như thế nào nhỉ?

Những ví dụ trên là cho trường hợp Local Notification, Vậy, với push notification thì chúng ta có thể tham khảo format chuẩn sau:

{
    "to" : "Paste FCM token vào đây nha!",
    "priority": "high",
    "content_available": true,
    "notification": {
        "badge": 42,
        "title": "Huston! The eagle has landed!",
        "body": "Bacon ipsum dolor amet bacon prosciutto chislic pork belly bresaola pig jerky"
    },
    "data" : {
        "content": {
            "id": 1,
            "badge": 42,
            "channelKey": "alerts",
            "displayOnForeground": true,
            "notificationLayout": "BigPicture",
            "largeIcon": "https://images.pexels.com/photos/3921014/pexels-photo-3921014.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2",
            "bigPicture": "https://images.pexels.com/photos/14679216/pexels-photo-14679216.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2",
            "showWhen": true,
            "autoDismissible": true,
            "privacy": "Private",
            "payload": {
                "secret": "Awesome Notifications Rocks!"
            }
        },
        "actionButtons": [
            {
                "key": "REPLY",
                "label": "Reply",
                "requireInputText": true
            },
            {
                "key": "DISMISS",
                "label": "Dismiss",
                "actionType": "DismissAction",
                "isDangerousOption": true,
                "autoDismissible": true
            }
        ],
        "Android": {
            "content": {
                "title": "Android! Notification title &#129319", // Gắn cái emojis vào nè!
                "payload": {
                    "android": "android custom content!"
                }
            }
        },
        "iOS": {
            "content": {
                "title": "Jobs! The eagle has landed!",
                "payload": {
                    "ios": "ios custom content!"
                }
            },
            "actionButtons": [
                {
                    "key": "REDIRECT",
                    "label": "Redirect message",
                    "autoDismissible": true
                },
                {
                    "key": "DISMISS",
                    "label": "Dismiss message",
                    "actionType": "DismissAction",
                    "isDangerousOption": true,
                    "autoDismissible": true
                }
            ]
        }
    }
}

cURL trên postman cho bạn nào cần

curl --location 'https://fcm.googleapis.com/fcm/send' \
--header 'Content-Type: application/json' \
--header 'Authorization: key=AAAAgCbszqQ:APA91bEroOcx2-hdno1n7QH4elwA_S4WpcBX8nsqJPtvigWUk824wGyEovL9mpE3EynZtrAYdDdAKesvE1KoFScmF97eeRsshVMCbFHEwc296cquuOhPDbThN9ByLuBttIyTC_XzqHKA' \
--data '{
    "to" : "dzia1myTSLKf1TsRKq57EE:APA91bEAi-ihDZzvSCK3LYNg2eVTNM94T4_BeVSIhL3aLvIin0SkZCMn_3WHGyETk-revWUMnD8X6xxWrIpZFgjF9ZJAaAFDjb9TDYAZHPPLxdFi3RNtWjDZitBHc9FYCufn28DiPN63",
    "priority": "high",
    "content_available": true,
    "notification": {
        "badge": 42,
        "title": "Huston! The eagle has landed!",
        "body": "Bacon ipsum dolor amet bacon prosciutto chislic pork belly bresaola pig jerky"
    },
    "data" : {
        "content": {
            "id": 1,
            "badge": 42,
            "channelKey": "alerts",
            "displayOnForeground": true,
            "notificationLayout": "BigPicture",
            "largeIcon": "https://images.pexels.com/photos/3921014/pexels-photo-3921014.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2",
            "bigPicture": "https://images.pexels.com/photos/14679216/pexels-photo-14679216.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2",
            "showWhen": true,
            "autoDismissible": true,
            "privacy": "Private",
            "payload": {
                "secret": "Awesome Notifications Rocks!"
            }
        },
        "actionButtons": [
            {
                "key": "REPLY",
                "label": "Reply",
                "requireInputText": true
            },
            {
                "key": "DISMISS",
                "label": "Dismiss",
                "actionType": "DismissAction",
                "isDangerousOption": true,
                "autoDismissible": true
            }
        ],
        "Android": {
            "content": {
                "title": "Android! Notification title &#129319",
                "payload": {
                    
                    "android": "android custom content!"
                }
            }
        },
        "iOS": {
            "content": {
                "title": "Jobs! The eagle has landed!",
                "payload": {
                    "ios": "ios custom content!"
                }
            },
            "actionButtons": [
                {
                    "key": "REDIRECT",
                    "label": "Redirect message",
                    "autoDismissible": true
                },
                {
                    "key": "DISMISS",
                    "label": "Dismiss message",
                    "actionType": "DismissAction",
                    "isDangerousOption": true,
                    "autoDismissible": true
                }
            ]
        }
    }
}'

6. Tổng kết

Bài vừa rồi, chúng ta đã tìm hiểu được những tiện ích mà Awesome Notifications mang lại như có thể tùy biết layout theo những nhu cầu, ứng dụng khác nhau. Ở bài viết tiếp theo mình sẽ tìm hiểu sâu hơn về cách hoạt động của push notification. Cùng đón đọc những bài viết tiếp theo trong series này nhé! 🤩

7. Tài liệu tham khảo

https://pub.dev/packages/awesome_notifications_fcm/

https://pub.dev/packages/awesome_notifications

8. Source code

https://github.com/AnhQuangCee/flutter-research/tree/awesome-notif/lesson-2


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí