Nativescript: Continue code execution when app goes to background - ios

(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.

Related

Background Service in Appcelerator titanium for iOS

I am facing some problem in background service.I have registered the backgrond service like:var service = Ti.App.iOS.registerBackgroundService({url:'/bgservice.js'});
in bgservice.js :I actually want to check the DB(where the data execution time is>8mins) and trigger local notification.But it is not working.So tried a sample first like this,to see how much time the app is active in background:
var timer = setInterval(startsampletest, 6000);
startsampletest();
function startsampletest(){
count=count+1;
Ti.API.info("1.!!!!!*******startsampletest is called for"+count);
}
which gives me only 5 times every 6 seconds so it is executing only for 30 min(please correct me if I am wrong)But in axway documentation it says the bgservice will be active for 10 mins.
Can anyone pls help me on this.I want the app to be active in background for 10 mins.pls let me know if I have made any mistakes.
It is not guaranteed that a background task created by Ti.App.iOS.registerBackgroundService() will run up to 10 minutes,
see: https://titaniumsdk.com/api/titanium/app/ios/backgroundservice.html#background-service-limitations
... typically to no more than 10 minutes.
But more crucial is that
The OS may terminate the background service at any point to reclaim resources.
For longer background tasks under iOS you have to use
the Ti.URLSession module (com.appcelerator.urlSession).
In general, see https://titaniumsdk.com/guide/Titanium_SDK/Titanium_SDK_How-tos/Platform_API_Deep_Dives/iOS_API_Deep_Dives/iOS_Background_Services.html

How to find if electron app is in foreground?

I have a requirement where I want to perform an action inside the electron app only when it is in foreground.
It is an electron-react application. On mounting of a component, I want to schedule a periodic task which only runs when the app is in focus or is being used by the user. And pause the task when the app goes in background.
How can we detect the Electron app being in foreground?
You can use the isFocused method from BrowserWindow. To get your own BrowserWindow, you can do this :
remote.BrowserWindow.getAllWindows();
This will return all your app's windows. So to get the first / primary window, you could deconstruct the array like this :
const [yourBrowserWindow] = remote.BrowserWindow.getAllWindows();
console.log(yourBrowserWindow.isFocused());
You can use the focus / blur events on your BrowserWindow to be notified when the app is focused / unfocused.
mainWindow = new BrowserWindow({})
mainWindow.on('focus', () => {
console.log('window got focus')
})
mainWindow.on('blur', () => {
console.log('window blur')
})
You may want to update the component's state within these event handlers or use any other method to keep track of the current focus status.
This assumes that you have a single application window. If you have multiple, you'll need to extend the check to cover all of your windows.

Firebase A/B test not counting users when activation event is used on iOS

We're using the current version of the Firebase iOS framework (5.9.0) and we're seeing a strange problem when trying to run A/B test experiments that have an activation event.
Since we want to run experiments on first launch, we have a custom splash screen on app start that we display while the remote config is being fetched. After the fetch completes, we immediately activate the fetched config and then check to see if we received info about experiment participation to reconfigure the next UI appropriately. There are additional checks done before we determine that the current instance, in fact, should be part of the test, thus the activation event. Basically, the code looks like:
<code that shows splash>
…
[[FIRRemoteConfig remoteConfig] fetchWithExpirationDuration:7 completionHandler:^(FIRRemoteConfigFetchStatus status, NSError * _Nullable error) {
[[FIRRemoteConfig remoteConfig] activateFetched];
if (<checks that see if we received info about being selected to participate in the experiment and if local conditions are met for experiment participation>) {
[FIRAnalytics logEventWithName:#"RegistrationEntryExperimentActivation" parameters:nil];
<dismiss splash screen and show next UI screen based on experiment variation received in remote config>
} else {
<dismiss splash screen and show next UI screen>
}
}
With the approach above (which is completely straight-forward IMO) does not work correctly. After spending time with the debugger and Firebase logging enabled I can see in the log that there is a race-condition problem occurring. Basically, the Firebase activateFetched() call does not set up a "conditional user property experiment ID" synchronously inside the activateFetched call but instead sets it up some short time afterward. Because of this, our firing of the activation event immediately after activateFetched does not trigger this conditional user property and subsequent experiment funnel/goal events are not properly marked as part of an experiment (the experiment is not even activated in the first place).
If we change the code to delay the sending of the activation event by some arbitrary delay:
<code that shows splash>
…
[[FIRRemoteConfig remoteConfig] fetchWithExpirationDuration:7 completionHandler:^(FIRRemoteConfigFetchStatus status, NSError * _Nullable error) {
[[FIRRemoteConfig remoteConfig] activateFetched];
if (<checks that see if we received info about being selected to participate in the experiment and if local conditions are met for experiment participation>) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[FIRAnalytics logEventWithName:#"RegistrationEntryExperimentActivation" parameters:nil];
<dismiss splash screen and show next UI screen based on experiment variation received in remote config>
}
} else {
<dismiss splash screen and show next UI screen>
}
}
the conditional user property for the experiment gets correctly setup beforehand and triggered by the event (causing experiment activation and subsequent events being correctly marked as part of the experiment).
Now, this code obviously is quite ugly and prone to possible race-conditions. The delay of 0.5 seconds is conservatively set to hopefully be enough on all iOS devices but ¯_(ツ)_/¯. I've read the available documentation multiple times and tried looking at all available API methods with no success in figuring out what the correct point of starting to send events should be. If the activateFetched method uses an asynchronous process of reconfiguring internal objects, one would expect a callback method that indicates to the caller the point in time when everything is done reconfiguring and ready for further use by the application. Seems the framework engineers didn't anticipate a use-case when someone needs to send the activation event immediatly after remote config profile activation…
Has anyone else experienced this problem? Are we missing something in the API? Is there a smarter way of letting activateFetched finish its thing?
Hope some Firebase engineers can chime-in with their wisdom as well :)
Thanks

Adobe Air Application - Not in background and not in Foreground - iPhone

I have an adobe air application - AS3 for iOs and Android.
Whenever the user clicks the home button, and thus the application is now in the background, the application automatically stops, which is the expected behavior. Now, if the user is in the application, and he double clicks his home button, showing all the multiple windows, the application continues running, which is not what i want. How can i access that state ( Not Background, not foreground )? If i can access it, i would then put my pausing code into that state, but how can i access that particular state?
When the user clicks the home button the app is moved to the background and suspended. The app isn't closed. The OS can close the app to free memory. If your app is a memory hog you'll see this happening.
You use events dispatched by the NativeApplication object. Below is example code to listen and handle these events.
import flash.events.Event;
import flash.desktop.NativeApplication;
import flash.desktop.SystemIdleMode;
// create listeners to NativeApplication
private var naApplication: NativeApplication;
naApplication = NativeApplication.nativeApplication;
naApplication.addEventListener(Event.ACTIVATE, eActivate);
naApplication.addEventListener(Event.DEACTIVATE, eDeactivate);
naApplication.addEventListener(Event.EXITING, eExiting);
private function eActivate(e: Event): void {
// app has opened or resumed
application.systemIdleMode = SystemIdleMode.KEEP_AWAKE;
}
private function eDeactivate(e: Event): void {
// app is going to be moved to background
application.systemIdleMode = SystemIdleMode.NORMAL;
}
private function eExiting(e: Event): void {
// app is going to be closed by user or by the OS (usually to free up memory)
// do whatever exit code here then remove all listeners (to be clean don't rely on OS to close them)
application.removeEventListener(Event.ACTIVATE, eActivate);
application.removeEventListener(Event.DEACTIVATE, eDeactivate);
application.removeEventListener(Event.EXITING, eExiting);
application.systemIdleMode = SystemIdleMode.NORMAL;
removeEventListener(Event.ENTER_FRAME, eMainTimer);
}
The systemIdleMode and ENTER_FRAME are just examples of typical code. Let me know of any questions.

Settings alarms while app is closed

How can I set local notifications with out forcing user to open app.
I need my app set a local notification for sunrise and sunset, but I don't want to ask people open app.
I know I can have up to 64 notifications via scheduleLocalNotification, but I need to set it for a year so I should be able to run app in background and set alarms for future sunrises and sunsets in background.
The simple answer is you can't. Your app can't run whenever it wants in the background; it can't schedule a timer to wake itself up to post more notifications when they are due.
The only way you could come close to something like this is by having a server which send a background push notification to your app as a wake-up call when a new batch of 64 notifications are coming close to needed to be posted.
However this would be relying on the fact the user doesn't terminate your app. If the user does then you'd have to send a non-background push notification to the user and hope they click on it to launch your app.
Android Awareness API has recently announced new features that provide a simple solution for your use-case (that avoids you having to explicitly manage location request or computing sunrise times). The way to achieve what you're trying to do is to create and register a TimeFence specified relative to sunrise/sunset.
For example:
// Create TimeFence
AwarenessFence sunriseFence =
TimeFence.aroundTimeInstant(TimeFence.TIME_INSTANT_SUNRISE,
0, 5 * ONE_MINUTE_MILLIS);
// Register fence with Awareness.
Awareness.FenceApi.updateFences(
mGoogleApiClient,
new FenceUpdateRequest.Builder()
.addFence("fenceKey", sunriseFence, myPendingIntent)
.build())
.setResultCallback(new ResultCallback<Status>() {
#Override
public void onResult(#NonNull Status status) {
if (status.isSuccess()) {
Log.i(TAG, "Fence was successfully registered.");
} else {
Log.e(TAG, "Fence could not be registered: " + status);
}
}
});
You will get callbacks when the fence evaluates to TRUE at sunrise, and when it evaluates back to FALSE at 5-min after sunrise.
Please check Fence API code snippets docs for how to add your custom app logic.

Resources