RxSwift extension for Gcm which acts as an architectural approach to easily satisfy the requirements of an iOS app when dealing with push notifications.
- Remove iOS boilerplate code (not need to do the dirty configuration in the AppDelegate).
- Decouple presentation responsibilities from data responsibilities when receiving notifications.
- Deploy a targeting strategy to aim for the desired ViewController when receiving notifications.
Due to a limitation with Google/CloudMessaging it is not in cocoapods yet,
So to use this you need to add the Sources folder to your project, add this dependencies to your podfile
pod 'Google/CloudMessaging'
pod 'RxSwift', '~> 3.0'
There is, thought, one step behind which RxGcm can't do for you. You need to create a google-services.json configuration file and place it in your iOS application. (You can create and download it from here)
GcmReceiverData implementation should be responsible for updating the data models. The onNotification
method requires to return an instance of the observable
supplied as argument, after applying doOn
operator to perform the update action:
class AppGcmReceiverData: NSObject, GcmReceiverData {
func onNotification(_ oMessage: Observable<RxMessage>) -> Observable<RxMessage> {
return oMessage.doOn(onNext: { message in print(message)})
}
}
The observable
type is an instance of RxMessage, which holds a Rreference to the notification (payload) and a method called getTarget()
, which returns the key associated with this notification.
class AppGcmReceiverData: NSObject, GcmReceiverData {
func onNotification(_ oMessage: Observable<RxMessage>) -> Observable<RxMessage> {
return oMessage.do(onNext: { (message) -> Void in
let payload = message.getPayload()
let title = payload["title"]
let body = payload["body"]
if message.getTarget() == "issues" {
SimpleCache.addIssue(Notification(title, body: body))
} else if message.getTarget() == "supplies" {
SimpleCache.addSupply(Notification(title, body: body))
}
}
}
To RxGcm be able to return a not nil
value when calling getTarget()
method, you need to add the key rx_gcm_key_target to the payload of the push notification:
{
"data": {
"title":"A title 4",
"body":"A body 4",
"rx_gcm_key_target":"supplies"
},
"to":"token_device"
}
}
If rx_gcm_key_target is not added to the json payload, you will get a nil
value when calling getTargeth()
method. So, you can ignore this, but you would be missing the benefits of the targeting strategy.
Both of them will be called only after GcmReceiverData
observable
has reached onCompleted()
state. This way it’s safe to assume that any operation related to updating the data model has been successfully achieved, and now it’s time to reflect these updates in the presentation layer.
GcmReceiverUIBackground
implementation will be called when a notification is received and the application is in the background. Probably the implementation class will be responsable for building and showing system notifications.
class AppGcmReceiverUIBackground: NSObject, GcmReceiverUIBackground {
func onNotification(_ oMessage: Observable<RxMessage>) {
oMessage.subscribe(onNext: { message in buildAndShowNotification(message) })
}
}
GcmReceiverUIForeground
implementation will be called when a notification is received and the application is in the foreground. The implementation class must inherits from an UIViewController
. GcmReceiverUIForeground
exposes a method called target()
, which forces to the implementation class to return a string.
RxGcm internally compares this string to the value of the rx_gcm_key_target node payload notification. If the current UIViewController
target()
method value matches with the one of rx_gcm_key_target node, onTargetNotification()
method will be called, otherwise onMismatchTargetNotification()
method will be called.
class IssuesViewController: UIViewController, GcmReceiverUIForeground {
func onTargetNotification(_ oRxMessage: Observable<RxMessage>) {
oMessage.subscribe(onNext: { (message) -> Void in
// Update views
})
}
func onMismatchTargetNotification(_ oRxMessage: Observable<RxMessage>) {
oMessage.subscribe(onNext: { (message) -> Void in
self.showAlert(message)
})
}
func matchesTarget(_ key: String) -> Bool {
return "issues" == key
}
}
class SuppliesViewController: UIViewController, GcmReceiverUIForeground {
func onTargetNotification(_ oRxMessage: Observable<RxMessage>) {
oMessage.subscribe(onNext: { (message) -> Void in
// Update views
})
}
func onMismatchTargetNotification(_ oRxMessage: Observable<RxMessage>) {
oMessage.subscribe(onNext: { (message) -> Void in
self.showAlert(message)
})
}
func matchesTarget(_ key: String) -> Bool {
return "suplies" == key
}
}
GcmRefreshTokenReceiver implementation will be called when the token has been updated. As the documentation points out, the token device may need to be refreshed for some particular reason.
class AppGcmRefreshTokenReceiver: NSObject, GcmRefreshTokenReceiver {
func onTokenReceive(_ oTokenUpdate: Observable<TokenUpdate>) {
oTokenUpdate.subscribe(onNext: { tokenUpdate in }
, onError: { error in })
}
}
If at some point you need to retrieve the gcm token device -e.g for updating the value on your server, you could do it easily calling RxGcm.Notifications.currentToken:
RxGcm.Notifications.currentToken().subscribe(
onNext: { tokenUpdate in }
, onError: { error in }
)
Once you have implemented GcmReceiverData
and GcmReceiverUIBackground
interfaces is time to register them in your iOS AppDelegate
class calling RxGcm.Notifications.register. Plus, register RefreshTokenReceiver
implementation too at this point.
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions:[UIApplicationLaunchOptionsKey:Any]?) -> Bool {
// Override point for customization after application launch.
RxGcm.Notifications.register(AppGcmReceiverData.self, gcmReceiverUIBackgroundClass: AppGcmReceiverUIBackground.self)
.subscribe(
onNext: { token in print(token) },
onError: { error in print((error as NSError).domain) }
)
RxGcm.Notifications.onRefreshToken(AppGcmRefreshTokenReceiver.self)
return true
}
}
Note: If your UIApplicationDelegate
is not called AppDelegate you should implement the initialize method and set the appDelegate
as self
@UIApplicationMain
class CustomAppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
override internal class func initialize() {
appDelegate = self
}
...
Important: The observable
returned by RxGcm.Notifications.register()
method will not emit the token twice. It means that it will be emit the token only the first time
RxGgm asks to Google for a token. But if the token is already cached, the observable will complete without emitting the item.
RxGcm uses internally RxSwift. Thanks to this, each observable created by RxGcm observes on the Main Thread and subscribe on an Serial thread. This means you do not need to worry about threading and sync. But if you need to change this behaviour, you can do it easily setting in which scheduler the observable needs to observe and subscribe.
There is a complete example of RxGcm in the app module. Plus, it has an integration test
You can easily send http post request to Google Cloud Messaging server using Postman or Advanced Rest Client. Or you can send directly push notifications using this page.
Roberto Frontado
- https://linkedin.com/in/robertofrontado
- https://github.com/robertofrontado
- https://twitter.com/robertofrontado
- BaseApp-iOS: Base skeleton structure to start every new iOS project
- OkDataSources: Wrappers for iOS TableView and CollectionView DataSources to simplify its api at a minimum. Also it has a cool PagerView and SlidingTabs!
- RxSocialConnect-iOS: OAuth RxSwift extension for iOS.