Skip to main content

Remote push và Local push notifications trong iOS


Giới thiệu.

Chắc hẳn với tất cả các app điện thoại nói chung và iOS nói riêng thì push notification là một phần không thể thiếu trong quá trình phát triển app đối với các lập trình viên.
Khi sử dụng notifications, chúng ta có thể thông báo cho user những thông tin cũng như sự kiện quan trọng khi người dùng không bật app.
Push notification có 2 kiểu:
  1. Local notification tức là push thông báo ở dưới local.
  2. Push notification hay là Remote notifications là sử dụng các remote push như Firebase, SNS Amazon cloud service thông qua APNS.

Vấn đề.

Dạo gần đây khi iOS lên iOS 12.0 ứng dụng của mình gặp rất nhiều vấn đề về Push notification từ Local push đến Remote push. Tuy code của các tiền bối trước rất ngon lành rồi nhưng mình cần tìm hiểu thêm về push notification để có thể debug trong đống code push siêu nhân các tiền bối để lại.
Hiện tại ứng dụng của mình đang dính đến 1 cái Local notificaiton không hoạt động theo ý muốn và cũng đang phải nhờ cao nhân chỉ giáo =))
Một cái oái ăm là iOS 10 run rất OK lên iOS 12 chết thẳng cẳng =))

Local notification.

Việc push này được thực hiện bằng cách đặt một thời điểm push thông qua thời gian hay là địa điểm.
Ví dụ: Bạn đặt app của bạn cứ 11h45' là bật thông báo nhắc bạn đi ăn trưa chẳng hạn =))

Thực hiện local notification.

Trước khi bạn thực hiện bất cứ kiểu push notification nào thì chúng ta phải xin quyền push trước đã nhé ^^!. Điều này rất cần thiết vì dạng push là các thông báo rất nổi bật.
Nếu người dùng muốn nhận thông báo của app đó là điều rất tốt. Nhưng nếu user không muốn nhận thông báo và disable nó đi.

Xin quyền push

Rất đơn giản. Trong applicationDidFinishLaunchingWithOptionsAppDelegate.swift chúng ta thực hiện.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { (granted, err) in
        print("granted: (\(granted)")
    }
 
    return true
}
  • Nhớ import UserNotifications framework đầu file.
import UserNotifications
  • Phương thức requestAuthorization kích hoạt một alert view hỏi người dùng có muốn nhận thông báo hay là không.
  • options là một mảng các options mà thông báo sẽ sử dụng trong quá trình push. Ví dụ ở trên bao gồm 1 thông báo (alert), có âm thanh (sound), có chỉ báo huy hiệu (badge) ở biểu tượng ứng dụng.
  • Tham số thứ 2 là một block chúng sẽ trả về kết quả sau khi người dùng chọn option trong alert.
  • granted cho bạn biết rằng user có đồng ý hay từ chối nhận push.
Nếu người dùng cấp quyền, ứng dụng của bạn hiện đã sẵn sàng lên lịch và gửi thông báo.

Send Local notifications

Để send một Local notification chúng ta cần những thông số như sau.
  1. Nội dung (content)
let content = UNMutableNotificationContent()
content.title = "Đây là cái title"
content.body = "Đây là cái nội dung."
content.sound = UNNotificationSound.default()
Trên đây là cấu trúc rất đơn giản của một push chỉ gồm title, body text và âm thanh mặc định.
Tất nhiên bạn có thể thêm nhiều thông tin hơn như badge, âm thanh tuỳ chỉnh, thông tin bổ sung thêm khi user click vào thông báo....
Nói chung chúng ta custom được kha khá ở cái notification này. Hi vọng sẽ có bài thứ 2 cho các bạn.
  1. Một trigger
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false)
Đây là một instance của UNTimeIntervalNotificationTrigger cái mà bạn sẽ phải định nghĩa một thời gian mà bạn sẽ phát ra thông báo. Ví dụ ở trên là 5 giây sau khi bạn đặt hẹn phát. Repeats là bạn muốn lặp lại thông báo không mà thôi.
Ngoài ra chúng ta có thêm 2 kiểu triggers nữa là
  • UNCalendarNotificationTrigger Trigger cho phép bạn hẹn ngày, thời gian để phát thông báo.
  • UNLocationNotificationTrigger Trigger địa điểm. Khi user đến một địa điểm nào đó thì sẽ có thông báo.
  1. Một request
let request = UNNotificationRequest(identifier: "TestIdentifier", content: content, trigger: trigger)
Cuối cùng là 1 request. Bao gồm content ở (1) và trigger ở (2) và một định danh (identifier).
identifier có ý nghĩa khi mà bạn push một thông báo có cùng định danh thì thông báo trước đó sẽ bị ghi đè bởi thông báo mới.
  1. Fired
Chúng ta đã có đầy đủ cho một kế hoạch để phát thông báo.
UNUserNotificationCenter.add(request, withCompletionHandler: nil)
center.add(request, withCompletionHandler: nil)
  1. Thử nghiệm
import UIKit
import UserNotifications
 
class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        let content = UNMutableNotificationContent()
        content.title = "Title"
        content.body = "Body"
        content.sound = UNNotificationSound.default()

        let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false)
        let request = UNNotificationRequest(identifier: "TestIdentifier", content: content, trigger: trigger)
        UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
        
    }
}
Rất "y ri ờ" đúng không nào ^^!.

Hiển thị thông báo khi app ở foreground.

Như đã giới thiệu. Thông báo thường được dùng để thông báo khi người dùng không sử dụng app.
Chúng ta cần delegate implement UNUserNotificationCenterDelegate. Chúng ta thực hiện như sau:
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
    completionHandler([.alert, .sound])
}
Chỉ khi công việc xử lý hoàn thành thì hàm này mới được gọi.
Thông báo sẽ hiển thị khi app ở foreground.
Để method thực hiện chúng ta cần delegate bên trong method didFinishLaunchingWithOptions như sau:
UNUserNotificationCenter.current().delegate = self
Trước iOS 10, không thể tự động hiển thị thông báo khi ứng dụng ở nền trước. Thay vào đó, bạn phải hiển thị chúng bằng cách sử dụng triển khai tùy chỉnh.

Xử lý notifications.

Khi người dùng taps vào notifications. User kì vọng ứng dụng sẽ mở và thực hiện một số công việc nào đó.
Ứng dụng sẽ tự động mở. Nếu bạn muốn phản ứng với một thông báo, bạn phải thực hiện một số hành vi tùy chỉnh.
Đối với điều này, chúng tôi thực hiện một phương pháp khác của UNUserNotificationCenterDelegate là:
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
    if response.notification.request.identifier == "TestIdentifier" {
        print("handling notifications with the TestIdentifier Identifier")
    } 
    completionHandler() 
}
Ví dụ trên là in ra console dòng text khi người dùng taps vào thông báo.

Push notification.

Là dạng push được send bằng backend tức là bằng server.
Backend có thể là một server bạn code hoặc có thể là dịch vụ clound nào đó như firebase hay sns của amazone ...

Push notification hoạt động như thế nào?

Chúng ta sẽ có 4 bên tham gia vào việc push này.
  1. Ứng dụng của chúng ta.
  2. Điện thoại iphone.
  3. Backend server or Cloud service.
  4. APNS: Apple push notification service

Flow như sau

  • Bước 1: Ứng dụng yêu cầu đăng kí một remote thông báo.
  • Bước 2: Thiết bị yêu cầu lên APNS
  • Bước 3: Nếu đăng kí thành công, APNS gửi token cho device.
  • Bước 4: Thiết bị đưa token cho ứng dụng.
  • Bước 5: Ứng dụng thực hiện gửi token lên cho backend (hoặc một cloud service nào đó.) để backend biết chính xác là thiết bị nào yêu cầu.
  • Bước 6: Khi backend muốn gửi một thông báo đến thiết bị. Nó sẽ gửi tin nhắn và mã thông báo thiết bị tương ứng tới APNS.
  • Bước 7: APNS sẽ gửi thông báo cho device.
Điều này có vẻ khá phức tạp, nhưng đừng lo lắng. Chúng ta sẽ xem xét từng bước.

Yêu cầu cho ứng dụng của bạn để nhận thông báo


Đăng kí push notifications

Cũng giống như local push, chúng ta cũng phải xin quyền user.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    UNUserNotificationCenter.current().delegate = self
    UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { (granted, error) in
        print("granted: (\(granted)")
    }
    UIApplication.shared.registerForRemoteNotifications() //(I)
    return true
}
Để ý (I) đây chính là chúng ta đăng kí nhận Push notifications đó.
Chúng ta gọi registerForRemoteNotifications

Nhận Push Device token

Nếu bước trên đăng kí push notifications thành công. APNS sẽ gửi cho chúng ta một Device token. Để nhận token chúng ta thực hiện method của UIApplicationDelegate
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
    let token = deviceToken.map { String(format: "%02.2hhx", $0) }.joined()
    print("token: \(token)")
}

func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
    print("failed to register for remote notifications with with error: \(error)")
}
Bạn thấy không? Chúng ta có 2 func 1 func cho việc đăng kí thành công, 1 func cho việc đăng kí thất bại.

Xử lý gửi device token lên backend

  • Backend cần biết được Device token để định danh thiết bị.
  • Việc gửi thì chúng ta dựa vào từng dự án backend cụ thể thường sẽ sử dụng API để gửi lên.
Bạn cần lưu ý: Việc nhận Device token sẽ được gọi mỗi khi ứng dụng khởi chạy.
Trong hầu hết trường hợp Device token là không đổi. Vì vậy backend cần có dịch vụ để cập nhật Device token.
Để thử nghiệm chúng ta sẽ sử dụng một Backend MacOS clientPusher

Tạo push certificates

Việc bạn đang sử dụng kiểu backend nào không quan trọng - bạn luôn cần một push certificates được gọi là để backend của bạn có thể tự xác định được với APNS.
Chúng ta cũng cần push certificates này cho Backend Mac OS. Bạn có thể tạo push certificates trong website dành cho nhà phát triển của Apple.Developer Apple.
  • Bước 1: Sau khi bạn đã đăng nhập
  • Bước 2: Hãy chuyển đến phần Certificates, Identifiers & Profiles.
  • Bước 3: Sau đó chọn App IDs.
  • Bước 4: Xcode nên tạo sẵn cho bạn một App identifier .Chọn nó và nhấp vào Edit. Bây giờ bạn có thể kích hoạt Push Notifications và tạo các chứng chỉ bằng cách làm theo hướng dẫn:

Lưu ý: Có 2 kiểu certificates là Development certificateproduction certifcate
Mình nhớ không nhầm thì tài khoản apple mất phí thì mới có thể tạo được certificates :v Tài khoản free không tạo được thì phải huhu.

Gửi push notifications

Chúng ta sử dụng Pusher.

Bạn để ý payload message sẽ như này:
{"aps":{"alert":"Testing.. (30)","badge":1,"sound":"default"}}
Nó đủ cho việc test một push notifications.

Xử lý kết quả

Chúng ta xử lý tương tự như local notifications. Nhưng đặc biệt ở đây là Payload. Thông tin quan trọng mà server sẽ trả về cho ứng dung.
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
    print("handling notification")
    if let notification = response.notification.request.content.userInfo as? [String:AnyObject] {
        let message = parseRemoteNotification(notification: notification)
        print(message as Any)
    }
    completionHandler()
}
 
private func parseRemoteNotification(notification:[String:AnyObject]) -> String? {
    if let aps = notification["aps"] as? [String:AnyObject] {
        let alert = aps["alert"] as? String
        return alert
    } 
    return nil
}
Trong trường hợp trên là in ra console payload
Một ví dụ khác. Payload có thể trả về bất cứ gì để bạn có thể phân biệt được đó loại thông báo cho cái gì để xử lý cho phù hợp cũng như hiển thị cho user đúng ý của ứng dụng.
Ví dụ:
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
    print("handling notification")
        if let notification = response.notification.request.content.userInfo as? [String:AnyObject] {
            let message = parseRemoteNotification(notification: notification)
            print(message as Any)
        }
        completionHandler()
    }
 
private func parseRemoteNotification(notification:[String:AnyObject]) -> String? {
    if let identifier = notification["identifier"] as? String {
        return identifier
    }
 
    return nil
}
Ở đây chúng ta nhận dạng push bằng một trường là identifier. Từ đó chúng ta xử lý code tuỳ mục đích của ứng dụng.
Trên đây là 2 cách Push notification hi vọng sẽ giúp ích cho các bạn mới làm quen với push như mình.

Comments

Popular posts from this blog

Alamofire vs URLSession

Alamofire vs URLSession: a comparison for networking in Swift Alamofire and URLSession both help you to make network requests in Swift. The URLSession API is part of the foundation framework, whereas Alamofire needs to be added as an external dependency. Many  developers  doubt  whether it’s needed to include an extra dependency on something basic like networking in Swift. In the end, it’s perfectly doable to implement a networking layer with the great URLSession API’s which are available nowadays. This blog post is here to compare both frameworks and to find out when to add Alamofire as an external dependency. Build better iOS apps faster Looking for a great mobile CI/CD solution that has tons of iOS-specific tools, smooth code signing, and even real device testing? Learn more about Bitrise’s iOS specific solutions! This shows the real power of Alamofire as the framework makes a lot of things easier. What is Alamofire? Where URLSession...

Swift Tool Belt, Part 1: Adding a Border, Corner Radius, and Shadow to a UIView with Interface Builder

During my iOS work, I’ve assembled a set of code that I bring with me on every iOS project. I’m not talking about large frameworks or CocoaPods here. These are smaller Swift extensions or control overrides that are applicable to many projects. I think of them as my tool belt. In this post, I’ll show you an extension that will add a border, a corner radius, and a shadow to any UIView, UIButton, or UILabel and allow you to preview what it will look like in Interface Builder. Back in 2014, I wrote a blog post on Expanding User-Defined Runtime Attributes in Xcode where I added a border, corner radius, and shadow to a UIView using Interface Builder’s user-defined runtime attributes. This solution had no type checking—you had to type the property you wanted to modify by hand and often had to look up what it was called. You also had to run your project in order to see the effect of the runtime attribute. Starting with Xcode 6 , there is a new mech...

Frame vs Bounds in iOS

This article is a repost of an answer I wrote on Stack Overflow . Short description frame = a view’s location and size using the parent view’s coordinate system ( important for placing the view in the parent) bounds = a view’s location and size using its own coordinate system (important for placing the view’s content or subviews within itself) Details To help me remember frame , I think of a picture frame on a wall . The picture frame is like the border of a view. I can hang the picture anywhere I want on the wall. In the same way, I can put a view anywhere I want inside a parent view (also called a superview). The parent view is like the wall. The origin of the coordinate system in iOS is the top left. We can put our view at the origin of the superview by setting the view frame’s x-y coordinates to (0, 0), which is like hanging our picture in the very top left corner of the wall. To move it right, increase x, to move it down increase y. To help me remember bound...