0

I am developing an application to send the current user location every 15 minutes via the MQTT client framework. When the application is in the foreground it works fine but when the application is in background, the MQTT delegate function "messageDelivered" doesn't get called

We want to use the MQTT client framework to publish message in background in iOS swift.

import UIKit
import MQTTClient

class MainViewController: UIViewController {
    
    let MQTT_HOST = "next.nanolink.com" // or IP address e.g. "192.168.0.194"
    //let MQTT_HOST = "tnclicks.free.beeceptor.com" // or IP address e.g. "192.168.0.194"
    let MQTT_PORT: UInt32 = 1883
    
    private var transport = MQTTCFSocketTransport()
    fileprivate var session = MQTTSession()
    fileprivate var completion: (()->())?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        
        //notification observer
        NotificationCenter.default.addObserver(self, selector: #selector(onDidReceiveData(_:)), name: .didReceiveData, object: nil)
        

        //MQTT
        self.session?.delegate = self
        self.transport.host = MQTT_HOST
        self.transport.port = MQTT_PORT
        session?.transport = transport

        
        updateUI(for: self.session?.status ?? .created)
        session?.connect() { error in
            print("connection completed with status \(String(describing: error?.localizedDescription))")
            if error != nil {
                self.updateUI(for: self.session?.status ?? .created)
            } else {
                self.updateUI(for: self.session?.status ?? .error)
            }
        }
        
    }
    
    private func subscribe() {
        self.session?.subscribe(toTopic: "test/message", at: .exactlyOnce) { error, result in
            print("subscribe result error \(String(describing: error)) result \(result!)")
        }
    }
    
    private func updateUI(for clientStatus: MQTTSessionStatus) {
        DispatchQueue.main.async {
            switch clientStatus {
                case .connected:
                    print("Connected")
                    self.publishMessage("on", onTopic: "test/message")

                case .connecting,
                     .created:
                    print ("Trying to connect...")
                default:
                    print ("Connetion Failed...")
            }
        }
    }
    
    private func publishMessage(_ message: String, onTopic topic: String)
    {
        session?.publishData(message.data(using: .utf8, allowLossyConversion: false), onTopic: topic, retain: false, qos: .exactlyOnce)
    }
    
    @objc func onDidReceiveData(_ notification:Notification) {
        print("check return")
        
        guard session?.status == .connected else {
            self.updateUI(for: self.session?.status ?? .error)
            return
        }
        
        let obj  =  notification.object! as! NSMutableDictionary
        
        print(notification.object!)
        let notificationLatitude = obj.value(forKey: "latitude")!
        let notificationLongitude = obj.value(forKey: "longitude")!
        
        //let notificationLongitude = notification.object

//        print(" Saved latitude:", latitude!)
//        print(" Saved longitude:", longitude!)
        
        print(" notification latitude:", notificationLatitude)
        print(" notification longitude:", notificationLongitude)
        
        guard session?.status == .connected else {
            return
        }
        publishMessage("on", onTopic: "test/message")

    }
   
}


extension MainViewController: MQTTSessionManagerDelegate, MQTTSessionDelegate {

    func newMessage(_ session: MQTTSession!, data: Data!, onTopic topic: String!, qos: MQTTQosLevel, retained: Bool, mid: UInt32) {
        if let msg = String(data: data, encoding: .utf8) {
            print("topic \(topic!), msg \(msg)")
        }
    }

    func messageDelivered(_ session: MQTTSession, msgID msgId: UInt16) {
        print("delivered")
        DispatchQueue.main.async {
            self.completion?()
        }
    }
}


extension Notification.Name {
    static let didReceiveData = Notification.Name("didReceiveData")
}

James Z
  • 12,209
  • 10
  • 24
  • 44
  • https://developer.apple.com/documentation/uikit/app_and_environment/scenes/preparing_your_ui_to_run_in_the_background/updating_your_app_with_background_app_refresh – Roman Ryzhiy Sep 16 '20 at 14:08

1 Answers1

0

We have implemented update location in Background so update your code with update location to send Message in background.

import UIKit
import CoreLocation
@UIApplicationMain

class AppDelegate: UIResponder, UIApplicationDelegate,CLLocationManagerDelegate {
var window: UIWindow?
var locationManager = CLLocationManager()
var backgroundUpdateTask: UIBackgroundTaskIdentifier!
var bgtimer = Timer()
var latitude: Double = 0.0
var longitude: Double = 0.0
var current_time = NSDate().timeIntervalSince1970
var timer = Timer()
var f = 0
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    self.doBackgroundTask()
    return true
}


func applicationWillResignActive(_ application: UIApplication) {
}

func applicationWillEnterForeground(_ application: UIApplication) {
    print("Entering foreBackground")
}

func applicationDidBecomeActive(_ application: UIApplication) {
}

func applicationWillTerminate(_ application: UIApplication) {
}

func applicationDidEnterBackground(_ application: UIApplication) {
    print("Entering Background")
   // self.doBackgroundTask()
}

func doBackgroundTask() {

    DispatchQueue.main.async {

        self.beginBackgroundUpdateTask()

        self.StartupdateLocation()


        self.bgtimer = Timer.scheduledTimer(timeInterval:-1, target: self, selector: #selector(AppDelegate.bgtimer(_:)), userInfo: nil, repeats: true)
        RunLoop.current.add(self.bgtimer, forMode: RunLoopMode.defaultRunLoopMode)
        RunLoop.current.run()

        self.endBackgroundUpdateTask()

    }
}

func beginBackgroundUpdateTask() {
    self.backgroundUpdateTask = UIApplication.shared.beginBackgroundTask(expirationHandler: {
        self.endBackgroundUpdateTask()
    })
}

func endBackgroundUpdateTask() {
    UIApplication.shared.endBackgroundTask(self.backgroundUpdateTask)
    self.backgroundUpdateTask = UIBackgroundTaskInvalid
}

func StartupdateLocation() {
    locationManager.delegate = self
    locationManager.desiredAccuracy = kCLLocationAccuracyBest
    locationManager.distanceFilter = kCLDistanceFilterNone
    locationManager.requestAlwaysAuthorization()
    locationManager.allowsBackgroundLocationUpdates = true
    locationManager.pausesLocationUpdatesAutomatically = false
    locationManager.startUpdatingLocation()
}

func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
    print("Error while requesting new coordinates")
}

func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {

    let locValue:CLLocationCoordinate2D = manager.location!.coordinate

    self.latitude = locValue.latitude
    self.longitude = locValue.longitude
    f+=1
    print("New Coordinates: \(f) ")
    print(self.latitude)
    print(self.longitude)
}

@objc func bgtimer(_ timer:Timer!){
    sleep(2)
  /*  if UIApplication.shared.applicationState == .active {
         timer.invalidate()
    }*/
    self.updateLocation()
}

func updateLocation() {
    self.locationManager.startUpdatingLocation()
    self.locationManager.stopUpdatingLocation()
}}
Hardik Bar
  • 86
  • 6
  • Thanks @hardik for your reply We have exactly implemented the code in AppDelegate the same way you mentioned. But our problem is that when we send the coordinates via MQTT and publish message, the message delivered function does not get triggered. – Umar Amjad Sep 16 '20 at 14:35