Assigning a delegate to a background job for dropbox upload - ios

I am using Dropbox's SDK API V1 to upload files from my iOS app. The files aren't loading in real time, but rather I push them into a queue, and they get uploaded in the background.
I can't figure out for the life of me how to take DB RestClient delegate and make it respond to a completion/failure event that happens in the background. Is that even possible?
It seems (and I am new here), that delegates are meant to respond to live events on screen. Is that even an option to assign them to anything other than a UI element/view?
Update: Adding some description of what the code looks like.
There is a lot of code and it's hard for me to tell what's relevant for this question.
I am using PHPhotoLibrary.sharedPhotoLibrary().performChanges() which has a completion handler.
On success, I send an image upload request via dispatch_async(dispatch_get_main_queue()) method.
Dropbox API has this method dropboxRestClient.uploadFile(filename, toPath: pathOnDropbox, withParentRev: nil, fromPath: localFileUrl)
In all the examples that I've seen, dropboxRestClient.delegate was assigned to a UIViewController of some sort, and never to anything else.
So I am not sure how to assign it correctly, so that it recognizes events that happen in this queue.

Related

Alamofire request gets stuck when entering background

When I download a file and I'm inside the app everything works as it should, but when the user gets a call the download just stops
I read a bit and saw that it might be possible to use BGProcessingTaskRequest
But I don't know how to use it with Alamofire

AVAssetDownloadDelegate method not called in background

In a recent project I'm working on video downloading using AVAssetDownloadURLSession. I'm using AVAssetDownloadDelegate to listen to any download updates. Using the below method I'm able to get the percentage download updated for each download task.
urlSession(_:aggregateAssetDownloadTask:didLoad:totalTimeRangesLoaded:timeRangeExpectedToLoad:for:)
Issue:
The above method doesn't get called when the app is in Background. There is nothing mentioned in the Apple docs regarding the method's foreground and background behaviour.
How can I clarify how the method works? And what is the way around if I want to get the background updated as well?

Rewarded Interstitials - How to react to a user cancelling an Ad? (AdMob Google Ads, iOS)

I'm implementing GADRewardedInterstitialAd into a game.
https://developers.google.com/admob/ios/api/reference/Classes/GADRewardedInterstitialAd
I'm using presentFromRootViewController:userDidEarnRewardHandler to react to the user finishing the ad.
Now I'd also like to know how to react to the user cancelling the ad.
If I continue directly after calling presentFromRootViewController, the callback handler will not have been called yet, because the systems works asynchonous, as is to be expected. So any game animations (e.g. screen fade, dialog close) will have to be stalled.
If I rely only on the handler, I won't get a callback when the ad was cancelled.
My solution would be to build in a timer that waits 30+1s to give the handler a chance to get called (hopefully on the next main thread dispatch cycle), and then react to it not being called yet (assuming a cancellation by the user).
I really hate that plan.
It's not deterministic.
It doesn't use callbacks/delegates/handlers (which are great exactly for this kind of thing)
I have to write the timer code and keep a boolean flag somewhere... it's messy.
It adds an arbitrary delay to the user experience (30+1s) when they close the ad!!
Am I thinking the wrong way about this or is this just the way Google has made it and I'll have to live with it?
Edit: please note that I'm talking about the new GADRewardedInterstitialAd API, not GADRewardedAd.
I've figured it out; it works by setting GADFullScreenContentDelegate fullScreenContentDelegate and implementing adDidDismissFullScreenContent.
In there you can check if this particular instance of GADRewardedInterstitialAd did not get a reward yet (as notified by userDidEarnRewardHandler...)
This all hinges on the assertion that adDidDismissFullScreenContent gets called AFTER the userDidEarnRewardHandler, else I will already have assumed there was no reward. Let's hope that is always the case.
https://developers.google.com/ad-manager/mobile-ads-sdk/ios/api/reference/Protocols/GADFullScreenContentDelegate

resuming upload of file after app crash using nsurlsession

I want to upload a file to a server using NSURLSession.
Cases Are:
1. It should resume uploading a file to the server from where it stopped because of app crash.
2. It should handle background upload as well.
Try AFNetworking Library to upload image asynchronously.You can find a brief example in this thread.
You should use background NSURLSession. If your app crashed or user left the app while upload was in progress, with a background NSURLSession the upload would continue seamlessly in the background. When the upload is done, your app will be notified of this via the delegate (and if your app wasn't alive at the time the download finished, it will be started in a background mode, at which point you can do whatever cleanup you need).
So create NSURLSessionConfiguration with backgroundSessionConfigurationWithIdentifier, and then instantiate a NSURLSession with that configuration.
There are a few caveats:
You cannot use completion handler pattern. You have to use delegate-based implementation.
You have to implement handleEventsForBackgroundURLSession in the app delegate, capturing the completionHandler it passes you and also instantiate the background session again. Likewise, in your NSURLSession delegate methods, you have to implement URLSessionDidFinishEventsForBackgroundURLSession, which will call the saved completion handler.
For more information, see Background Task Considerations in URL Session Programming Guide, see a section of the same name (but different text) in the NSURLSession class reference, or see the WWDC 2013 What's New in Foundation Networking, where Apple first introduced us to background sessions.

Make http call on iOS while offline

Perhaps I have been reading the wrong stuff, but one thing that all of the literatures that I have been reading seem to agree on is that: iOS does not allow background threads to run for longer than ten minutes. That seems to violate one of the greatest principles of app development: the internet should be invisible to your users. So here is a scenario.
A user is going through a tunnel or flying on an airplane, which causes no or unreliable network. At that instant, the user pulls out my email app, composes an email, and hits the send button.
Question: How do I the developer make sure that the email is sent when network becomes available? Of course I am using email as a general example, but in reality I am dealing with a very much simple http situation where my app needs to send a POST to my server.
Side Note: on android, I use Path’s priority job queue, which allows me to set it and forget it (i.e. as soon as there is network it sends my email).
another Side Note: I have been trying to use NSOperationQueue with AFNetworking, but does not do it.
What you want to achieve can be done using a background NSURLSession. While AFNetworking is based on NSURLSession I’m not quite sure if it can be used with a background session that runs while your app doesn’t. But you don’t really need this, NSURLSession is quite easy to use as is.
As a first step you need to create a session configuration for the background session:
let config = URLSessionConfiguration.background(withIdentifier: "de.5sw.test")
config.isDiscretionary = true
config.waitsForConnectivity = true
The isDiscretionary property allows the system to decide when to perform the data transfer. waitsForConnectivity (available since iOS 11) makes the system wait if there is no internet connection instead of failing immediately.
With that configuration object you can create your URL session. The important part is to specify a delegate as the closure-based callbacks get lost when the app is terminated.
let session = URLSession(configuration: config, delegate: self, delegateQueue: OperationQueue.main)
To perform your upload you ask the session to create an upload task and then resume it. For the upload task you first create your URLRequest that specifies the URL and all needed headers. The actual data you want to upload needs to be written to a file. If you provide it as a Data or stream object it cannot be uploaded after your app terminates.
let task = session.uploadTask(with: request, fromFile: fileUrl)
task.resume()
To get notified of success or failure of your upload you need to implement the URLSessionDataDelegate method urlSession(_:task:didCompleteWithError:). If error is nil the transfer was successful.
The final piece that is missing is to handle the events that happened while your app was not running. To do this you implement the method application(_:handleEventsForBackgroundURLSession:completionHandler:) in your app delegate. When the system decides that you need to handles some events for background transfers it launches your app in the background and calls this method.
In there you need first store the completion handler and then recreate your URLSession with the same configuration you used before. This then calls it’s delegate for the events you need to handle as usual. Once it is done with the events it calls the delegate method urlSessionDidFinishEvents(forBackgroundURLSession:). From there you need to call the completion handler that was passed to your app delegate.
The session configuration provides some more options:
timeoutIntervalForResource: How long the system should try to perform your upload. Default is 7 days.
sessionSendsLaunchEvents: If false the app will not be launched to handle events. They will be handled when the user opens the app manually. Defaults is true.
Here is a small sample project that shows how everything fits together: https://github.com/5sw/BackgroundUploadDemo
Your app needs to store the data internally and then you either need something which will cause the app to run in the background (but you shouldn't necessarily add something specially if you don't already have a reason to be doing it) or to wait until the user next brings the app to the foreground - then you can check for a network connection and make the call.
Note that e-mail is very different to a POST, because you can pass an e-mail off to the system mail app to send for you but you can't do exactly the same thing with a POST.
Consider looking also at NSURLSessionUploadTask if you can use it.
In three words: you don't.
And that's actually a good thing. I certainly do not want to have to think and speculate about my last 20 apps, if they are still running in the background, using memory and battery and bandwidth. Furthermore, they would be killed if more memory is needed. How would the user be able to predict if it completed its task successfully? He can't, and need to open the app anyhow to check.
As for the email example, I'd go with showing the email as "pending" (i.e. not sent), until it transferred correctly. Make it obvious to the user that he has to come back later to fulfill the job.
While every developer thinks that his app has an extremely good reason for backgrounding, reality is, for the user in 99% it's just a pain. Can you say "task manager"? ;-)
I wrote a pod that does pretty much this - https://cocoapods.org/pods/OfflineRequestManager. You'd have to do some work listening to delegate callbacks if you want to monitor whether the request is in a pending or completed/failed state, but we've been using it to ensure that requests go out in poor or no connectivity scenarios.
The simplest use case would look something like the following, though most actual cases (saving to disk, specific request data, etc.) will have a few more hoops to jump through:
import OfflineRequestManager
class SimpleRequest: OfflineRequest {
func perform(completion: #escaping (Error?) -> Void) {
doMyNetworkRequest(withCompletion: { response, error in
handleResponse(response)
completion(error)
})
}
}
///////
OfflineRequestManager.defaultManager(queueRequest: SimpleRequest())

Resources