Fetch data from Alamofire in background mode - ios

I need to update orders from my app while app is in background.
Ok, I am using OneSignal, I can get message on didReceiveRemoteNotification and inside it, I call Alamofire to check on my api what I need to update.
The problem is when the code get to the point: Alamofire.request(url).responseJSON {(response) in it doesnt go inside, just when I open the app I can get the result.
I would like it to get the new data on background and notify users after updating, so they can click on the notification to see whats is new.
I read that Alamofire runs on a background thread by default, but the network request goes on Main thread.
So, I tried: this and this, both don't work.
I tried URLSessionConfiguration but I got Error code -999 cancelled.
So, I added sessionManager.session.finishTasksAndInvalidate() in the end of my response. The error stops, but the code still don't go inside Alamofire request.
Some of my code - didReceiveRemoteNotification on my AppDelegate:
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
if let custom = userInfo["custom"] as? NSDictionary {
if let a = custom["a"] as? NSDictionary {
if let update = a["update"] as? NSString {
if update.isEqual(to: "Pedido") {
let strdataPedido = PedidoDAO().getMostRecentDtPedido()
if self.defaults.getDownloadInicialPedido(){
if strdataPedido != "" {
//let task = self.beginBackgroundTask()
self.loadOrdersFromLastSyncByApi(strdataPedido)
//self.endBackgroundTask(task)
}
}
}
}
}
}
loadOrdersFromLastSyncByApi function on my AppDelegate:
func loadOrdersFromLastSyncByApi(_ lastSync: String) {
let parceiroId = defaults.getParceiroId()
PedidoAPI().loadOrdersForLastSync(parceiroId, lastSync){ (dados) in
if let dadosPedidoModel = dados as? [PedidoModel] {
//do what needs to do to save new data
}
PedidoAPI().loadOrdersForLastSync function:
func loadOrdersForLastSync(_ parceiroId: String, _ lastSync: String, _ onComplete: #escaping(Any?) -> Void) {
let url = Test.basePath + "/api/orders/parceiro/\(parceiroId)/\(lastSync)"
//let queue = DispatchQueue(label: "com.test.br", qos: .background, attributes: .concurrent)
let task = self.beginBackgroundTask()
let queue = DispatchQueue.global(qos: .background)
queue.async {
Alamofire.request(url).responseJSON {(response) in
//This result its fired just when I open my app, I would like it to make everything on background
switch (response.result) {
//do what needs to send new data
}
self.endBackgroundTask(task)
Any help please?

Thanks for your question. So to clarify, I understood your question as the following:
You want to update users when there has been a change in their orders while the app is in background.
Follow-up Question:
Is there a reason you want to make the request from the client? Also, what data is coming in from OneSignal that you couldn't just handle on your server?
Answer:
You should handle any requests to a third party (Alamofire) on the server and then use our API to send a notification to the user with new info from the request response. I think that would be the best approach.

Related

didReceiveRemoteNotification only works for a short time for background case then stops working

I am using push notifications for an iOS chat app.
I have successfully implemented and receive push notifications and also implemented background fetch.
When I upload a new build either for TestFlight or through he store, I can correctly run through the function didReceiveRemoteNotification until the completionHandler only for several push notifications. ( about 10 or so ) in background mode.
After that the function does not run in background mode. It either get's out of sync or stops working. I know apple limits apps in background mode but not sure this is the case as this function call in my case only takes less than one second to process:
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
let state = UIApplication.shared.applicationState
if state == .background {
if saveNotificationData(userInfo: userInfo) {
completionHandler(UIBackgroundFetchResult.newData)
} else {
completionHandler(UIBackgroundFetchResult.noData)
}
} else {
completionHandler(UIBackgroundFetchResult.noData)
}
}
private func saveNotificationData(userInfo: [AnyHashable: Any]) -> Bool {
let context = self.privateContext
var result = false
if let chatRoomId = (userInfo["chatRoomId"] as? String) {
do {
let fetchRequest: NSFetchRequest<Room> = Room.fetchRequest()
fetchRequest.predicate = NSPredicate(format: "chatRoomId == %#", chatRoomId)
let fetchedResults = try context.fetch(fetchRequest)
if fetchedResults.count > 0 {
let message = Message(context: context)
message.messageRoomId = userInfo["chatRoomId"] as? String
message.messageCreatorUserId = userInfo["user_id"] as? String
message.messageText = userInfo["message_text"] as? String
message.messageChatRoom = fetchedResults.first!
do {
try context.save()
result = true
} catch {
print(error)
}
}
} catch {
// error fetching list
print("Could not fetch \(error)")
}
}
return result
}
I have tried so many different ways to do this including using the main context instead of a defined background context. Putting the save function in a context.perform block, dispatching to main queue prior to returning the completionHandler.
But the result is always the same. The functions run as expected for the first few notifications and then stop working in background mode. Either getting out of sync or just being locked out. It seems the previous function execution continues when the next push is received which throws things out of order.
I am also implementing didReceive and willPresent but I have removed them completely to see if it made any difference but it did not. Really stuck here and no idea what is causing the problem.
I wonder if it is an out of sync case if when executing the new push I can cancel what is ongoing from the previous one? But that may not be the problem.

How to Call Background Fetch Completion Handler Properly

Currently, I am using an API (PowerAPI) in which the "authenticate" function is called and then once the user's data has been processed, a notification is sent out. This authenticate function needs to be called as a background fetch. My question is whether my current way of calling the completion handler is even calling the completion handler and if there is a better way?
Currently this is in my app delegate class:
let api = PowerAPI.sharedInstance
var completionHandler: ((UIBackgroundFetchResult) -> Void)? = nil
func application(application: UIApplication, performFetchWithCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
print("BG fetch start")
NSNotificationCenter.defaultCenter().addObserver(self, selector: "handleTranscript:", name:"transcript_parsed", object: nil)
self.completionHandler = completionHandler
api.authenticate("server.com", username: "User", password: "password", fetchTranscript: true)
}
func handleTranscript(notification : NSNotification){
print("BG fetch finit")
completionHandler!(UIBackgroundFetchResult.NewData)
print(api.studentInformation)
}
The API is a singleton type object.
EDIT: The PowerAPI object is a class I wrote to download student data from a server and parse it. The "transcript_parsed" notification is a notification generated from within the PowerAPI directly after the "transcript_fetched" notification is sent out in the following asynchronous code (Also within PowerAPI):
let task = session.dataTaskWithRequest(request) {
(let data, let response, let error) in
guard let _:NSData = data, let _:NSURLResponse = response where error == nil else {
print("error")
return
}
switch notificationID {
case "authentication_finished":
//NSString(data: data!, encoding: NSUTF8StringEncoding)! //used to return data from authentication
let success = self.parse(data!) //notification object is true if success
NSNotificationCenter.defaultCenter().postNotificationName(notificationID, object: success)
case "transcript_fetched":
NSNotificationCenter.defaultCenter().postNotificationName(notificationID, object: data)
default:
break
}
}

No array sent from parent app to Watch app

I am trying to receive an array of objects (that are retrieved from Parse within the app) from a parent application to be displayed in the watch application. I have been trying a few different things but with no success.
Here is my code in the extension:
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
var parkPassed = context as! String
openParentAppWithPark(parkPassed)
}
private func openParentAppWithPark(park: String) {
WKInterfaceController.openParentApplication(["request": park], reply: { (reply, error) -> Void in
println(reply)
})
}
And the code in the parent app:
func application(application: UIApplication, handleWatchKitExtensionRequest userInfo: [NSObject : AnyObject]?, reply: (([NSObject : AnyObject]!) -> Void)!) {
println("Test")
if let userInfo = userInfo, request = userInfo["request"] as? NSArray {
if request == "Park 1" {
DataManager.sharedInstance.loadRides("Park 1")
} else if request == "Park 2" {
DataManager.sharedInstance.loadRides("Park 2")
} else if request == "Park 3" {
DataManager.sharedInstance.loadRides("Park 3")
} else {
DataManager.sharedInstance.loadRides("Park 4")
}
let rides = DataManager.sharedInstance.rideArray
println("Rides: \(rides)")
reply(["rideData": rides])
return
}
reply([:])
}
The println I have always returns nil the first time I try to load, and then [:] every other time. I assume this is because the request is timing out before the app has time to load the data from Parse? Also, the println that is supposed to print "Test" is never called.
In the extension, you're passing a String (park) to the parent application via the request key, but in the parent application, you're testing whether userInfo["request"] is an NSArray or not. You should be testing for a String, as in:
if let userInfo = userInfo, request = userInfo["request"] as? String {
First add a background task assertion to the openParentCall, you can find more context on that here: Background Task Watchkit
let backgroundTask = application.beginBackgroundTaskWithExpirationHandler { NSLog("TIME UP")}
///do code
reply(callback)
//
application.endBackgroundTask(backgroundId)
Now for the actual handleWatchKitExtensionRequest call I would change the first line to
if let request = userInfo["request"] as? String {
Now for the println("Test") not printing to console if you don't attach to process with the parentApplication then the println will not log out.
If the ride data is returning empty then I would inspect this function:
DataManager.sharedInstance.loadRides(ride: String)
make sure it is actually returning the correct data you need. Attach to process and place a breakpoint on each case and check that one of the cases is being called and also jump into the loadRides function to make sure it is coming back out from it. As a side note, the information you send back in the reply block has to be a property list or the reply block will always fail.

How trigger background process from Watch on iPhone (trigger: Watch)?

I'd like to add to my Watch app functionality which send to iPhone app a Local Notification (while iPhone app is on the background or iPhone is locked).
I know how to create Local Notification itself.
What Im asking for is way, how to trigger background process (which contains also Local Notification) on iPhone by (for example) tapping on button on Apple Watch.
WKInterfaceController.openParentApplication is the official way to communicate with the iPhone. Documentation.
You pass parameters in the userInfo dictionary and retrieve results via the reply block.
On the iPhone the request is handled by appDelegate's handleWatchKitExtensionRequest method. Documentation
Code in my InterfaceController.swift:
#IBAction func btn() {
sendMessageToParentApp("Button tapped")
}
// METHODS #2:
func sendMessageToParentApp (input:String) {
let dictionary = ["message":input]
WKInterfaceController.openParentApplication(dictionary, reply: { (replyDictionary, error) -> Void in
if let castedResponseDictionary = replyDictionary as? [String:String], responseMessage = castedResponseDictionary["message"] {
println(responseMessage)
self.lbl.setText(responseMessage)
}
})
}
Next i made new method in my AppDelegate.swift:
func application(application: UIApplication, handleWatchKitExtensionRequest userInfo: [NSObject : AnyObject]?, reply: (([NSObject : AnyObject]!) -> Void)!) {
if let infoDictionary = userInfo as? [String:String], message = infoDictionary["message"] {
let response = "iPhone has seen this message." // odešle se string obsahující message (tedy ten String)
let responseDictionary = ["message":response] // tohle zase vyrobí slovník "message":String
NSNotificationCenter.defaultCenter().postNotificationName(notificationWatch, object: nil)
reply(responseDictionary)
}
}
As you can see I use Notification to get iOS app know that button has been tapped. In ViewController.swift I have Notification Observer and function which is executed every time observer catch notification that user tapped on button on watch ("notificationWatch" is global variable with notification key). Hope this will help to anybody.

how to make alamofire download progress run in background ios?

I am using Alamofire to download data
How to make alamofire run download in background with swift?
Thanks
The basic idea is as follows:
The key problem is that with background downloads, your app may actually be terminated while downloads are in progress (e.g. jettisoned due to memory pressure). Fortunately, your app is fired up again when background downloads are done, but any task-level closures you originally supplied are long gone. To get around this, when using background sessions, one should rely upon session-level closures used by the delegate methods.
import UIKit
import Alamofire
import UserNotifications
fileprivate let backgroundIdentifier = ...
fileprivate let notificationIdentifier = ...
final class BackgroundSession {
/// Shared singleton instance of BackgroundSession
static let shared = BackgroundSession()
/// AlamoFire `SessionManager`
///
/// This is `private` to keep this app loosely coupled with Alamofire.
private let manager: SessionManager
/// Save background completion handler, supplied by app delegate
func saveBackgroundCompletionHandler(_ backgroundCompletionHandler: #escaping () -> Void) {
manager.backgroundCompletionHandler = backgroundCompletionHandler
}
/// Initialize background session
///
/// This is `private` to avoid accidentally instantiating separate instance of this singleton object.
private init() {
let configuration = URLSessionConfiguration.background(withIdentifier: backgroundIdentifier)
manager = SessionManager(configuration: configuration)
// specify what to do when download is done
manager.delegate.downloadTaskDidFinishDownloadingToURL = { _, task, location in
do {
let destination = try FileManager.default.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
.appendingPathComponent(task.originalRequest!.url!.lastPathComponent)
try FileManager.default.moveItem(at: location, to: destination)
} catch {
print("\(error)")
}
}
// specify what to do when background session finishes; i.e. make sure to call saved completion handler
// if you don't implement this, it will call the saved `backgroundCompletionHandler` for you
manager.delegate.sessionDidFinishEventsForBackgroundURLSession = { [weak self] _ in
self?.manager.backgroundCompletionHandler?()
self?.manager.backgroundCompletionHandler = nil
// if you want, tell the user that the downloads are done
let content = UNMutableNotificationContent()
content.title = "All downloads done"
content.body = "Whoo, hoo!"
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false)
let notification = UNNotificationRequest(identifier: notificationIdentifier, content: content, trigger: trigger)
UNUserNotificationCenter.current().add(notification)
}
// specify what to do upon error
manager.delegate.taskDidComplete = { _, task, error in
let filename = task.originalRequest!.url!.lastPathComponent
if let error = error {
print("\(filename) error: \(error)")
} else {
print("\(filename) done!")
}
// I might want to post some event to `NotificationCenter`
// so app UI can be updated, if it's in foreground
}
}
func download(_ url: URL) {
manager.download(url)
}
}
Then I can just initiate those downloads. Note, I do not specify any task-specific closure when I initiate the download, but rather merely use the above session-level closures that use the details of the URLSessionTask to identify what to do:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// request permission to post notification if download finishes while this is running in background
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound]) { granted, error in
if let error = error, !granted {
print("\(error)")
}
}
}
#IBAction func didTapButton(_ sender: Any) {
let urlStrings = [
"http://spaceflight.nasa.gov/gallery/images/apollo/apollo17/hires/s72-55482.jpg",
"http://spaceflight.nasa.gov/gallery/images/apollo/apollo10/hires/as10-34-5162.jpg",
"http://spaceflight.nasa.gov/gallery/images/apollo-soyuz/apollo-soyuz/hires/s75-33375.jpg",
"http://spaceflight.nasa.gov/gallery/images/apollo/apollo17/hires/as17-134-20380.jpg",
"http://spaceflight.nasa.gov/gallery/images/apollo/apollo17/hires/as17-140-21497.jpg",
"http://spaceflight.nasa.gov/gallery/images/apollo/apollo17/hires/as17-148-22727.jpg"
]
let urls = urlStrings.flatMap { URL(string: $0) }
for url in urls {
BackgroundSession.shared.download(url)
}
}
}
If your app isn't running when the downloads finish, iOS needs to know that, after it restarted your app, when you're all done and that it can safely suspend your app. So, in handleEventsForBackgroundURLSession you capture that closure:
class AppDelegate: UIResponder, UIApplicationDelegate {
...
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: #escaping () -> Void) {
BackgroundSession.shared.saveBackgroundCompletionHandler(completionHandler)
}
}
That is used by sessionDidFinishEventsForBackgroundURLSession, in step 1.
Two observations:
This is only called if your app was not running when the downloads finish.
If doing background sessions, though, you must capture this closure and call it when you're all done processing the background session delegate methods.
So, to recap, the basic limitations of background sessions are:
You can only use download and upload tasks while the app is in background;
You can only rely upon session-level delegates because the app may have been terminated since the requests were initiated; and
In iOS, you must implement handleEventsForBackgroundURLSession, capture that completion handler, and call it when your background process is done.
I must also point out that while Alamofire is a wonderful library, it's not actually adding a lot value (above and beyond what is provided by URLSession to this background download process). If you're doing simple uploads/downloads only, then you might consider just using URLSession directly. But if you are using Alamofire in your project already or if your requests consist of more complicated application/x-www-form-urlencoded requests (or whatever) which merit the advantages of Alamofire, then the above outlines the key moving parts involved in the process.

Resources