Sometimes the implementation does not work when the application is disconnected - ios

I want to delete some data in google firesore when the app disconnects.
However, the following code does not work correctly.
Also, it works correctly in the simulator, but not on the actual machine.
import Firebase
import FirebaseFirestore
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
let db = Firestore.firestore()
var window: UIWindow?
func sceneDidDisconnect(_ scene: UIScene) {
db.collection("parentCol").document("doc").collection("col_1").document("doc_1").delete()
db.collection("parentCol").document("doc").collection("col_2").document("doc_2").delete()
db.collection("parentCol").document("doc").delete()
}
}
What I expect
(I am producing a game application.)
The host determines the roomId and produces a room.
Guests connect to the room via that roomId.
When the game ends or the player disconnects from the application, server data that is no longer needed is deleted.
//data structure
col: rooms
|
|- doc: 1234 (roomId)
| |
| |- col: members
| |
| |- doc: name ( here I store an array of String)
| |- 1: Tom
| |- 2: Ares
| |- 3: Michael
|
|- doc: abc (roomId)
 
What I tried
It worked fine in the simulator.
However, when I tried it on the actual device, it did not work correctly.
Only doc_1 was deleted in the following code.↓
func sceneDidDisconnect(_ scene: UIScene) {
db.collection("parentCol").document("doc").collection("col_1").document("doc_1").delete()
db.collection("parentCol").document("doc").delete()
}
Only doc was deleted in the following code.↓
func sceneDidDisconnect(_ scene: UIScene) {
db.collection("parentCol").document("doc").delete()
}
Nothing is deleted when the following code.↓
func sceneDidDisconnect(_ scene: UIScene) {
db.collection("parentCol").document("doc").collection("col_1").document("doc_1").delete()
db.collection("parentCol").document("doc").collection("col_2").document("doc_2").delete()
db.collection("parentCol").document("doc").delete()
}
 
I know that after an app is disconnected, it will only run for a few seconds. But that should be more than enough time to delete data.
If you have any solutions, please let me know.
My native language is not English, so sorry if it is hard to read.

I think what happens on simulator (i.e. sceneDidDisconnect is a few seconds delayed) is rather an anomaly, while the behavior you see on device is correct (i.e. sceneDidDisconnect is almost instantaneous).
I recommend that you re-read Managing your app’s life cycle. It states that scene disconnection is for cleanup, while any data operations should be done prior to that when the app is about to leave the foreground-active state:
Upon leaving the foreground-active state, save data and quiet your app’s behavior. See Preparing your UI to run in the background.
Upon entering the background state, finish crucial tasks, free up as much memory as possible, and prepare for your app snapshot. See Preparing your UI to run in the background.
At scene disconnection, clean up any shared resources associated with the scene.
So use sceneWillResignActive(_:) to perform the deletion instead. Furthermore: as Apple states -
Don’t rely on specific app transitions to save all of your app's critical data.
So if you can do these deletions on some other app events, do that, and only use app events as a backup. And also make sure your app handles a situation where the data may still exist on next invocation of the app (because if the app is killed in some more brutal ways may never allow any of the shutdown functions to run).

Related

How to disable vpn connection when app is terminated in iOS?

I'm implementing Personal VPN by PacketTunnel. Through startVPNTunnel method of NETunnelProviderManager, i checked whether VPN Connection is run well.
However, I have a problem. I added the exit code of vpn connection in applicationWillTerminate to disable vpn connection when app is terminated like following code below. But it doesn't work.
If test this code, loadAllFromPreferences is run but callback function of loadAllFromPreferences isn't called. This code runs well anywhere except applicationWillTerminate. Why it doesn't work? Is there any way to disable vpn when app is terminated?
func applicationWillTerminate(_ application: UIApplication) {
NETunnelProviderManager.loadAllFromPreferences { (managers, error) in
var manager:NETunnelProviderManager?=nil;
for m in managers! {
if m != nil {
if m.localizedDescription == "profile" {
manager = m;
break
}
}
}
manager?.connection.stopVPNTunnel()
}
}
As per Apple's documentation of applicationWillTerminate(application:):
Your implementation of this method has approximately five seconds to perform any tasks and return. If the method does not return before time expires, the system may kill the process altogether.
If applicationWillTerminate is called but the closure is not make sure that it does not take more than "approximately" (sic) 5 seconds.
I have not used the libraries you are using but from a general point of view there are probably better strategies than iterating over all available managers. Like storing a reference/identifier to a given manager when starting a connection and using that reference/identifier to terminate it.

How to track the time a user listens an specific podcast

I have an iOS app that is about podcasts and I want to track how long a user listens every podcast. I have tried the basic - when a user plays I save the timestamp and when stops it sends an event with the timestamp difference but it obviously doens't work because there's many edge cases.
I have issues to know when a user has the app in background and stops listening at some point through the the system controls. Also when the user or the system kills the app without tapping on "pause" or "stop". I think these 2 cases are my main non-tracked cases so far.
Any idea how can I build a working solution? I don't want/can't pay an external service - I am merely relying on Firebase.
Thanks!
You can override applicationWillTerminate method in your app, and save a current user progress to UserDefaults.
As docs say, you have few seconds to do it:
This method lets your app know that it is about to be terminated and
purged from memory entirely. You should use this method to perform any
final clean-up tasks for your app, such as freeing shared resources,
saving user data, and invalidating timers. Your implementation of this
method has approximately five seconds to perform any tasks and return.
Your code can look like this:
var player: AVPlayer!
func applicationWillTerminate(_ application: UIApplication) {
UserDefaults.standard.setValue(player.currentTime().seconds, forKey: "curPlayerTime")
}
Then, on application launch, you can restore it:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
if let lastPlayerTime = UserDefaults.standard.value(forKey: "curPlayerTime") as? Double {
// update your player
}
return true
}

Nativescript: Continue code execution when app goes to background

(edited to provide updated info)
I have a nativescript app that performs various tasks that I would like to continue going if the phone goes into background mode or is locked.
Focused on iOS, with Nativescript Angular. I am also new to using obj C code in Nativescript.
As an easy example, let's say I want to print to the console every 5 seconds after a user hits a button, so I have the following code in my component ts file:
coolComponent.ts:
#Component({...})
Export class coolComponent {
...
whenButtonClicked(){
setInterval(function(){
console.log('button has been clicked. show every 5 seconds!');
}, 5000);
}
Without further code, when the user hits the button, it will print to console every 5 seconds, but then stop when the app is in the background or phone is locked. How do I get the function to continue executing even when app is in the background or locked?
In seeing different sources, like here (NS docs on background execution) and here (docs on app delegate) , it looks like the first step is to create a custom-app-delegate, get that to work, and then identify the background task in info.plist.
I have gotten things generally to be functional, like this:
app/custom-app-delegate.ts:
import { ios, run as applicationRun } from "tns-core-modules/application";
export class CustomAppDelegate extends UIResponder implements
UIApplicationDelegate {
public static ObjCProtocols = [UIApplicationDelegate];
public applicationDidEnterBackground(application: UIApplication) {
console.log('in background mode!')
}
}
main.ts:
import { platformNativeScriptDynamic } from "nativescript-angular/platform";
import { AppModule } from "./app.module";
import * as application from "tns-core-modules/application";
import { CustomAppDelegate } from "./custom-app-delegate";
application.ios.delegate = CustomAppDelegate;
platformNativeScriptDynamic().bootstrapModule(AppModule);
app/app.module.ts:
import { CustomAppDelegate } from "./custom-app-delegate";
app/App_Resources/iOS/info.plist:
...
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
</array>
edit: create reference.d.ts:
/// <reference path="./node_modules/tns-platform-declarations/ios.d.ts" />
/// <reference path="./node_modules/tns-platform-declarations/android.d.ts" />
Edit: FYI, to get the custom-app-delegate to work, I also had to download "tns-platform-declerations", with the command:
$ npm i tns-platform-declarations --save-dev
With this, the app properly reads "in background mode!" when the app goes to the background. So the custom-app-delegate is functional.
However, the examples online assume that the code in the custom-app-delegate is independent of the rest of the app, so they assume there are new tasks to do when the app goes into background mode.
That is not the case here. I have a task that is being performed from the coolComponent function, and when the app goes into background or is locked I want that to continue.
This probably requires that coolComponent.ts communicate with custom-app-delegate, but I don't know how to do this.
Just repeating the code in both files--having the setInterval function appear in both coolComponent.ts and custom-app-delegate--does not work, because this would not result in the custom-app-delegate continuing on the same timing that began in coolComponent.ts after the user hit the button.
So how can I have the code start in coolComponent.ts and continue after the app is in background mode?
Technically you can't force your app to be active when user no longer wants it to be (by locking the phone / minimising the app). If you like to run anything in background, you will have to use background fetch in iOS.
iOS allows you to run code in the background only for certain situations. For instance (but not limited to):
Background location updates.
Audio and video playback (PiP in iPad)
Remote Push Notifications handling
among others...
If your app does not fit any of the available categories, the best you can do is to request to iOS more time to run in the background (by default is 10 seconds). This will allow you to run for 3 more minutes. Just keep running the task in an infinite loop and gracefully terminate your app before the granted 180 seconds.
Regarding background fetch, this mechanism allows apps to update its contents in the background. iOS will execute apps that declare background fetch at least once a day, so you, in your delegate can perform an update from the server. This mechanism is not suitable for what you are looking for.

Do I have to always remove the listener in Firestore?

I am actually confused what the benefits of removing listener when I listen to Firestore database. I have tried to find the documentation but I can't find it
I usually always remove the listener in viewWillDisappear. from the video in here https://www.youtube.com/watch?v=rvxYRm6n_NM it is said that the benefit of removing listener is to reduce battery and data usage, based on that so I think it will be good if I always remove the listener before the view disappear.
var userListener : ListenerRegistration?
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// removing listener
guard let userListener = userListener else {return}
userListener.remove()
}
but when I read the firestore pricing in here https://firebase.google.com/docs/firestore/pricing
it is said:
Also, if the listener is disconnected for more than 30 minutes (for
example, if the user goes offline), you will be charged for reads as
if you had issued a brand-new query.
if I remove the listener, is it just the same as disconnected? because it will affect the number of document read.
so What is the best time to remove the listener or what is the advantage and disadvantage of removing listener?
In most scenarios I see, developers remove their listeners when the view that needs their data disappears. This may indeed to additional documents having to be read on the server when you reconnect the listener, to check if they've been updated. But the alternative is to keep the connection open, which may lead to additional battery drain and bandwidth usage. Which one of these is preferable for your app, only you can tell.

iOS Swift - Reload location function with NSTimer for app background doesn't work

I've got a problem with location services. I can't set up a function that updates my location coordinates in the background by a NSTimer. Here is my code from appDelegate:
var locationManager = CLLocationManager()
func applicationDidEnterBackground(application: UIApplication) {
self.locationManager.delegate = self
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
self.theTimer = NSTimer(fireDate: NSDate(), interval: 40, target: self, selector: "handleTimer", userInfo: nil, repeats: true)
NSRunLoop.currentRunLoop().addTimer(self.theTimer, forMode: NSDefaultRunLoopMode)
}
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
var locValue:CLLocationCoordinate2D = manager.location.coordinate
println("dinBack = \(locValue.latitude) \(locValue.longitude)")
self.locationManager.stopUpdatingLocation()
}
func handleTimer(){
println("started")
self.locationManager.startUpdatingLocation()
}
PS. - Of course that i've imported corelocation.
- When I get back into the app, the console prints what should have printed in the background.
You can not make an NSTimer work like this while your application is in the background. NSTimer's are not "real-time mechanisms". From the official documentation:
Timers work in conjunction with run loops. To use a timer effectively, you should be aware of how run loops operate—see NSRunLoop and Threading Programming Guide. Note in particular that run loops maintain strong references to their timers, so you don’t have to maintain your own strong reference to a timer after you have added it to a run loop.
A timer is not a real-time mechanism; it fires only when one of the run loop modes to which the timer has been added is running and able to check if the timer’s firing time has passed. Because of the various input sources a typical run loop manages, the effective resolution of the time interval for a timer is limited to on the order of 50-100 milliseconds. If a timer’s firing time occurs during a long callout or while the run loop is in a mode that is not monitoring the timer, the timer does not fire until the next time the run loop checks the timer. Therefore, the actual time at which the timer fires potentially can be a significant period of time after the scheduled firing time.
Emphasis mine.
The important take away from this is that while your application is in the background, any run loop that your timer would have been scheduled on is not actively running.
As soon as your app returns to the foreground, this run loop fires back up, sees that your timer is overdue, and sends the message to the selector.
With iOS 7 and forward, if you want to perform operations in the background, you can tell the OS that you want to perform "background fetches".
To set this up, we must first tell the OS how frequently we want to fetch data, so in didFinishLaunching..., add the following method:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
application.setMinimumBackgroundFetchInterval(UIApplicationBackgroundFetchIntervalMinimum)
return true
}
We can pass any time interval here (for example, if we only want to check once a day). The value we pass in is only defining a minimum amount of time that should pass between checks, however. There is no way to tell the OS a maximum amount of time between checks.
Now, we must implement the method that actually gets called when the OS gives us an opportunity to do background work:
func application(application: UIApplication, performFetchWithCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
// do background work
}
We can do whatever we want within this method. There are two catches, however.
This method is called while our app is in the background. The OS limits us to (I believe) thirty seconds. After thirty seconds, our time is up.
We must call the completionHandler() (or the OS will think we used all of our time).
The completionHandler that gets passed in takes an enum, UIBackgroundFetchResult. We should pass it either .Failed, .NewData, or .NoData, depending upon what our actual results were (this approach is typically used for checking a server for fresh data).
So, our method might look like this:
func application(application: UIApplication, performFetchWithCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
// do stuff
if let _ = error {
completionHandler(.Failed)
} else if results.count > 0 {
completionHandler(.NewData)
} else {
completionHandler(.NoData)
}
}
Keep in mind, we have absolutely zero control over how frequently the OS will actually let us run this code in the background. The OS uses several metrics in order to optimize the user's experience.
I think if your app reports .Failed to the completion handler, the OS might give you a second chance soon, however if you're abusing .Failed, the OS could probably blacklist your application from using background fetches (and Apple could deny your app).
If your app isn't reporting .NewData, the OS will let your app do background work less often. I'm not saying this because I recommend that you just always report .NewData. You should definitely report accurately. The OS is very smart about scheduling work. If you're passing .NewData when there isn't new data, the OS will let your app work more often than it may need to, which will drain the user's battery quicker (and may lead to them uninstalling your app altogether).
There are other metrics involved in when your app gets to do background work however. The OS is very unlikely to let any app do background work while the user is actively using their device, and it is more likely to let apps do background work while the user is not using their device. Additionally, OS is more likely to do background work while it is on WiFi and while it is plugged into a charger of some sort.
The OS will also look at how regularly the user uses your app, or when they regularly use it. If the user uses your app every day at 6pm, and never at any other time, it's most likely that your app will always get an opportunity to do background work between 5:30pm and 6pm (just before the user will use the app) and never during any other part of the day. If the user very rarely uses your app, it may be days, weeks, or months between opportunities to work in the background.

Resources