Rx_Swift for Local and Push Notifications - ios

How can I implement observable for Local notification and Push notifications when they are received.
In app delegate, We are notify on
didReceiveLocalNotification
and
didReceiveRemoteNotification
How can I listen these notification on other screen? I've used NotificationCenter for notify but now I want to use RX-Swift. I've tried with this way but not working.
extension UILocalNotification {
var notificationSignal : Observable<UILocalNotification> {
return Observable.create { observer in
observer.on(.Next(self))
observer.on(.Completed)
return NopDisposable.instance
}
}
}
Can anyone help me?
Updated:
Hi, I found a solution for that using same way as you are used but some changes.
class NotificationClass {
static let bus = PublishSubject<AnyObject>()
static func send(object : AnyObject) {
bus.onNext(object)
}
static func toObservable() -> Observable<AnyObject> {
return bus
}
}
Send notification from AppDelegate:
func application(application: UIApplication, didReceiveLocalNotification notification: UILocalNotification) {
NotificationClass.send(notification)
}
Then observe on any other class.
NotificationClass.bus.asObserver()
.subscribeNext { notification in
if let notification : UILocalNotification = (notification as! UILocalNotification) {
print(notification.alertBody)
}
}
.addDisposableTo(disposeBag)
Best thing of this class is we can emit and consume anyObject through it.

How about something like this?
// this would really be UILocalNotification
struct Notif {
let message: String
}
class MyAppDelegate {
let localNotification = PublishSubject<Notif>()
// this would really be `application(application: UIApplication, didReceiveLocalNotification notification: UILocalNotification)`
func didReceiveLocalNotification(notif: Notif) {
localNotification.on(.Next(notif))
}
}
let appDelegate = MyAppDelegate() // this singleton would normally come from `UIApplication.sharedApplication().delegate`
class SomeClassA {
let disposeBag = DisposeBag()
init() {
appDelegate.localNotification
.subscribe { notif in
print(notif)
}
.addDisposableTo(disposeBag)
}
}
let a = SomeClassA()
appDelegate.didReceiveLocalNotification(Notif(message: "notif 1"))
let b = SomeClassA()
appDelegate.didReceiveLocalNotification(Notif(message: "notif 2"))
I'm still learning RxSwift, so this might not be the best way to do it. However, it works.

Related

Add delegate to custom iOS Flutter Plugin

I'm working on integrating a custom iOS plugin into my Flutter app, problem is that I'm not getting delegate callbacks from the custom SDK Protocol.
I have to connect a bluetooth device to my app and I from the delegate calls I should receive the device's ID and pair it.
From the Flutter side, I can call the native functions from the customSdk: sdkInstance.scan() and there are even some internal (inside the sdk) prints with the scan results but my delegate calls are not in place.
I think I'm not correctly adding the delegate to the SDK, I can get this to work in a swift native app but not as a Flutter Plugin.
So here's more or less the code:
iOS Code
AppDelegate.swift
import UIKit
import Flutter
#UIApplicationMain
#objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
SwiftIosPlugin.swift
import Flutter
import UIKit
import CustomSDK
public class SwiftIosPlugin: NSObject, FlutterPlugin {
let sdkInstance = CustomSDK.shared // This returns an instance of the SDK
let channel: FlutterMethodChannel
public static func register(with registrar: FlutterPluginRegistrar)
let channel = FlutterMethodChannel(name: "ios_plugin_channel", binaryMessenger: registrar.messenger())
let instance = SwiftIosPlugin(channel)
registrar.addMethodCallDelegate(instance, channel: channel)
registrar.addApplicationDelegate(instance)
}
init (_ channel: FlutterMethodChannel) {
self.channel = channel
super.init()
// In Swift, this is done in viewDidLoad()
// Is this the correct place to do this?
sdkInstance.addDelegate(self)
}
public func handle(_ call: FlutterMethodCall, result: #escaping FlutterResult) {
switch call.method {
case "startScan":
do {
// This is being called and results printed
try sdkInstance.scan()
} catch {
result(FlutterError(code: "400", message: "\(error)", details: nil))
}
case "connect":
sdkInstance.connect(call, result)
default:
result(FlutterMethodNotImplemented)
}
}
}
// These should be called but are not
extension SwiftIosPlugin: CustomSDKDelegate {
// Isn't called when scan() is executeed!
public func onScanDevice(didScan id:String) {
// do logic
}
public func onPairedDevice(didPair id:String) {
// do logic
}
}
Update:
Silly thing that I hope nobody else has this trouble...
Two things to consider:
The problem was some of the delegate's functions public func onScanDevice(didScan id:String) was missing a parameter (even though there weren't any errors pointed out by Xcode).
sdkInstance.addDelegate(self) was called too early in the class "lifecycle".
Be mindful of these things and you won't have any trouble!

Benefits and Disadvantages for subclassing NotificationCenter

I am quite new to Swift. In all the examples of NotificationCenter I have seen, the default NotificationCenter.default is used. However I have tested it is possible to subclass NotificationCenter and use custom objects to post and listen notification.
import Foundation
struct Notice {
let num: Int
let str: String
}
class TestObj: NotificationCenter {
private var number = 0
override init() {
number = 5
}
func postNotification(_ num: Int) {
post(name: Notification.Name(rawValue: "TestObjNotification"), object: Notice(num: num, str: "No is \(num)"))
}
}
class Watcher: NSObject {
var obj = TestObj()
func addWatchers() {
obj.addObserver(self, selector: #selector(watched(noti:)), name: Notification.Name(rawValue: "TestObjNotification"), object: nil)
}
func watch(num: Int) {
obj.postNotification(num)
}
#objc func watched(noti: NSNotification) {
print(noti.name.rawValue)
print(noti.object!)
guard let noticeObj = noti.object as? Notice else {
print("Not working")
return
}
print(noticeObj.num)
print(noticeObj.str)
}
}
let watcherObj = Watcher()
watcherObj.addWatchers()
watcherObj.watch(num: 500)
I would prefer this approach as this would ensure grouping notifications to specific types rather than maintaining notifications for app wide. Also it is possible to implement ObservableObject functionality with these custom types for iOS 12 and before. My concern is that:
Is there any performance penalty for this?
What happens when custom NotificationCenter is deallocated? Does all the listeners need to stop listening? What happens when all the listeners register for the same notifications. Since NotificationCenter.default is read-only there isn't any issue regarding this.
In general, you should prefer composition over inheritence. I would not recommend subclassing here. A TestObj isn't a notification center. You could configure it so that TestObj has a notification center, if you like:
class TestObj {
var notificationCenter: NotificationCenter
private var number = 5
override init(notificationCenter: NotificationCenter) {
self.notificationCenter = notificationCenter
}
func postNotification(_ num: Int) {
notificationCenter.post(name: Notification.Name(rawValue: "TestObjNotification"), object: Notice(num: num, str: "No is \(num)"))
}
}
Now you are free to worry about the lifetime of any passed-in notification center beyond the lifetime of TestObj.

swift ios - How to run function in ViewController from AppDelegate

I am trying to run a function in certain ViewController using AppDelegate
func applicationDidBecomeActive(_ application: UIApplication) {
ViewController().grabData()
}
But somehow the function does not seem to run at all when the app has become active after entering the app from the background.
The function looks like this
func grabData() {
self._DATASERVICE_GET_STATS(completion: { (int) -> () in
if int == 0 {
print("Nothing")
} else {
print(int)
for (_, data) in self.userDataArray.enumerated() {
let number = Double(data["wage"]!)
let x = number!/3600
let z = Double(x * Double(int))
self.money += z
let y = Double(round(1000*self.money)/1000)
self.checkInButtonLabel.text = "\(y) KR"
}
self.startCounting()
self.workingStatus = 1
}
})
}
And uses this var
var money: Double = 0.000
What have I missed?
Thanks!
ViewController().grabData() will create a new instance of the ViewController and call this function. Then.. as the view controller is not in use it will be garbage collected/removed from memory. You need to be calling this method on the actual view controller that is in use. Not a new instance of it.
The best option would be to listen for the UIApplicationDidBecomeActive notification that iOS provides.
NotificationCenter.default.addObserver(
self,
selector: #selector(grabData),
name: NSNotification.Name.UIApplicationDidBecomeActive,
object: nil)
make sure that you also remove the observer, this is usually done in a deinit method
deinit() {
NotificationCenter.default.removeObserver(self)
}
I simply solved it like this:
func applicationDidBecomeActive(_ application: UIApplication) {
let viewController = self.window?.rootViewController as! ViewController
viewController.grabData()
}

Where best to call updateApplicationContext using Watch Connectivity?

Several of the good blog posts detailing Watch Connectivity (http://www.kristinathai.com/watchos-2-tutorial-using-application-context-to-transfer-data-watch-connectivity-2/ and http://natashatherobot.com/watchconnectivity-application-context/) use simple app examples that send data to the watch when you tap on UI on the iPhone.
My app simply lists the data from my iPhone app, so I don't need to send data immediately, I just wanted to send it when the app loads or enters background...to this end I've made the updateApplicationContext in didFinishLaunching and didEnterBackground...however my dataSource delegate in my watch interface controllers are very spotting at getting triggered...particularly the glance only loads on the simulator and never on device. Is there a better time and place to push the info?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
WatchSessionManager.sharedManager.startSession()
do {
try WatchSessionManager.sharedManager.updateApplicationContext(["peopleDict" : peopleDict])
} catch {
print(error)
}
return true
}
func applicationDidEnterBackground(application: UIApplication) {
do {
try WatchSessionManager.sharedManager.updateApplicationContext(["peopleDict" : peopleDict])
} catch {
print(error)
}
}
below is my WatchSessionManager I used to call activiateSession in my extensionDelegate's appliciationDidFinishLaunching
import WatchConnectivity
protocol DataSourceChangedDelegate {
func dataSourceDidUpdate(dataSource: DataSource)
}
class WatchSessionManager: NSObject, WCSessionDelegate {
static let sharedManager = WatchSessionManager()
private override init() {
super.init()
}
private var dataSourceChangedDelegates = [DataSourceChangedDelegate]()
private let session: WCSession = WCSession.defaultSession()
func startSession() {
session.delegate = self
session.activateSession()
}
func addDataSourceChangedDelegate<T where T: DataSourceChangedDelegate, T: Equatable>(delegate: T) {
dataSourceChangedDelegates.append(delegate)
}
func removeDataSourceChangedDelegate<T where T: DataSourceChangedDelegate, T: Equatable>(delegate: T) {
for (index, indexDelegate) in dataSourceChangedDelegates.enumerate() {
if let indexDelegate = indexDelegate as? T where indexDelegate == delegate {
dataSourceChangedDelegates.removeAtIndex(index)
break
}
}
}
}
// MARK: Application Context
// use when your app needs only the latest information
// if the data was not sent, it will be replaced
extension WatchSessionManager {
// Receiver
func session(session: WCSession, didReceiveApplicationContext applicationContext: [String : AnyObject]) {
dispatch_async(dispatch_get_main_queue()) { [weak self] in
self?.dataSourceChangedDelegates.forEach { $0.dataSourceDidUpdate(DataSource(data: applicationContext))}
}
}
}
As updateApplicationContext only stores the newest application context you can update it whenever you like. The watch will only get the newest data. There is no queue with old contexts.
On the watch side the most secure location to activate the session and set the WCSessionDelegate is in the ExtensionDelegate init method:
class ExtensionDelegate: NSObject, WKExtensionDelegate {
override init() {
super.init()
WatchSessionManager.sharedManager.startSession()
}
...
}
Your Glance does not update because when the Glance is shown, applicationDidFinishLaunching is not being called (because the watch app is not launched when only the Glance is launched)

RxSwift subscribe block not called

I'm playing around with RxSwift and I'm stuck with a simple toy programm. My program essentially contains a model class and a viewcontroller. The model contains an observable that gets updated on the main queue after an asynchronous network call, the viewcontroller subscribes in viewDidLoad(). The AppDelegate initializes the model and passes it to ViewController and triggers the network request.
class GalleryModel {
var galleryCount: BehaviorSubject<Int>
init() {
galleryCount = BehaviorSubject.init(value:0)
}
func refresh() {
doAsyncRequestToAmazonWithCompletion { (response) -> AnyObject! in
var counter = 0
//process response
counter = 12
dispatch_async(dispatch_get_main_queue()) {
self.galleryCount.on(.Next(counter))
}
return nil
}
}
class ViewController: UIViewController {
#IBOutlet weak var label: UILabel!
var galleryModel: GalleryModel?
override func viewDidLoad() {
super.viewDidLoad()
galleryModel?.galleryCount.subscribe { e in
if let gc = e.element {
self.label.text = String(gc)
}
}
}
}
class AppDelegate: UIResponder, UIApplicationDelegate {
var galleryModel: GalleryModel?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
//do amazon setup
galleryModel = GalleryModel()
if let viewController = window?.rootViewController as? ViewController {
viewController.galleryModel = GalleryModel()
}
return true
}
func applicationDidBecomeActive(application: UIApplication) {
galleryModel?.refresh()
}
The label gets updated only one, it shows "0". I expected the label to get updated twice, showing "0" after the first update and showing "12" after the second update after the processing of the network request. A breakpoint in the dispatch_async block gets hit, but it seems that galleryCount lost its observer. Anybody any idea what's happening or how to debug this?
Best
In case reads this anyone is interested. It was an refactoring error, after renaming variables I stopped passing the observable to the ViewController. Instead I created a new one... facepalm
Here are some useful snippets for subscribe in RxSwift (in Japanese)
For example to subscribe to different events:
let source: Observable<Int> = create { (observer: ObserverOf<Int>) in
sendNext(observer, 42)
sendCompleted(observer)
return AnonymousDisposable {
print("disposed")
}
}
let subscription = source.subscribe { (event: Event<Int>) -> Void in
switch event {
case .Next(let element):
print("Next: \(element)")
case .Completed:
print("Completed")
case .Error(let error):
print("Error: \(error)")
}
}
Clean and Build solved the problems for me

Resources