How to find if electron app is in foreground? - electron

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.

Related

ReactJs PWA not updating on iOS

I'm building a ReactJs PWA but I'm having trouble detecting updates on iOS.
On Android everything is working great so I'm wondering if all of this is related to iOS support for PWAs or if my implementation of the service worker is not good.
Here's what I've done so far:
Build process and hosting
My app is built using webpack and hosted on AWS. Most of the files (js/css) are built with some hash in their name, generated from their content. For those which aren't (app manifest, index.html, sw.js), I made sure that AWS serves them with some Cache-Control headers preventing any cache. Everything is served over https.
Service Worker
I kept this one as simple as possible : I didn't add any cache rules except precache for my app-shell:
workbox.precaching.precacheAndRoute(self.__precacheManifest || []);
Service-worker registration
Registration of the service worker occurs in the main ReactJs App component, in the componentDidMount() lifecycle hook:
componentDidMount() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js')
.then((reg) => {
reg.onupdatefound = () => {
this.newWorker = reg.installing;
this.newWorker.onstatechange = () => {
if (this.newWorker.state === 'installed') {
if (reg.active) {
// a version of the SW is already up and running
/*
code omitted: displays a snackbar to the user to manually trigger
activation of the new SW. This will be done by calling skipWaiting()
then reloading the page
*/
} else {
// first service worker registration, do nothing
}
}
};
};
});
}
}
Service worker lifecycle management
According to the Google documentation about service workers, a new version of the service worker should be detected when navigating to an in-scope page. But as a single-page application, there is no hard navigation happening once the app has been loaded.
The workaround I found for this is to hook into react-router and listen for route changes, then manually ask the registered service worker to update itself :
const history = createBrowserHistory(); // from 'history' node package
history.listen(() => {
if ('serviceWorker' in navigator) {
navigator.serviceWorker
.getRegistration()
.then((reg) => {
if (!reg) {
return null;
}
reg.update();
});
}
});
Actual behavior
Throwing a bunch of alert() everywhere in the code showed above, this is what I observe :
When opening the pwa for the first time after adding it to the homescreen, the service worker is registered as expected, on Android and iOS
While keeping the app opened, I deploy a new version on AWS. Navigating in the app triggers the manual update thanks to my history listener. The new version is found, installed in the background. Then my snackbar is displayed and I can trigger the switch to the new SW.
Now I close the app and deploy a new version on AWS. When opening the app again :
On Android the update is found immediately as Android reloads the page
iOS does not, so I need to navigate within the app for my history listener to trigger the search for an update. When doing so, the update is found
After this, for both OS, my snackbar is displayed and I can trigger the switch to the new SW
Now I close the app and turn off the phones. After deploying a new version, I start them again and open the app :
On Android, just like before, the page is reloaded which detects the update, then the snackbar is displayed, etc..
On iOS, I navigate within the app and my listener triggers the search for an update. But this time, the new version is never found and my onupdatefound event handler is never triggered
Reading this post on Medium from Maximiliano Firtman, it seems that iOS 12.2 has brought a new lifecycle for PWAs. According to him, when the app stays idle for a long time or during a reboot of the device, the app state is killed, as well as the page.
I'm wondering if this could be the root cause of my problem here, but I was not able to find anyone having the same trouble so far.
So after a lot of digging and investigation, I finally found out what was my problem.
From what I was able to observe, I think there is a little difference in the way Android and iOS handle PWAs lifecycle, as well as service workers.
On Android, when starting the app after a reboot, it looks like starting the app and searching an update of the service worker (thanks to the hard navigation occuring when reloading the page) are 2 tasks done in parallel. By doing that, the app have enough time to subscribe to the already existing service worker and define a onupdatefound() handler before the new version of the service worker is found.
On the other hand with iOS, it seems that when you start the app after a reboot of the device (or after not using it for a long period, see Medium article linked in the main topic), iOS triggers the search for an update before starting your app. And if an update is found, it will be installed and and enter its 'waiting' status before the app is actually started. This is probably what happens when the splashscreen is displayed...
So in the end, when your app finally starts and you subscribe to the already existing service worker to define your onupdatefound() handler, the update has already been installed and is waiting to take control of the clients.
So here is my final code to register the service worker :
componentDidMount() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js')
.then((reg) => {
if (reg.waiting) {
// a new version is already waiting to take control
this.newWorker = reg.waiting;
/*
code omitted: displays a snackbar to the user to manually trigger
activation of the new SW. This will be done by calling skipWaiting()
then reloading the page
*/
}
// handler for updates occuring while the app is running, either actively or in the background
reg.onupdatefound = () => {
this.newWorker = reg.installing;
this.newWorker.onstatechange = () => {
if (this.newWorker.state === 'installed') {
if (reg.active) {
// a version of the SW already has control over the app
/*
same code omitted
*/
} else {
// very first service worker registration, do nothing
}
}
};
};
});
}
}
Note :
I also got rid of my listener on history that I used to trigger the search for an update on every route change, as it seemed overkill.
Now I rely on the Page Visibility API to trigger this search every time the app gets the focus :
// this function is called in the service worker registration promise, providing the ServiceWorkerRegistration instance
const registerPwaOpeningHandler = (reg) => {
let hidden;
let visibilityChange;
if (typeof document.hidden !== 'undefined') { // Opera 12.10 and Firefox 18 and later support
hidden = 'hidden';
visibilityChange = 'visibilitychange';
} else if (typeof document.msHidden !== 'undefined') {
hidden = 'msHidden';
visibilityChange = 'msvisibilitychange';
} else if (typeof document.webkitHidden !== 'undefined') {
hidden = 'webkitHidden';
visibilityChange = 'webkitvisibilitychange';
}
window.document.addEventListener(visibilityChange, () => {
if (!document[hidden]) {
// manually force detection of a potential update when the pwa is opened
reg.update();
}
});
return reg;
};
As noted by Speckles (thanks for saving me the headache), iOS installs the new SW before launching the app. So the SW doesn't get a chance to catch the 'installing' state.
Work-around: check if the registration is in the waiting state then handle it.
I've made an (untested) example of handling this. - a mod to the default CRA SW.

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.

Delete Data from Web SQL database whenever phonegap Application's exit event occurs

I have two Questions:
What is the exit event of phone-gap Application? I haven't find it yet.
How to Delete Data from All the tables whenever exit event occurs? and insert it back from the web service whenever the deviceready() event of phonegap occurs.
Thanks in advance.
Regards.
I think you are looking for the pause event. The documentation reads like this:
The pause event fires when the native platform puts the application into the background, typically when the user switches to a different application.
You can attach a handler to this event like this:
document.addEventListener("pause", onPause, false);
Now the onPause method will be called everytime your app is going to the background and you can drop your tables there somewhat like this:
function onPause() {
db.transaction(function (tx) {
tx.executeSql("DROP TABLE foo",[],
function(tx,results){console.log("Successfully Dropped")},
function(tx,error){console.log("Could not delete")}
);
});
}
Assuming the db variable holds your WebSQL database and you're trying to drop the table named foo.

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