Prevent for user to access "Auth Loading Screen" on android - react-native

In my app (and most likely like any other app) I have a loading screen which checks if the user is either logged in or not. Thing is that the user on android right now has the possibility of returning to that screen if the "back" button is pressed. How is it possible for me to avoid for the user doing that?
This is what I have on my code:
const loadingStack = createSwitchNavigator({
Loading: {
screen: Loading
}
}, {
defaultNavigationOptions: {
unmountOnBlur: true,
headerMode: 'none',
gestureEnabled: false
}
}
);
export default createAppContainer(
createStackNavigator({
Loading: loadingStack,
App: {
screen: AppTabNavigator,
navigationOptions: {
gestureEnabled: false,
},
},
Auth: {
screen: AuthStack,
},
...

I see two solutions:
Change your navigation and opt for a switch navigation instead.
createAppContainer(
createSwitchNavigator(
{
Loading: {
screen: Loading,
navigationOptions: {
headerMode: 'none',
}
},
App: {
screen: AppTabNavigator,
navigationOptions: {
gestureEnabled: false
}
},
Auth: {
screen: AuthStack
}
}
)
)
If you want to "enjoy" the animations of the StackNavigator, when you navigate from your Loading to your App, you replace your current view with your new view.
//Example on your Loading Screen
this.props.navigation.replace({ routeName: "App" })
This will replace your Loading Screen view with your App "View", when you go back, you will not be redirected to the Loading Screen anymore because it is no longer in the stack, it has been replaced by App.
EDIT (In response to the comment because it's too long):
In this case, if you want to make a special backButton management on the Home you can use the React Native BackHandler.
For example, if you want nothing to happen when you click on the backButton, add an eventListener that returns true no matter what:
import { BackHandler } from 'react-native'
...
componentDidMount() {
this.backHandler = BackHandler.addEventListener('hardwareBackPress', () => true)
}
componentWillUnmount(){
this.backHandler.remove()
}
Otherwise, if you want the app to quit, for example:
this.backHandler = BackHandler.addEventListener('hardwareBackPress', () => BackHandler.exitApp())

Related

Disable loading of initialRoute inside stack navigator when

I have two stacks in my react-native app similar to this structure. When I navigate a user from Home screen inside MainStack to Quiz screen inside QuizStack, the componentDidMount method of QuizList screen is being called. How do I prevent this? I want only the componentDidMount of Quiz screen to be called.
export const MainStack = createStackNavigator({
Home: { screen: Home },
Game: { screen: Game },
Leaderboard: { screen: Leaderboard }
}, { initialRouteName: 'Home' })
export const QuizStack = createStackNavigator({
QuizList: { screen: QuizList },
Quiz: { screen: Quiz },
QuizResult: { screen: QuizResult }
}, { initialRouteName: 'QuizList' })
export const InternalStacks = createBottomTabNavigator({
QuizStack: { screen: QuizStack, navigationOptions: { tabBarLabel: 'Quiz' } },
MainStack: { screen: MainStack, navigationOptions: { tabBarLabel: 'Home' } },
})
What makers matters worse is, on sending a push notification, users are directed automatically from Home(MainStack) to Quiz (QuizStack). The componentDidMount method in Quiz calls an API, so if 10K users opens notifications, that 10K APIs called. Then unecesarily the componentDidMount method of QuizList(QuizStack) is also being called, which is an additional 10K APIs
It is unclear to me why you have two separate stacks, and how they are connected. Maybe by a top level navigator? You should try to merge the stacks into a single one.
About the Push notification, you can create a SwitchNavigator:
const SwitchNavigator = createSwitchNavigator(
{
Loading: LoadingScreen,
Root: RootStack
},
{
initialRouteName: 'Loading'
}
)
And in the componentDidMount in LoadingScreen, you will check the initial URL received by the push.
async componentDidMount() {
const { navigation } = this.props
const initialURL = await Linking.getInitialURL()
if (initialURL && initialURL.startsWith('myapp://myapp/xxx')) {
navigation.navigate('MyScreenA', { options })
} else {
navigation.navigate('MyScreenDefaultScreen')
}
}

Back button handler working on every screen after changing drawer navigator in react - native

I am making a react native application in which there can be two type of user model and member.Now each having different drawer navigator as both having different data and both these drawer are inside a main stack navigator which is main navigator for the application. Now in both the both the drawer navigator .
const RootStack = createStackNavigator({
Splash : { screen: Splash },
PrivacyPolicy : { screen: PrivacyPolicy},
Welcome : { screen: Welcome },
Login : { screen: Login },
HomeMember: {screen: memberdrawerNavigator},
HomeModel: {screen: modeldrawerNavigator},
OtpChangePassword : { screen: OtpChangePassword},
}, {
headerMode: 'none',
initialRouteName: 'Splash'
})
I have a homepage different for both the member type and in both the home page i have implemented the back handler like this.
handleBackWithAlert = () => {
if (this.props.isFocused) {
if(this.state.loading_status){
this.setState({loading_status:false})
}
else{
Alert.alert(
'Exit App',
'Exiting the application?',
[
{
text: 'Cancel',
onPress: () => console.log('Cancel Pressed'),
style: 'cancel'
},
{
text: 'OK',
onPress: () => BackHandler.exitApp()
}
],
{
cancelable: false
}
);
}
return true;
}
}
componentWillMount() {
BackHandler.addEventListener('hardwareBackPress',this.handleBackWithAlert);
//this.props.navigation.dispatch(DrawerActions.closeDrawer());
this.props.navigation.closeDrawer();
}
componentWillUnmount() {
BackHandler.removeEventListener('hardwareBackPress', this.handleBackWithAlert);
}
Since i wanted to check back handling on only specific page so i exported my component like this.
export default withNavigationFocus(HomePage);
Now what the problem is that suppose i am using member account and after that i logout and then login with model account then this back handler is working on every screen why ? And if i close or kill that app without logout and restart then this problem disappears.
Here is my code called on logout
AsyncStorage.clear()
ToastAndroid.show("Logged Out Successfully !",ToastAndroid.SHORT);
const resetAction = StackActions.reset({
index: 0,
actions: [NavigationActions.navigate({ routeName: 'Welcome' })],
});
this.props.navigation.dispatch(resetAction);
Kindly help me to solve this problem. Thanks in advance.
According to my understanding, something is wrong with your BackHandler listeners. maybe you're not removing BackHandler listener on componentWillUnmount?

React Navigation v3 Modal does not work with createBottomTabNavigator

React Navigation v3 Modal does not work with createBottomTabNavigator and not sure why. However, headerMode: 'none' seems to be working but mode: 'modal' is not showing up as modal.
const Addpicture = createStackNavigator(
{
Addpicture: {
screen: Addpicture
}
},
{
mode: 'modal', // This does NOT work
headerMode: 'none' // But this works
}
);
const Navigator = createBottomTabNavigator(
{
'My Interviews': {
screen: MyDatesStack
},
'Add Interview': {
screen: AddDateStack
},
'Companies': {
screen: CompaniesStack
}
}
);
export default createAppContainer(Navigator);
Indeed it doesn't work no matter what I tried.
I solved this problem by following steps below. Let's say you want to open modal when NewModal tab is pressed.
Set up your app container by including tabs stack & to be opened modal navigation stack:
const FinalTabsStack = createStackNavigator(
{
Home: TabNavigator,
NewModal: NewModalNavigator
}, {
mode: 'modal',
}
)
Create app container with that tabs stack per this guide
Inside the TabNavigator in the createBottomTabNavigator return null component for specific tab (NewModal) (to turn off navigation by react-navigator)
const TabNavigator = createBottomTabNavigator({
Feed: FeedNavigator,
Search: SearchNavigator,
NewModal: () => null,
Chat: ChatNavigator,
MyAccount: MyAccountNavigator,
}
defaultNavigationOptions: ({ navigation }) => ({
mode: 'modal',
header: null,
tabBarIcon: ({ focused }) => {
const { routeName } = navigation.state;
if (routeName === 'NewModal') {
return <NewModalTab isFocused={focused} />;
}
},
}),
Handle click manually inside a custom tab component NewModalTab with TouchableWithoutFeedback & onPress. Inside NewModalTab component:
<TouchableWithoutFeedback onPress={this.onPress}>
<Your custom tab component here />
</TouchableWithoutFeedback>
Once you catch onPress dispatch redux event
onPress = () => {
this.props.dispatch({ type: 'NAVIGATION_NAVIGATE', payload: {
key: 'NewModalNavigator',
routeName: 'NewSelfieNavigator',
}})
}
Handle dispatched event using Navigation Service. I'm using redux-saga for it
NavigationService.navigate(action.payload);
A bit complicated but works.

Custom header in nested TabNavigator

I have a fairly complication navigation flow requirement for an app I'm working on.
I have a bottom tab bar, for each tab I'll be having a top tab bar for additional related views.
Which I have working, however on the videos tab in the nested "All" tab, I need to add a search bar to the header, and on the "Favourites" tab I'll be having yet another custom header with an "Edit" button at the top right.
How can I achieve this navigation whilst allowing React Navigation to co-ordinate everything. See images below:
What I don't want to do is disable the header at the MainNavigator level and enable it for particular routes. Or even worse embed the header and the tab bar on individual containers.
routes.js
import {
StackNavigator,
TabNavigator,
DrawerNavigator
} from "react-navigation";
import Feed from "./containers/Feed";
import Auth from "./containers/Auth";
import News from "./containers/News";
import Videos from "./containers/Videos";
import FavouriteVideos from "./containers/FavouriteVideos";
const DashboardNavigator = TabNavigator(
{
Feed: {
screen: Feed
},
News: {
screen: News
}
},
{
tabBarPosition: "top"
}
);
const VideoNavigator = TabNavigator(
{
Videos: {
screen: Videos,
navigationOptions: {
title: "All"
}
},
Favourites: {
screen: FavouriteVideos
}
},
{
tabBarPosition: "top"
}
);
const MainNavigator = TabNavigator(
{
Dashboard: {
screen: DashboardNavigator,
navigationOptions: ({}) => ({
title: "Dashboard"
})
},
Video: {
screen: VideoNavigator,
navigationOptions: ({}) => ({
title: "Videos"
})
}
},
{
swipeEnabled: false,
animationEnabled: false,
tabBarPosition: "bottom"
}
);
const AuthenticatedNavigator = DrawerNavigator({
App: {
screen: MainNavigator
}
});
const RootNavigator = StackNavigator({
LoggedOut: {
screen: Auth
},
Authenticated: {
screen: AuthenticatedNavigator
}
});
export default RootNavigator;
Snack
https://snack.expo.io/H1qeJrLiM
Images
You can use react-navigation addListener function with combination setParams to achieve desired behavior.
You can listen for focus and blur events and then change a parameter. Then in your route config you can look for this parameter and decide what to render for header. I changed your snack to show a working example of what I am suggesting.
Example
const MainNavigator = TabNavigator(
{
Dashboard: {
screen: DashboardNavigator,
navigationOptions: ({}) => ({
title: "Dashboard"
})
},
Video: {
screen: VideoNavigator,
navigationOptions: ({navigation}) => {
let title = 'Videos';
navigation.state.routes.forEach((route) => {
if(route.routeName === 'Videos' && route.params) {
title = route.params.title;
}
});
// I set title here but you can set a custom Header component
return {
tabBarLabel: 'Video',
title
}
}
}
},
{
swipeEnabled: false,
animationEnabled: false,
tabBarPosition: "bottom"
}
);
export default class Videos extends Component {
constructor(props) {
super(props);
this.willFocusSubscription = props.navigation.addListener(
'willFocus',
payload => {
this.props.navigation.setParams({title: 'All Videos'});
}
);
this.willBlurSubscription = props.navigation.addListener(
'willBlur',
payload => {
this.props.navigation.setParams({title: 'Just Videos'});
}
);
}
componentWillUnmount() {
this.willFocusSubscription.remove();
this.willBlurSubscription.remove();
}
render() {
return (
<View>
<Text> Videos </Text>
</View>
);
}
}

navigating user on notification click app in background/foreground

I'm using REACT-NATIVE-FCM.
My app has drawer navigator inside stack navigator in App.js.
On notification click from notification tray I want user to directly go to certain Screen.
Registering FCM events in Home page only work if app is closed, while in app in foreground the events are not called.It just takes you to the last open Screen.
I have to register FCM events on every Screen, which doesn’t look ok to me. So I have the solution to register FCM events in App.js since App.js is called from index.js while initializing the App but I’m unable to use this.props.navigation.navigate in App.js.
Using react navigation how can we redirect user just after stack is declared.
APP.JS
const Drawer = DrawerNavigator(
{
Dashboard: { screen: Dashboard },
Screen1: { screen: Screen1 },
Screen2: { screen: Screen2},
Screen3: { screen: Screen3},
},
{
navigationOptions: {
gesturesEnabled: false,
},
initialRouteName: "Dashboard",
contentOptions: {
activeTintColor: "#e91e63"
},
drawerPosition: 'right',
contentComponent: props => <SideBar {...props} />
}
);
const AppNavigator = StackNavigator(
{
Home: { screen: Home },
Drawer: { screen: Drawer },
ScreenABC: { screen: ScreenABC },
ScreenXYZ: { screen: ScreenXYZ }
},
{
headerMode: "none",
}
);
export default () =>
<Root>
<AppNavigator />
</Root>;
//CALLED WHEN APP IN FOREGROUND
this.notificationListener = FCM.on(FCMEvent.Notification, async (notif) => {
//alert('FCM.on: ' + JSON.stringify(notif));
if(notif.routeValue == 1)
{
this.props.navigation.navigate("Screen1")}
});
//CALLED WHEN APP IN BACKGROUND
FCM.getInitialNotification().then(notif => {
});
If you want to really go to a certain screen no matter what. You should at least reset your navigator state before you call navigate.
Or you can also do is creating a custom action, dispatch this action and return a correct navigator state.
Here is how I reset my navigator state to home:
export const homeNav = (state, action) => {
let index;
let routes;
switch (action.type) {
case types.RESET_HOME_NAV:
routes = [...state.routes];
for (let index = state.index; index > 0; --index) {
routes.pop();
}
return {
...state,
routes,
index: 0,
};
default:
return NavigatorHome.router.getStateForAction(action, state);
}};