Kill Background Job on Active App State React Native - android

I am having problems stopping a background action running in my android app. Basically, the way I am using it in this way:
1- I use an appState hook that lets me know when the app is in foreground or background.
2- When the appState Turns to 'background' I start a background job using BackgroundJob.start() from 'react-native-background-action'.
3-When the appState turns to 'active' (comes back to foreground). I use BackgroundJob.stop()
The problem happens when I kill the app, cause when I kill the app and I lose the reference from the background job I started before, so every time kill the app I add another job to the background making the app work bad, so my question basically is:
Did you face the same problem? If you did how did you solve it? If not, do you have another way to solve the problem?
I use the background job cause my app has a speech recognition functionality that gets triggered by a wake word, and it needs to listen when the app is in the background.
useEffect(() => {
const startBackgroundListening = async () => {
await BackgroundJob.start(backgroundListening, backgroundOptions);
};
const stopPorcupine = async () => {
await porcupineRef.current?.stop();
porcupineRef.current?.delete();
};
if (currentAppState === 'background') {
if (isLoggedIn) {
if (isNotificationShowed === false) {
handleNotification(
i18n.t('closingWarning'),
i18n.t('closingWarningText'),
);
uiStore.updateIsNotificationShowed(true);
}
if ((isRecord || isCall || isSms || isAlarm) && isBackgroundListeningRunning.current === false) {
//start background listenning
startBackgroundListening();
isBackgroundListeningRunning.current = true;
}
}
} else if (currentAppState === 'active') {
//stop and delete background listenning in case there is one
stopPorcupine();
BackgroundJob.stop();
isBackgroundListeningRunning.current = false;
}
}, [currentAppState]);

The react-native-background-action documentation (https://www.npmjs.com/package/react-native-background-actions) states that "If you call stop() on background no new tasks will be able to be started! Don't call .start() twice, as it will stop performing previous background tasks and start a new one. If .start() is called on the background, it will not have any effect." If your concern was that multiple Background tasks would run simultaneously and cause bad app performance, the documentation shows calling .start() stops any previous background tasks, so you should be fine!

Related

Persistent background worker in Android Flutter App that fetch Web API every 30 seconds

I'm developing Android only app on Flutter. My app needs to call some Web APIs in the background every 30 seconds. The background worker should keep running even if the user or Android OS closes my app's main activity.
I know that doing something in the background too frequently will drain the battery very quickly and it is considered bad practice. But still, I need to do it every 30 seconds.
The target platform of my app is Android 7.0 or higher.
Any ideas how can I implement this in Flutter?
Try to add Timer I have already use Timer (below code) in my app my problem is solved
Timer: A count-down timer that can be configured to fire once or
repeatedly.
Declare Timer inside your class
Timer timer;
int counter = 0;
Create initState() method for timer
#override
void initState() {
super.initState();
timer = Timer.periodic(
Duration(seconds: 30),
(Timer t) => addValue(),
);
}
Create function for addValue it is increased
void addValue() {
setState(() {
counter++;
});
}
your page auto reload every 30 seconds
OR you just use setState method inside your API call function like below
setState(() { });

How to know an action is done globally react native

This is really common that i want to know an action is done and then do sth after that. for this we usually use events but i don't know how to use it in my case.
my scenario: There is an SplashScene which shows some animations for a constant time, after that time i navigate to my HomeScene. there are some other initializations which i don't want to be done unless SplashScene is gone and we r in HomeScene.
those initializations are in App component. and what im doing is that because the SplashScene animation time is constant i use a timeout to init things.
// Constants.ts
export const GlobalStaticData = {
initialDuration: 5000 // ms
}
// App
public componentDidMount() {
setTimeout(() => {
// initialize things
}, GlobalStaticData.initialDuration) // show dialogs after splash loading time
}
// SplashScene
private onAnimationEnd = () => {
NavigationActions.navigate(HomeScene)
}
but i know this is not good at all, i already experience sometimes which timing doesn't work as expected and things get initialized when app is still in SplashScene.
i was thinking for way to use events but i dont know how do that. what i want to listen to a value e.g called isSplashLoadCompleted in App component and in splash change that value when its works are done. then in App its event listener is called and initialing get started.
You can write a basic event system to subscribe to an event and then emit the event when certain thing happened on the other page:
const subscribers = {}
// Subscribe to loading your target page here Ex subsciber('home_load',()=>{ Your Logic })
const subscribe = (event,callback)=>{
if(subscribers[event] == null){ subscribers[event] = {}}
subscribers[event].push(callback)
}
const unSubscribe = (event,callback)=>{
....
}
// Call this inside your target page componentDidMount Ex: emitEvent('home_load',Some data or null)
const emitEvent = (event,data)=>{
if(subscribers[event]!=null && subscibers[event].length > 0){
for(const cb of subscribers[event]){
if(cb != null){cb()}
}
}
}
Sounds like the you want to trigger specific code when different scenes are loaded. I would recommend moving your animation/initialization logic into the compomentDidMount() for the respective scenes - SplashScene and HomeScene.
You're running into issues because your animation/initialization code is completely decoupled from your scenes. Couple the logic to the componentDidMount for these scenes and you won't have to worry about timing issues.

React Native: AppState.addEventListener registering duplicate events on resume when tapping a notification

I have code which I want to run when my app resumes from the background. To this end, I register an event using AppState.addEventListener().
const handleAppStateChange = () => {
console.log('Do stuff')
}
constructor(props: Props) {
super(props)
AppState.addEventListener('change', this.handleAppStateChange)
}
componentWillUnmount() {
AppState.removeEventListener('change', this.handleAppStateChange)
}
When I normally exit the app and resume, it prints 'do stuff' as expected, however (and here is the issue), when the app is minimised and I tap on a notification, the app will print 'Do stuff' twice.
I have figured out it's because when I tap a notification, it seems to re-run the app (including the constructor part), which means it creates a second event listener...
So, does anyone know either why it's doing that when tapping on a notification and if I can prevent it (using react-native-push-notification plugin), or alternatively if there is a way I can ensure that duplicate events are not registered?
This is happening on my Android physical device, not sure if it's an iOS issue as well, but just thought I would check if anyone knew if this was possible)
So after much agonising, I have managed to come up with a solution. It's not great, but gets the job done for now.
if (AppState._eventHandlers.change.size === 0) {
AppState.addEventListener('change', this.handleAppStateChange)
}
I feel the AppState page https://facebook.github.io/react-native/docs/appstate, is woefully inadequate, and that is why the only option I could see right now is this private method. Will try and follow up with the team if this could be improved, because it would make sense that in some cases you don't want duplicated events to be registered.
The answer above is still the correct one, however it might cause crashes on iOS 13. It works fine on Android.
My suggestion is to explicitly check for Android:
if (Platform.OS === 'android' && AppState._eventHandlers.change.size === 0)
When I used
if (AppState._eventHandlers.change.size === 0) {
AppState.addEventListener('change', this.handleAppStateChange)
}
I got this error :
Now I have this tsc error: error TS2339: Property '_eventHandlers' does not exist on type 'AppStateStatic'.
So,I rollbacked it and then I resolved AppState.addEventListener registering duplicate events on resume when tapping a notification, by changing arrow function to function in this way:
AppState.addEventListener('change', controlSocketConnection);
function controlSocketConnection() { // some code }

Event to handle app is closed (Ionic)

I'm running a process in background and I need to kill it before the app is closed from recent apps (swiping to right whit the square button).
The app use a plugin to get the current location https://github.com/mauron85/cordova-plugin-background-geolocation and another to make the task in background. The background plugin calls to the location plugin, and if you swipe the app during the location is being stored the notification keeps in the drawer.
if(window.cordova && window.cordova.plugins.Keyboard) {
cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
cordova.plugins.backgroundMode.enable();
cordova.plugins.backgroundMode.onactivate = function () {
if(promise != []){
$interval.cancel(promise);
}
promise = $interval(geolocation_function, 25000);
};
cordova.plugins.backgroundMode.ondeactivate = function(){
if(promise != []){
$interval.cancel(promise);
}
promise = $interval(geolocation_function, 25000);
}
}
The geolocation function call to the background plugin. I'm using this https://github.com/katzer/cordova-plugin-background-mode and I don't know how to control this.
Is there any function I can use for that? Thanks.
I used this event to close the service before the app is closed
.run(function(){
window.onunload = function(){
backgroundGeoLocation.stop();
}
})
You can put this three codes in your controller:
$scope.$on('$ionicView.beforeLeave', YOURFUNCTION);
to listen before user leave the page.
$ionicPlatform.on('pause', YOURFUNCTION);
$ionicPlatform.on('resume', YOURFUNCTION);
to listen when user pause and resume the app:

What happens to JavaScript execution (settimeout, etc.) when iPhone/Android goes to sleep?

I have a jQuery Mobile web app which targets iOS and Android devices. A component of the application is a background task, which periodically checks for a.) changes to local data and b.) connectivity to the server. If both are true, the task pushes the changes.
I'm using a simple setTimeout()-based function to execute this task. Each failure or success condition calls setTimeout() on the background task, ensuring that it runs on 30 second intervals. I update a status div with the timestamp of the last task runtime for debugging purposes.
In any desktop browser, this works just fine; however, on iOS or Android, after some period of time, the task stops executing. I'm wondering if this is related to the power conservation settings of the devices--when iOS enters stand-by, does it terminate JavaScript execution? That is what appears to happen.
If so, what is the best way to resume? Is there an on-wake event which I can hook into? If not, what other options are there which don't involve hooking into events dependent on user interaction (I don't want to bind the entire page to a click event just to restart the background task).
Looks like Javascript execution is paused on MobileSafari when the browser page isn't focused. It also seems if setInterval() events are late, they are simply fired as soon as the browser is focused. This means we should be able to keep a setInterval() running, and assume the browser lost/regained focus if the setInterval function took much longer than usual.
This code alerts after switching back from a browser tab, after switching back from another app, and after resuming from sleep. If you set your threshold a bit longer than your setTimeout(), you can assume your timeout wouldn't finish if this fires.
If you wanted to stay on the safe side: you could save your timeout ID (returned by setTimeout) and set this to a shorter threshold than your timeout, then run clearTimeout() and setTimeout() again if this fires.
<script type="text/javascript">
var lastCheck = 0;
function sleepCheck() {
var now = new Date().getTime();
var diff = now - lastCheck;
if (diff > 3000) {
alert('took ' + diff + 'ms');
}
lastCheck = now;
}
window.onload = function() {
lastCheck = new Date().getTime();
setInterval(sleepCheck, 1000);
}
</script>
Edit: It appears this can sometimes trigger more than once in a row on resume, so you'd need to handle that somehow. (After letting my android browser sleep all night, it woke up to two alert()s. I bet Javascript got resumed at some arbitrary time before fully sleeping.)
I tested on Android 2.2 and the latest iOS - they both alert as soon as you resume from sleep.
When the user switches to another app or the screen sleeps, timers seem to pause until the user switches back to the app (or when the screen awakens).
Phonegap has a resume event you can listen to instead of polling for state (as well as a pause event if you want to do things before it is out of focus). You start listening to it after deviceReady fires.
document.addEventListener("deviceready", function () {
// do something when the app awakens
document.addEventListener('resume', function () {
// re-create a timer.
// ...
}, false);
}, false);
I use angular with phonegap and I have a service implemented that manages a certain timeout for me but basically you could create an object that sets the timer, cancels the timer and most importantly, updates the timer (update is what is called during the 'resume' event).
In angular I have a scopes and root scope that I can attach data to, my timeout is global so I attach it to root scope but for the purpose of this example, I'll simply attach it to the document object. I don't condone that because you need should apply it to some sort of scope or namespace.
var timeoutManager = function () {
return {
setTimer: function (expiresMsecs) {
document.timerData = {
timerId: setTimeout(function () {
timeoutCallback();
},
expiresMsecs),
totalDurationMsecs: expiresMsecs,
expirationDate: new Date(Date.now() += expiresMsecs)
};
},
updateTimer: function () {
if (document.timerData) {
//
// Calculate the msecs remaining so it can be used to set a new timer.
//
var timerMsecs = document.timerData.expirationDate - new Date();
//
// Kill the previous timer because a new one needs to be set or the callback
// needs to be fired.
//
this.cancelTimer();
if (timerMsecs > 0) {
this.setTimer(timerMsecs);
} else {
timeoutCallback();
}
}
},
cancelTimer: function () {
if (document.timerData && document.timerData.timerId) {
clearTimeout(document.timerData.timerId);
document.timerData = null;
}
}
};
};
You could have the manager function take a millisecond parameter instead of passing it into set, but again this is modeled somewhat after the angular service I wrote. The operations should be clear and concise enough to do something with them and add them to your own app.
var timeoutCallback = function () { console.log('timer fired!'); };
var manager = timeoutManager();
manager.setTimer(20000);
You will want to update the timer once you get the resume event in your event listener, like so:
// do something when the app awakens
document.addEventListener('resume', function () {
var manager = timeoutManager();
manager.updateTimer();
}, false);
The timeout manager also has cancelTimer() which can be used to kill the timer at any time.
You can use this class github.com/mustafah/background-timer based on #jlafay answer , where you can use as follow:
coffeescript
timer = new BackgroundTimer 10 * 1000, ->
# This callback will be called after 10 seconds
console.log 'finished'
timer.enableTicking 1000, (remaining) ->
# This callback will get called every second (1000 millisecond) till the timer ends
console.log remaining
timer.start()
javascript
timer = new BackgroundTimer(10 * 1000, function() {
// This callback will be called after 10 seconds
console.log("finished");
});
timer.enableTicking(1000, function(remaining) {
// This callback will get called every second (1000 millisecond) till the timer ends
console.log(remaining);
});
timer.start();
Hope it helps, Thank you ...
You should use the Page Visibility API (MDN) which is supported just about everywhere. It can detect if a page or tab has become visible again and you can then resume your timeouts or carry out some actions.

Categories

Resources