handling push notifications in android expo - react-native

I tried adding push notification with the expo, I am able to push notification from the server but even though the app is in foreground notification handler is not triggered,
I have replicated the documentation example from expo-notifications
export default function App(props) {
const [expoPushToken, setExpoPushToken] = useState('');
const [notification, setNotification] = useState(false);
const notificationListener = useRef();
const responseListener = useRef();
useEffect(() => {
registerForPushNotificationsAsync().then(token => setExpoPushToken(token));
notificationListener.current = Notifications.addNotificationReceivedListener(notification => {
setNotification(notification);
});
responseListener.current = Notifications.addNotificationResponseReceivedListener(response => {
console.log(respone)
});
}, []);

registerForPushNotificationsAsync() should be an async function that would be in your App Component here. In the example from expo it is added bellow the component as convention.
Further more the 'response' would only be logged after you press the push notification.
The other listener notificationListener is setting a state in your example here, so is not being logged maybe.
If you are using a bare workflow also add useNextNotificationsApi: true to app.json in your android object.

Related

Error: Invalid hook call. When trying to call hook... Followed suggested resolutions

I'm getting this error when trying to use a hook
Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.
I checked and I do not have mismatching version and I do not have more than one copy of react in the same app
this is how I'm trying to use my hook.
This is how I'm trying to use my hook:
import messaging from '#react-native-firebase/messaging'
const Stack = createStackNavigator()
if (!firebase.apps.length) {
firebase.initializeApp(FIREBASE_CONFIG)
}
export default function App() {
// Request Push Notification permission from device.
const requestPermission = async () => {
const authStatus = await messaging().requestPermission();
const enabled =
authStatus === messaging.AuthorizationStatus.AUTHORIZED || authStatus === messaging.AuthorizationStatus.PROVISIONAL;
if (enabled) {
console.log('Authorization status:', authStatus);
}
};
useEffect(() => {
requestPermission();
}, []);
return (
...
...
...
I believe I'm using it correctly.. I'm not sure what else to check.
Your useEffect code is inside of the inner function you need to place it outside of the function and make sure your useEffect is inside the main function.
Your Updated code Looks like this.
export default function App() {
useEffect(() => {
requestPermission();
}, []);
// Request Push Notification permission from device.
const requestPermission = async () => {
const authStatus = await messaging().requestPermission();
const enabled =
authStatus === messaging.AuthorizationStatus.AUTHORIZED || authStatus === messaging.AuthorizationStatus.PROVISIONAL;
if (enabled) {
console.log('Authorization status:', authStatus);
}
};
return (
...
...
It's works fine.

React native Connection status on background / locked screen NetInfo

Description:
When the app is running in a background state or Locked, When the user unlock or foreground the app, useNetInfo(); hook return as isConnected as false. Even I tried to re-fetch the state still using NetInfo.fetch() return the same state.
It's happening in Android real device connected to Wifi
Package Name:
"#react-native-community/netinfo": "^7.1.2",
Code:
const netInfo = useNetInfo();
const [show, setShow] = useState(false);
useEffect(() => {
setShow(!(netInfo.isConnected && netInfo.isInternetReachable));
}, [netInfo]);
useEffect(() => {
fetchConnection();
}, []);
const fetchConnection = () => {
NetInfo.fetch().then((state: any) => {
setShow(!(state.isConnected && state.isInternetReachable));
});
};
I fixed this issue by reverting the package version into "#react-native-community/netinfo": "5.9.7",
Also, change the androidXCore version into 1.6.0. Now it's working as expected.
Reason:
Due to the hibernation features changes in androidXCore version 1.7.0. Netinfo does not return the state properly if the app is in a hibernation state. Please fix this issue in the upcoming release. Thanks.
add navigation focus event listener and check for network update whenever the screen is focused.
Please try this, it's from the package npm site
useEffect(() => {
const subAppState = AppState.addEventListener("change", async (nextAppState) => {
if (IS_IOS_DEVICE && nextAppState=='active') {
let newNetInfo = await NativeModules.RNCNetInfo.getCurrentState('wifi');
//your code here
}
});
const unsubNetState = NetInfo.addEventListener(state => {
//your code here
});
return () => {
if (subAppState) {
subAppState.remove();
}
unsubNetState();
};
},[]);

Ionic React - Navigate to a page when an FCM notification is tapped

I am implementing FCM notifications in an Ionic React application. I am having trouble navigating to another page to display the notification details.
I have created a FCMService class in my react App, and initialising this in the index.ts file.
// FCMService.ts
export default class FCMService {
public static Instance: FCMService;
private _store: Store<IAppState>;
constructor(store: Store<IAppState>) {
this._store = store;
}
public static Initalise(store: Store<IAppState>) {
if (!FCMService.Instance) {
FCMService.Instance = new FCMService(store);
FCMService.Instance.InitaliseFCM();
FCMService.Instance._store.subscribe(() => { console.log(store.getState()) });
} else {
console.debug("FCM service already intialised. Please use FCMService.Instance");
}
}
private InitaliseFCM() {
// Request permission to use push notifications
// iOS will prompt user and return if they granted permission or not
// Android will just grant without prompting
PushNotifications.requestPermission().then(result => {
console.log(result);
if (result.granted) {
// Register with Apple / Google to receive push via APNS/FCM
PushNotifications.register();
} else {
// Show some error
}
});
// On success, we should be able to receive notifications
PushNotifications.addListener('registration', (token: PushNotificationToken) => {
console.log(token);
localStorage.setItem("FCM_TOKEN", token.value);
}
);
// Some issue with our setup and push will not work
PushNotifications.addListener('registrationError',
(error: any) => {
console.log(error);
}
);
// Show us the notification payload if the app is open on our device
PushNotifications.addListener('pushNotificationReceived',
(notification: PushNotification) => {
console.log(notification);
let data = notification.notification.data as INotificationData;
}
);
// Method called when tapping on a notification
PushNotifications.addListener('pushNotificationActionPerformed',
(notification: PushNotificationActionPerformed) => {
console.log(notification);
let data = notification.notification.data as INotificationData;
this._store.dispatch(setNotificationActionCreator(data));
}
);
}
}
and then the index.ts
const store = configureStore();
interface MainProps {
store: Store<IAppState>;
}
FCMService.Initalise(store);
ReactDOM.render(<Provider store={store}><App /> </Provider>, document.getElementById('root'));
serviceWorker.unregister();
I even tried using the Redux store to save the notification on Tap - and then that would publish the notification change event (which might of worked - if I could access the useHistory() hook in the App.tsx file)
This was my attempt at navigating via Redux store in App.tsx
const App: React.FC<IProps> = ({ getCompanies, getUser, notification }) => {
console.log('app');
console.log(process.env);
const history = useHistory();
if(notification){
history.push(`/page/plot-position/{notification.id}`);
}
return (
<IonApp>
<IonReactRouter>
<IonSplitPane contentId="main" when="false">
<Menu />
<IonRouterOutlet id="main">
<Route path="/login" component={LoginPage} exact />
<PrivateRoute path="/page/plot-position/:notificationId/" component={PlotPositionPage} exact />
<Redirect from="/" to="/login" exact />
</IonRouterOutlet>
</IonSplitPane>
</IonReactRouter>
</IonApp>
);
};
const mapStateToProps = (store: IAppState) => {
return {
user: store.user.user as UserDTO,
notification: store.notificationState.notification
};
};
const mapDispatchToProps = (dispatch: any) => {
return {
getCompanies: () => dispatch(getCompaniesStartActionCreator()),
getUser: () => dispatch(getUserStartActionCreator())
}
};
export default connect(mapStateToProps, mapDispatchToProps)(App);
It looks like your navigation works, but you're having trouble passing the notification object through to the page? You can pass the object through history state.
To access the useHistory hook you would need to make your FCMService a custom hook.
const useFCMService = (): void => {
const history = useHistory();
React.useEffect(() => {
// Method called when tapping on a notification
PushNotifications.addListener('pushNotificationActionPerformed',
(action: PushNotificationActionPerformed) => {
const notification = action.notification.data as INotificationData;
history.push({ pathname: '/page/plot-position/', state: { notification } });
}
);
}, []);
}
And then include your useFCMService custom hook in your App component.
const App: React.FC<IProps> = ({ getCompanies, getUser }) => {
useFCMService();
...
};
Deep linking provides us a way to do this: Using both an action to open the application and an action at opening the application we can enroute the user to the correct destination.
Opening the application
Here we will create an action to open the url when the user taps on the push notification; to do this less use a listener:
const {PushNotifications, App} = Plugins
***
PushNotifications.addListener(
"pushNotificationActionPerformed",
(notification: PushNotificationActionPerformed) =>{
const data = notification.notification.data;
if (data.packageNumber) App.openUrl({url: `com.company.appname://tabs/package-details/${data.packageNumber}`})
else App.openUrl({url:'/tabs'})
}
)
com.company.app:// is of capital importance since the app must reach the application must reach an existing given url, otherwise the following action(catching the url) won't be triggers since it waits a complete true from the App.openUrl function; as we are opening an internal url, this must begin with the apps given name in the capacitor config page(see the following example where we can realize how use the local url).
In this way we are adding a function to open the application in an specific route.
Redirecting the user
Here, we will complete the application's part from the deep linking tutorial: we create a new listener component who handles the appOpenUrl events and redirects to the user and we will put it on the main App file inside of its respective IonRouter:
const AppUrlListener: React.FC<any> = () => {
let history = useHistory();
useEffect(() => {
App.addListener('appUrlOpen', (data: any) => {
const slug = data.url.split(':/').pop();
if (slug) {
history.push(slug);
}
});
}, []);
return null;
};
Don't forget the route in router must begin with /, and since the application url contains :/, we split the url here and we get the second part, the slug; we push it on the history, triggering the router and getting the normal behaviour when you entering in a new route.
We will add this component inside of the router:
<IonReactRouter>
<IonSplitPane contentId="main">
<Menu />
<AppUrlListener />
<IonRouterOutlet id="main">
Now, the application will be listening the appOpenUrl event, and when it gets a new of this events, it will push the gotten url to the history, redirecting the user to that route.

orientation change listener in expo react native not firing?

i want to detect the current orientation of device in expo react native, this is my code that doesn't work:
import {
Dimensions,
} from 'react-native';
import * as ScreenOrientation from 'expo-screen-orientation';**
const App = () => {
...
useEffect(() => {
const isPortrait = () => {
const dimension = Dimensions.get('screen');
return dimension.height >= dimension.width;
};
Dimensions.addEventListener('change', () => {
const orientation = isPortrait() ? 'portrait' : 'landscape';
console.log('Dimensions orientation', orientation);
});
ScreenOrientation.addOrientationChangeListener((e) => {
console.log('e ', e);
});
}, []);
how ever when i rotate the device there is no logs so it's not firing?
This works for me:
const [orientation, setOrientation] = useState(
ScreenOrientation.Orientation.PORTRAIT_UP
);
useEffect(() => {
// set initial orientation
ScreenOrientation.getOrientationAsync().then((info) => {
setOrientation(info.orientation);
});
// subscribe to future changes
const subscription = ScreenOrientation.addOrientationChangeListener((evt) => {
setOrientation(evt.orientationInfo.orientation);
});
// return a clean up function to unsubscribe from notifications
return () => {
ScreenOrientation.removeOrientationChangeListener(subscription);
};
}, []);
You should set your orientation field as default in your app.json / app.config.js. The app is locked to the specified orientation if this field is set to another value.
Related doc is here:
https://docs.expo.dev/versions/v46.0.0/config/app/#orientation
This is the line that doesn't do anything. Broken, bugged, POS? All of the above?
ScreenOrientation.addOrientationChangeListener((e) => {
console.log(e);
});
I had this same issue. The listener function was never firing.
Adding expo-sensors to my project seems to have fixed the callback for me. I think expo-screen-orientation might depend on expo-sensors
Steps for adding:
npx expo install expo-sensors
Rebuild your expo development client. (For me that command is eas build --profile simulator, but that will depend on your eas config)
After that, the listener callback function started firing.
Here's a code snippet of where I add the listener:
useEffect(() => {
ScreenOrientation.addOrientationChangeListener((e) => {
console.log(e)
})
}, [])
You're using the wrong package.
From the expo-screen-orientation docs:
Screen Orientation is defined as the orientation in which graphics are painted on the device. ... For physical device orientation, see the orientation section of Device Motion.

how to handle failed silent auth error in auth0

I followed spa react quick start guide and it worked fine for more than a month. Recently i had this error and it is logged on auth0 as 'failed silent error' with no further information. I have been told that it is because of the browsers cookie updates and recommended to use new beta release of auth0-spa-js and change cache location to local storage. And it didn't work either.
The code is as follows:
auth_config.json:
{
"domain": "dev.........eu.auth0.com",
"clientId": "....eEKkQ.............",
"redirect_uri": "https://localhost:8080",
"audience": "https://.......herokuapp.com/v1/....",
"cacheLocation": "localstorage"
}
and
react-auth0-wrapper.js:
import React, { useState, useEffect, useContext } from "react";
import createAuth0Client from "#auth0/auth0-spa-js";
const DEFAULT_REDIRECT_CALLBACK = () =>
window.history.replaceState({}, document.title, window.location.pathname);
export const Auth0Context = React.createContext();
export const useAuth0 = () => useContext(Auth0Context);
export const Auth0Provider = ({
children,
onRedirectCallback = DEFAULT_REDIRECT_CALLBACK,
...initOptions
}) => {
const [isAuthenticated, setIsAuthenticated] = useState();
const [user, setUser] = useState();
const [auth0Client, setAuth0] = useState();
const [loading, setLoading] = useState(true);
const [popupOpen, setPopupOpen] = useState(false);
useEffect(() => {
const initAuth0 = async () => {
const auth0FromHook = await createAuth0Client(initOptions);
setAuth0(auth0FromHook);
if (window.location.search.includes("code=")) {
const { appState } = await auth0FromHook.handleRedirectCallback();
onRedirectCallback(appState);
}
const isAuthenticated = await auth0FromHook.isAuthenticated();
setIsAuthenticated(isAuthenticated);
if (isAuthenticated) {
const user = await auth0FromHook.getUser();
setUser(user);
}
setLoading(false);
};
initAuth0();
// eslint-disable-next-line
}, []);
const loginWithPopup = async (params = {}) => {
setPopupOpen(true);
try {
await auth0Client.loginWithPopup(params);
} catch (error) {
console.error(error);
} finally {
setPopupOpen(false);
}
const user = await auth0Client.getUser();
setUser(user);
setIsAuthenticated(true);
};
const handleRedirectCallback = async () => {
setLoading(true);
await auth0Client.handleRedirectCallback();
const user = await auth0Client.getUser();
setLoading(false);
setIsAuthenticated(true);
setUser(user);
};
return (
<Auth0Context.Provider
value={{
isAuthenticated,
user,
loading,
popupOpen,
loginWithPopup,
handleRedirectCallback,
getIdTokenClaims: (...p) => auth0Client.getIdTokenClaims(...p),
loginWithRedirect: (...p) => auth0Client.loginWithRedirect(...p),
getTokenSilently: (...p) => auth0Client.getTokenSilently(...p),
getTokenWithPopup: (...p) => auth0Client.getTokenWithPopup(...p),
logout: (...p) => auth0Client.logout(...p)
}}
>
{children}
</Auth0Context.Provider>
);
};
What is wrong with this code, any help appreciated. Or i can use a different method, i just followed the docs, it doesn't matter as long as it authenticates.
Thanks
I know this has been hanging around for a bit, but i was running into a similar issue.
As I understand it the createAuth0Client helper factory runs the getTokenSilently function by default as part of the set up to re-authenticate users every browser refresh. The problem i was having was that the call to getTokenSilently was erroring, meaning that auth0FromHook was never set and the auth0client never set in state. Because auth0client was undefined, it was then impossible to call loginwithredirect, which is the behaviour i wanted to achieve.
Basically i wanted it to auth silently, but if it failed, send to the log in screen, but that's impossible because the auth0client was undefined, resulting in a cannot call loginwithredirect of undefined error. It seems that (sadly) in the current stable version of the #auth0/auth0-spa-js library (1.6.5 at time of writing) there is no way to bypass getTokenSilently when initialising the client. However in the current beta (1.7.0-beta.5) (Here is a list of versions) they have exposed the Auth0Client class itself, so if you want to move to that version the code could be tweaked with something like....
initAuth0().catch( e => {
const newClient = new Auth0Client(initOptions);
setAuth(newClient);
})
and then in any protected components you can check the loading is finished and if isAuthenticated is still falsey, you should be able to redirect to login despite an error occurring during the getSilentToken.
== NON BETA OPTION
The alternative in the current api would be to perhaps set max_age to 0 or 1 in the initOptions, to force a re-login, and maybe setting prompt to "login" on the second attempt to initialize the authClient