I am trying to show a large list in react native - Expo. When I lock the screen while data loading via API. App State changed from "Active" to "Inactive".
When I return to an active state, no data has been loaded. The App processes are stopped. ListEmptyComponent renders the ActivityIndicator. It is loading indefinitely. It occurs only in android build.
I tried to recall the API by AppState.
const handleAppStateChange = nextAppState => {
console.log(nextAppState);
if (nextAppState === 'active') {
console.log(JSON.stringify(Store.apiCall));
// "Store.apiCall" has data about last API Call and its status.
if (Store.apiCall.status === codes.PENDING || Store.apiCall.status === codes.ERROR) {
api(Store.apiCall.payload);
}
}
setAppState(nextAppState);
};
Still it doesn't works..
This happening because of the battery optimization features of newer Android versions. The only fix would be to ask the user to disable the battery optimization for your app by redirecting them to your app settings or you can use a node package like this one:
https://www.npmjs.com/package/react-native-disable-battery-optimizations
Also, if you dont want your device to sleep if its in active state you can use something like this:
https://www.npmjs.com/package/react-native-keep-awake
Hope this helps :)
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 }
Can a "pure" HTML5/Javascript (progressive) web application intercept the mobile device back button in order to avoid the App to exit?
This question is similar to this one but I want to know if it is possible to achieve such behavior without depending on PhoneGap/Ionic or Cordova.
While the android back button cannot be directly hooked into from within a progressive web app context, there exists a history api which we can use to achieve your desired result.
First up, when there's no browser history for the page that the user is on, pressing the back button immediately closes the app.
We can prevent this by adding a previous history state when the app is first opens:
window.addEventListener('load', function() {
window.history.pushState({}, '')
})
The documentation for this function can be found on mdn:
pushState() takes three parameters: a state object, a title (which is currently ignored), and (optionally) a URL[...] if it isn't specified, it's set to the document's current URL.
So now the user has to press the back button twice. One press brings us back to the original history state, the next press closes the app.
Part two is we hook into the window's popstate event which is fired whenever the browser navigates backwards or forwards in history via a user action (so not when we call history.pushState).
A popstate event is dispatched to the window each time the active history entry changes between two history entries for the same document.
So now we have:
window.addEventListener('load', function() {
window.history.pushState({}, '')
})
window.addEventListener('popstate', function() {
window.history.pushState({}, '')
})
When the page is loaded, we immediately create a new history entry, and each time the user pressed 'back' to go to the first entry, we add the new entry back again!
Of course this solution is only so simple for single-page apps with no routing. It will have to be adapted for applications that already use the history api to keep the current url in sync with where the user navigates.
To do this, we will add an identifier to the history's state object. This will allow us to take advantage of the following aspect of the popstate event:
If the activated history entry was created by a call to history.pushState(), [...] the popstate event's state property contains a copy of the history entry's state object.
So now during our popstate handler we can distinguish between the history entry we are using to prevent the back-button-closes-app behaviour versus history entries used for routing within the app, and only re-push our preventative history entry when it specifically has been popped:
window.addEventListener('load', function() {
window.history.pushState({ noBackExitsApp: true }, '')
})
window.addEventListener('popstate', function(event) {
if (event.state && event.state.noBackExitsApp) {
window.history.pushState({ noBackExitsApp: true }, '')
}
})
The final observed behaviour is that when the back button is pressed, we either go back in the history of our progressive web app's router, or we remain on the first page seen when the app was opened.
#alecdwm, that is pure genius!
Not only does it work on Android (in Chrome and the Samsung browser), it also works in desktop web browsers. I tested it on Chrome, Firefox and Edge on Windows, and it's likely the results would be the same on Mac. I didn't test IE because eew. Even if you're mostly designing for iOS devices that have no back button, it's still a good idea to ensure that Android (and Windows Mobile... awww... poor Windows Mobile) back buttons are handled so that the PWA feels much more like a native app.
Attaching an event listener to the load event didn't work for me, so I just cheated and added it to an existing window.onload init function I already had anyhow.
Keep in mind that it might frustrate users who would actually want to really Go Back to whatever web page they were looking at before navigating to your PWA while browsing it as a standard web page. In that case, you can add a counter and if the user hits back twice, you can actually allow the "normal" back event to happen (or allow the app to close).
Chrome on Android also (for some reason) added an extra empty history state, so it took one additional Back to actually go back. If anyone has any insight on that, I'd be curious to know the reason.
Here's my anti-frustration code:
var backPresses = 0;
var isAndroid = navigator.userAgent.toLowerCase().indexOf("android") > -1;
var maxBackPresses = 2;
function handleBackButton(init) {
if (init !== true)
backPresses++;
if ((!isAndroid && backPresses >= maxBackPresses) ||
(isAndroid && backPresses >= maxBackPresses - 1)) {
window.history.back();
else
window.history.pushState({}, '');
}
function setupWindowHistoryTricks() {
handleBackButton(true);
window.addEventListener('popstate', handleBackButton);
}
This approach has a couple of improvements over existing answers:
Allows the user to exit if they press back twice within 2 seconds: The best duration is debatable but the idea of allowing an override option is common in Android apps so it's often the correct approach.
Only enables this behaviour when in standalone (PWA) mode: This ensures the website keeps behaving as the user would expect when within an Android web browser and only applies this workaround when the user sees the website presented as a "real app".
function isStandalone () {
return !!navigator.standalone || window.matchMedia('(display-mode: standalone)').matches;
}
// Depends on bowser but wouldn't be hard to use a
// different approach to identifying that we're running on Android
function exitsOnBack () {
return isStandalone() && browserInfo.os.name === 'Android';
}
// Everything below has to run at page start, probably onLoad
if (exitsOnBack()) handleBackEvents();
function handleBackEvents() {
window.history.pushState({}, '');
window.addEventListener('popstate', () => {
//TODO: Optionally show a "Press back again to exit" tooltip
setTimeout(() => {
window.history.pushState({}, '');
//TODO: Optionally hide tooltip
}, 2000);
});
}
i did not want to use native javascript functions to handle this inside of a react app, so i scoured solutions
that used react-router or react-dom-router, but in the end, up against a deadline, native js is
what got it working. Added the following listeners inside inside componentDidMount() and setting the history
to an empty state
window.addEventListener('load', function() {
window.history.pushState({}, '')
})
window.addEventListener('popstate', function() {
window.history.pushState({}, '')
})
this worked fine on the browser, but was still not working in the PWA on mobile
finally a colleague found out that triggering the history actions via code is what somehow initiated the listeners
and voila! everything fell in place
window.history.pushState(null, null, window.location.href);
window.history.back();
window.history.forward();
In my case, I had a SPA with different drawers on that page and I want them to close when User hits back button..
you can see different drawers in the image below:
I was managing states(eg open or close) of all drawers at a central location (Global state),
I added the followin code to a useEffect hook that runs only once on loading of web app
// pusing initial state on loading
window.history.pushState(
{ // Initial states of drawers
bottomDrawer,
todoDetailDrawer,
rightDrawer,
},
""
);
window.addEventListener("popstate", function () {
//dispatch to previous drawer states
// dispatch will run when window.history.back() is executed
dispatch({
type: "POP_STATE",
});
});
and here is what my dispatch "POP_STATE" was doing,
if (window.history.state !== null) {
const {
bottomDrawer,
rightDrawer,
todoDetailDrawer,
} = window.history.state; // <- retriving state from window.history
return { // <- setting the states
...state,
bottomDrawer,
rightDrawer,
todoDetailDrawer,
};
It was retriving the last state of drawers from window.history and setting it to current state,
Now the last part, when I was calling window.history.pushState({//object with current state}, "title", "url eg /RightDrawer") and window.history.back()
very simple,
window.history.pushState({//object with current state}, "title", "url eg /RightDrawer") on every onClick that opens the drawer
&
window.history.back() on every action that closes the drawer.
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:
I have searched all over the web and found different ways of closing a PhoneGap App. I tested all of them and none work. At least on Android.
Question:
Is it possible (By Feb 2014) to have a close button in a PhoneGap App on Android?
Thanks
This doesn't work:
function CloseApp() {
if (confirm('Close this App?')){
if (navigator.app) {
navigator.app.exitApp();
}else if (navigator.device) {
navigator.device.exitApp();
}
}
}
Is
navigator.app.exitApp()
really killing/closing the android app with phonegap?
I use cordova and have the same issue. Above mentioned code is just putting the app into background - I checked the running tasks (android task manager) after above code got executed by the app.
I am confused on why you want a button to close the app. Android already has a back button when clicked enough times will take the user back to the phone's main screen. There is also a home button that takes the user out of an app. Once, out of the app the user can "kill" the app through a task manager.
navigator.app.exitApp()
works and I use it in all my cordova apps. Check the rest of your code.
As ejwill said, having a "close" button is a bad idea. On Android I call exitApp when the user is the home page of my app and he presses the backbutton:
function onDeviceReady() {
document.addEventListener("backbutton", onBackKey, false);
}
function onBackKey( event ) {
var l = window.location.toString();
var parts = l.split('#/'); // this works only if you are using angularjs
var page = parts[1];
if (page == 'home') {
navigator.app.exitApp();
} else {
// do something else... one option is:
navigator.app.backHistory();
}
}
My 2c.