I want to perform some actions when my app goes to the background—e.g., when pressing the home button. (I am testing on an Android device.)
I tried the following in my app.component.ts:
this.platform.ready().then(() => {
this.platform.pause.subscribe(async () => {
alert("Pause event detected");
//Do stuff here
});
this.platform.resume.subscribe(async () => {
alert("Resume event detected");
//Do stuff here
});
…
I also tried:
App.getState().then((result) => {
alert("state active?" + result.isActive);
});
No listener is triggered when the app goes to background (e.g., by pressing the home button). But when I start the app again, all events are triggered (in this case, the alerts), including the platform.pause event.
I am using Ionic 9 and Capacitor.
Am I misunderstanding something? What could be the problem?
You can use the event listeners provided in Capacitor's App API.
// Import the relevant stuff from Capacitor
import { Plugins, AppState } from '#capacitor/core';
const { App } = Plugins;
Then in your AppComponent class
this.platform.ready().then(() => {
App.addListener('appStateChange', (state: AppState) => {
if (state.isActive) {
console.log('App has become active');
} else {
console.log('App has become inactive');
}
});
})
Note that you can test this in a desktop browser as well by switching to another tab.
Ok... things work. The problem was, that I had both variants in code active.
Related
Here is my sample React component:
const OwnerView = () => {
const [monthlyCharge, setMonthlyCharge] = useState(0)
useEffect(() => {
getPerMonthCharges(ownerPhoneNumber, vehicles.length)
}, [])
async function getPerMonthCharges(ownerPhoneNumber, noOfCars) {
console.log(`inside getPerMonthCharges`);
try {
const serviceProviderChargesDoc = await firestore().collection(`${serviceProviderId}_charges`).doc(`${ownerPhoneNumber}`).get()
if (serviceProviderChargesDoc?.data()?.chargesPerMonth > 0) {
setMonthlyCharge(serviceProviderChargesDoc?.data()?.chargesPerMonth)
return
}
} catch (error) {
console.log(`Error while fetching monthly charge ${error}`);
}
setMonthlyCharge(noOfCars * perMonthGeneralCharge)
console.log(`done with getPerMonthCharges`);
}
}
There is a possibility that OwnerView gets unmounted even before getPerMonthCharges() completes its execution. Therefore in case OwnerView gets unmounted I receive a warning that am doing state update on an unmounted component and this is a non-op. Can someone please highlight what is your observation and right way to write this piece of code?
There are many ways to address this
You can check if the component is still Mounted, a bit ugly approach I agree, but quite a standard one (I would just use something like useAsync from react-use, which essentially does the same, but hides the ugliness)
Move loading logic outside of UI and make part of the global state (Redux, MobX, Apollo, or any other state management library), it would be in lines of separation of concerns and should make your code more readable.
The worst would be to prevent your user from any actions, while content is loading - making your app seem clunky, but React would not complain anymore.
The closest to the right way would be 2, but this can sparkle religious debates and some witch-burning, which I'm not a fan of.
You can refer to this: https://reactjs.org/docs/hooks-effect.html#effects-with-cleanup
You can have a variable to keep track whether your component has unmount, let isMounted = true inside useEffect and set it to false as soon as the component is unmounted.
The code will be:
useEffect(() => {
let isMounted = true;
async function getPerMonthCharges(ownerPhoneNumber, noOfCars) {
console.log(`inside getPerMonthCharges`);
try {
const serviceProviderChargesDoc = await firestore().collection(`${serviceProviderId}_charges`).doc(`${ownerPhoneNumber}`).get()
if (serviceProviderChargesDoc?.data()?.chargesPerMonth > 0 && isMounted) { // add conditional check
setMonthlyCharge(serviceProviderChargesDoc?.data()?.chargesPerMonth)
return
}
} catch (error) {
console.log(`Error while fetching monthly charge ${error}`);
}
if (isMounted) setMonthlyCharge(noOfCars * perMonthGeneralCharge) // add conditional check
console.log(`done with getPerMonthCharges`);
}
getPerMonthCharges(ownerPhoneNumber, vehicles.length)
return () => { isMounted = false }; // cleanup toggles value, if unmounted
}, []);
I have a requirement of pausing video and some other actions to perform when user clicks on home button or switch to another app using swiper action. I looked at react-native AppState, but it is not calling the event listener when home button clicked on android for some devices. Is there any compatibility minimum version requirements for the react-native app state to work?
The code I tried is as below
useEffect(() => {
AppState.addEventListener("change", _handleAppStateChange);
return () => {
AppState.removeEventListener("change", _handleAppStateChange);
};
}, []);
const _handleAppStateChange = (nextAppState: any) => {
console.log(nextAppState);
};
The console is not printed when clicked on home button/Swiped to another app on some android devices. Is there anyway I can achieve this using react-native preferably without using any external libraries.
As for your requirement, in react-native, to detect is application is in foreground or in background, you don't need to add home-button listener, we can easily achieve this with the help of AppState, check below example,
_handleAppStateChange = (nextAppState: any) => {
if (this.state.appState === "active" && nextAppState === "background") {
// this condition calls when app goes in background mode
// here you can detect application is in background, and you can pause your video
} else if (this.state.appState === "background" && nextAppState === "active") {
// this condition calls when app is in foreground mode
// here you can detect application is in active state again,
// and if you want you can resume your video
}
this.setState({ appState: nextAppState });
};
You also need to attach AppState listener to application to detect application states, for example,
componentDidMount() {
AppState.addEventListener("change", _handleAppStateChange);
}
and detach app-state listener when application component unmount, for example,
componentWillUnmount {
AppState.removeEventListener("change", _handleAppStateChange);
}
Or if you are working with functional component you attach or detach app-state listener in useEffect, for example,
useEffect(() => {
AppState.addEventListener("change", _handleAppStateChange);
return () => {
AppState.removeEventListener("change", _handleAppStateChange);
};
}, []);
I have redirected my app from app.component.ts by setting rootpage as follow
rootPage:any=HomePage;
Later when I implemented login functionality I decided to redirect page as follow
rootPage:any;//=HomePage;
constructor(platform: Platform,
statusBar: StatusBar,
splashScreen: SplashScreen,
private storage: Storage,
private changeDetectorRef: ChangeDetectorRef) {
platform.ready().then(() => {
// Okay, so the platform is ready and our plugins are available.
// Here you can do any higher level native things you might need.
statusBar.styleDefault();
splashScreen.hide();
// Check session and redirect
storage.get('session').then((val) => {
if(val){
this.rootPage=HomePage;
}else{
this.rootPage=LoginPage;
}
});
});
}
But my page is not redirecting .
What I tried -
1. Page is redirecting when I am using this.changeDetectorRef.detectChanges() after setting rootpage, but my map in homepage is not loading when I am redirecting this way.
Is there any better solution to this problem?
The problem probably is that you assign the page to rootPage inside a promise. Because it is an async operation, the component renders before it is a assigned a root page. To fix it, you can simply use navCtrl.setRoot instead and wait to hide the splashscreen.
ngOnInit() {
platform.ready().then(() => {
// Check session and redirect
storage.get('session').then((val) => {
statusBar.styleDefault();
splashScreen.hide();
if (val) {
this.navCtrl.setRoot(HomePage);
} else {
this.navCtrl.setRoot(LoginPage);
}
});
});
}
Since you're not initializing root page anymore, I would also recommend doing this in ngOnInit instead of the constructor. It wasn't the cause of your problem, but it is recommended to do so.
Everything worked fine when I redirected user from ngOnInit() as follow
ngOnInit() {
// Check session and redirect
this.storage.get('session').then((val) => {
if(val){
this.rootPage=HomePage;
}else{
this.rootPage=LoginPage;
}
});
}
I am writing a react native app and need to implement keyboard events inside components.
I tried with following code and did not success.
import { DeviceEventEmitter } from 'react-native';
export default class KeyEvent {
static onKeyDownListener(cb) {
KeyEvent.removeKeyDownListener();
console.log('Key Down');
this.listenerKeyDown = DeviceEventEmitter.addListener('onKeyDown', cb);
}
static removeKeyDownListener() {
if (this.listenerKeyDown) {
this.listenerKeyDown.remove();
}
}
static onKeyUpListener(cb) {
console.log('Key up');
KeyEvent.removeKeyUpListener();
this.listenerKeyUp = DeviceEventEmitter.addListener('onKeyUp', cb);
}
static removeKeyUpListener() {
if (this.listenerKeyUp) {
this.listenerKeyUp.remove();
}
}
}
Call inside componentDidMount function relevant component as follows.
componentDidMount() {
console.log('componentDidMount');
// if you want to react to keyDown
KeyEvent.onKeyDownListener((keyCode) => {
console.log(`Key code pressed: ${keyCode}`);
});
// if you want to react to keyUp
KeyEvent.onKeyUpListener((keyCode) => {
console.log(`Key code pressed: ${keyCode}`);
});
}
componentWillUnmount() {
console.log('componentWillUnmount');
// if you are listening to keyDown
KeyEvent.removeKeyDownListener();
// if you are listening to keyUp
KeyEvent.removeKeyUpListener();
}
Can anyone help me to fix this or suggest any other way of doing this. I need to press a button on enter key down.
As per react native doc, you can attach below events to keyboard.
keyboardWillShow
keyboardDidShow
keyboardWillHide
keyboardDidHide
keyboardWillChangeFrame
keyboardDidChangeFrame
So if you want pressed key event then you need to attach event to input field like below.
<TextInput
onKeyPress={this.handleKeyDown}
placeholder="Enter text here..."
/>
handleKeyDown: function(e) {
if(e.nativeEvent.key == "Enter"){
dismissKeyboard();
}
},
I created a simple app using Meteor 1.3, which has only one method. It works like that: When a button is clicked, the method is invoked - it calculates a specific value and returns the result.
The app works perfectly on the localhost server, but when I launch it on my device with "meteor run android-device", it cannot access the method (simply opens the app, but nothing happens when I press a button.
Do you know how I could resolve this?
import { Template } from 'meteor/templating';
import { ReactiveVar } from 'meteor/reactive-var';
import { ReactiveDict } from 'meteor/reactive-dict';
import './main.html';
Template.check.onCreated(function checkOnCreated() {
this.state = new ReactiveDict();
});
Template.check.events({
'click .checkit'(event, instance) {
Meteor.call('code.check', function(error, result){
if(error){
console.log('Error from the client side!');
} else {
instance.state.set('fett', result.titles[0]);
}
});
},
});
Template.check.helpers({
fett() {
const instance = Template.instance();
if (instance.state.get('fett')) {
return instance.state.get('fett');
} else {
return 'Value still not known...'
}
},
});
Ensure your smartphone's WiFi is turned on and it connected to the same WiFi network as you computer where meteor app is running. Then everything should work fine.
Also, I recommend to use chrome://inspect feature (more info here) in order to debug your app on Android. Then, you will be able quickly investigate any problems with mobile app.