backgroundTask not receiving BGTaskScheduler in Swift / SwiftUI - swift

I want to execute something in the background the check data every one hour.
For this, I am following the Apple Documentation video.
Here is the App Delegate code
import SwiftUI
import UserNotifications
import BackgroundTasks
#main
struct notificationtestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.onAppear(perform: {
AppCheckData()
})
}.backgroundTask(.appRefresh("notifyuser")) {
print("In Background Task")
//Check data
}
}
}
func AppCheckData() {
var startDate = Date()
let calendar = Calendar.autoupdatingCurrent
var checkTime = calendar.date(byAdding: .minute, value: 2, to: startDate)!
print(checkTime)
let notifyrequest = BGAppRefreshTaskRequest(identifier: "notifyuser")
notifyrequest.earliestBeginDate = checkTime
try? BGTaskScheduler.shared.submit(notifyrequest)
print("Done Scheduling")
}
For testing purposes, I changed addition of time to 2 minutes.
According to this code, it should print out the date after adding 2 minutes to the current date, then print out a confirmation that the BGTaskScheduler had been submitted. From there, after 2 minutes, it should print out the "In background task" in the console.
However, my app is not receiving the scheduled background task. I am not sure what I am missing here, is it the limits that Apple creates, or something else.
Please help

A BGAppRefreshTaskRequest is a request, and it is scheduled with earliestBeginDate. You are not requesting a specific time to run. You are indicating the earliest time it makes sense to run so that the OS doesn't call this unnecessarily.
As the docs note (emphasis mine):
The system decides the best time to launch your background task, and provides your app up to 30 seconds of background runtime.
In order to test your background operations, see Starting and Terminating Tasks During Development. IMO it is quite awkward, but it does work.
I want to execute something in the background the check data every one hour.
You will need to send a push notification in that case. This is not possible with BGTaskScheduler. As a rule, however, you should avoid this kind of polling and rather send the push notification only when there is new information. The point of BGAppRefreshTask is to periodically update your app's information at some point soon before the user is anticipated to need it so the user doesn't have to wait. It is not to facilitate polling.
There is no mechanism in iOS to allow you to do something at specific time intervals, without the user's intervention, except silent push notifications (and there are limits on those). This is by design. If you do not have a system for sending push notifications, then this is impossible, and you will need to redesign to not require it.

Related

Simple app to buzz the watch when it gets disconnected from phone

I would like a simple app that periodically checks the bluetooth connection between the phone and my watch (GTR 3 Pro), and buzzes the watch when it gets disconnected from my phone. This will be useful if I accidentally leave my phone somewhere and walk away from it, or my phone gets stolen or something like that.
Some previous amazfit watches had this feature built-in, but it doesn't seem to be available in my GTR 3 Pro right now. Thank you.
I made it, but only in ACTIVE application. So, if you open your mini-app, then it is possible to handle bluetooth status events (see the picture). Couldn't apply it in the background yet :-(.
You'll need to do a little hack to poll the Bluetooth connection to achieve the desired behavior, but first let's understand why.
Per ZeppOS architectural decision, your app will never run in background on the device. I believe this is for battery efficiency reasons or even available processing power.
With that in mind, We'll use hmApp.alarmNew and hmApp.alarmCancel in order to get it working, as follows:
Create a new page that will be responsible for checking the bluetooth connection, something like page/connectionCheck.js and declare it in your app.json target (you can also use the default index.js if you want)
In the onInit() of the page, register a new hmApp.alarm and cancel the existing ones if needed to avoid waking up the app unnecessarily
In the build() call, verify if it's connected to the cellphone using the hmBle.connectStatus() and alert the user.
Summarizing, it'll look like this:
(I'm using zeppOS API v1.0 here to make it work on all devices)
const WAKE_UP_INTERVAL_SECONDS = 30 // this value must be higher than the screen on time on app
const POLL_ALARM_PREF_ID = 'my_bluetooth_poll_alarm'
const vibrate = hmSensor.createSensor(hmSensor.id.VIBRATE)
Page({
onInit(param) {
vibrate.stop() // stop any vibration
vibrate.scene = 27 // set the vibration scene to 27 (1000ms vibration, high intensity)
// verify if this launch was triggered by an alarm or not
if(param === POLL_ALARM_PREF_ID) {
const existingAlarm = hmFS.SysProGetInt(POLL_ALARM_PREF_ID) // get existing alarm reference from system preferences
if(existingAlarm) {
// cancel existing alarm
hmApp.alarmCancel(existingAlarm)
}
}
// always create a new alarm to avoid alarm trigger while using the app
const alarm = hmApp.alarmNew({
file: 'pages/connectionCheck',
appid: 123123, // <YOU APP ID HERE>
delay: WAKE_UP_INTERVAL_SECONDS,
param: POLL_ALARM_PREF_ID
})
hmFS.SysProSetInt(POLL_ALARM_PREF_ID, alarm) // Save new alarm reference on system preferences
},
build() {
if(hmBle.connectStatus() === true) {
// Do something if already connected, maybe return to the home screen, exit the program or even turn the sreen off
hmApp.exit()
} else {
// show a message to the user / vibrate the watch
vibrate.start()
}
},
onDestroy() {
vibrate && vibrate.stop() // stop any vibration
}
})

Does X-CallBack work in Background thread?

I am constructing a xcallBack Url like this
"\(scheme)://x-callback-url\(path)?x-source=ApName&ResponseData=\(base64str)"
and after constructing i had open url method
UIApplication.shared.open(callbackUrlN!) { (result) in
if result {
print("success")
}
else {
print("cannot open url")
}
}
when my application is foreground state it is working fine, but when it enters into the background state instead of printing success i am getting "cannot open Url", Eventhough i had tried dispatch global background thread it is not working
Does anybody could help me out on this?
I suppose that you are trying to call this acton with or without time interval after app goes to background, but iOS lifecycle make impossible to interact with app. If i'm not mistaken system gives short amount of time to finish your tasks, when your app goes to background, but you can extend your application lifetime and finish all the stuff using backgroundTask (system gives you about 3 min and then kill it), but it's still wouldn't help you to use callback.

How to detect another instance of a Swift app is already running

I am trying to detect in my Swift app if an instance is already running, and if so, refrain the user from launching another instance. I am using NSRunningApplication class to detect such behavior but am having trouble getting it to work properly. I have read through some of the older posts and know that flock() is a lower-level way to detect so but I would prefer to "do it the Swift way" if possible. Here is a snippet of the code:
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Check if app has already been launched. If so, alert user and terminate the new session
let bundleID = Bundle.main.bundleIdentifier!
if NSRunningApplication.runningApplications(withBundleIdentifier: bundleID).count > 1 {
// warn and terminate
NSApp.terminate(nil)
}
// keep going and finish launching the app
...
}
It appears that the count returned is always of value 1 regardless of how many sessions I am trying to open. Any idea what goes wrong, or am I not calling this check in the right place?
Additionally, my app also allows the user to double-click on a file with my defined file extension to launch the app. I assume that if I can get the above working then it should also work for this scenario as well?
UPDATE:
After reviewing the launch behavior of my app using Activity Monitor, I think I have determined the source of the observed behavior.
Full disclosure, my app creates a new process to launch a console app, and will wait until this process completes and terminates. Updated code snippets below:
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Check if app has already been launched. If so, alert user and terminate the new session
let bundleID = Bundle.main.bundleIdentifier!
let num_instances = NSRunningApplication.runningApplications(withBundleIdentifier: bundleID).count
// Debug dialog box to see instances count
let alert = NSAlert()
alert.messageText = "msg"
alert.informativeText = "num instances running: \(num_instances)"
alert.alertStyle = NSAlert.Style.critical
alert.addButton(withTitle: "OK")
alert.runModal()
if num_instances > 1 {
// warn and terminate
NSApp.terminate(nil)
}
// keep going and finish launching the app
let task = Process()
task.launchPath = "myConsoleApp"
task.launch()
task.waitUntilExit()
let status = task.terminationStatus
if status == 0 {
// Done with the console app, exit
...
} else {
// something's wrong; report to user
...
}
}
Now, when the app is first launched, there are two processes created initially: mySwiftApp and myConsoleApp. However, with the presence of the debug dialog box, the mySwiftApp process is terminated after myConsoleApp has launched. As a result, the instance count for mySwiftApp is now 0. Therefore, subsequent launch of mySwiftApp will be successful and now there are two myConsoleApp processes running on the system. It is not clear to me why the presence of the alert box will terminate the mySwiftApp process but I suspect that it sends a termination signal to my app and changes its termination status.
If the debug dialog box is not there, due to the waitUnitlExit() setup, any attempt to launch the second instance will not be seen and essentially got dropped to the floor. Although this is the "desired" behavior since the second instance cannot be launched, it will be nice to have a way to notify the user.
Any idea what's the best way to do so?

Observable is not working when app is running in background in ionic?

When I do Login I am starting to check observable working but when application is in background it is not executing. I am using observable because after every 5 seconds refresh should happen. It seems it is not running in background. Any other solution to run the code after every 1 minute whether application is in background or foreground. Thanks in advance !
Loginsuccessfully
this.provider.startBackgroundRefreshInterval();
In service class below code added
startBackgroundRefreshInterval() {
var refreshInterval = localStore.getPersistedData(App.ISCHECKBOX_KEY);
this.timerSubscription = Observable.interval(parseInt(refreshInterval))
.subscribe((val) => {
console.log("Observable subscribed");
});
}
/**
* unsubscribe subscription
*/
unSubscribeOnehourSubscription() {
console.log("Observable Unsubscribed");
this.timerOneHourSubscription.unsubscribe();
}
Now refreshInterval is based on checkbox value selection
updateCheckboxList(value:any) {
this.service.unSubscribeSubscription();
this.service.startBackgroundRefreshInterval();
}
Once application will go in background Observable not calling
currently refreshInterval selected as 1 minute
what is other way to call functon continuosly in background in ionic ?
I am currently working on the same thing. While I can't say I know all the things about this, here is what I have found so far.
When your app goes to the background, it is essentially put to sleep. Meaning none of the code you are counting on, including intervals is going to execute.
Here are some solutions to this problem I have worked on.
1) BackgroundFetch as seen here in this cordova plugin https://github.com/transistorsoft/cordova-plugin-background-fetch and https://ionicframework.com/docs/native/background-fetch will in both android and ios attempt to run a callback every approximately 15 minutes (read the link for why that timing is a thing).
2) BackgroundMode as seen here https://ionicframework.com/docs/native/background-mode will put your app into a "stay active while in background" mode that you can control. This will work in keeping your app up and running even in background, but you may find that this is not acceptable to the stores (particularly might get rejected by ios store).
That's what I've found thus far. Hope it's helpful.

Check a web page periodically in background

I need to check periodically a web page.
My idea is to set a local notification periodically, for example every 20 minutes. When notification leaves, device should load the web page, check a condition and if the condition is true, device should rang, otherwise nothing.
(NOTIFICATION) -> (LOAD WEB PAGE) -> [VERIFY CONDITION]-|if true|-> (RING)
Is this technically possible to do? How can I load a web page while app isn't running?
My sketch of code was like this:
func check () {
pageCode = // find a way to load the page
let delayTime = dispatch_time(DISPATCH_TIME_NOW, Int64(NSEC_PER_MSEC * 100))
dispatch_after(delayTime, dispatch_get_main_queue()){
let readCode = self.pageCode.stringByEvaluatingJavaScriptFromString("document.getElementsByTagName('body')[0].outerHTML")
if letturaCodice?.containsString("Some text") == true {
ringPhone()
}
}
}
Not possible using UILocalNotification, you could do it with a remote notification though.
Apple has a guide here: See - "Using Push Notifications to Initiate a Download"
https://developer.apple.com/library/ios/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/BackgroundExecution/BackgroundExecution.html
A remote notification platform I've used before is called OneSignal, it's completely free and allows you to schedule notifications. https://onesignal.com
Edit:
Doesn't answer question about ringing, not sure about that bit sorry!
Option 1:
You could build the app to use background app refresh function that you could cause an alert if lets say you do not get an http status code of 200.
If you use this option you could build in the function to have a custom tone to play that you build into the app.
Option 2:
Use a server side script todo the checking and have it fire off a push notification. This method would depend on phone settings as to how the notification would function.
You can try to use Grand Central Dispatch to run a background process and inside it add notification observer.
UPD: Like this:
dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0)) {
() -> Void in
// catch notifications here
}