I have followed the documentation of React Navigation and implemented an authentication flow as well the Linking mechanism for notifications via the linking prop of NavigationContainer.
When I open a notification when the app is running, e.g. the link https://domain/transactions/history, I am redirected to the screen TransactionHistory, and when I go back I am redirected to ChargingStations (as I want)
But, when I do the same thing from a quit state, I am being redirected as well but I can’t go back to ChargingStations and I see the warning (The action GO_BACK was not handled by any navigator)..
My code is a follow:
App.tsx
export default class App extends React.Component<Props, State> {
public state: State;
public props: Props;
public centralServerProvider: CentralServerProvider;
public deepLinkingManager: DeepLinkingManager;
private appVersion: CheckVersionResponse;
private readonly navigationRef: React.RefObject<NavigationContainerRef<ReactNavigation.RootParamList>>;
private readonly appContext;
private initialUrl: string;
public constructor(props: Props) {
super(props);
this.navigationRef = React.createRef();
this.appContext = {
handleSignIn: () => this.setState({isSignedIn: true}),
handleSignOut: () => this.setState({isSignedIn: false})
};
this.state = {
switchTheme: false,
navigationState: null,
showAppUpdateDialog: false,
isSignedIn: undefined
};
}
public setState = (
state: State | ((prevState: Readonly<State>, props: Readonly<Props>) => State | Pick<State, never>) | Pick<State, never>,
callback?: () => void
) => {
super.setState(state, callback);
};
public async componentDidMount() {
// Get the central server
this.centralServerProvider = await ProviderFactory.getProvider();
// Setup notifications
await Notifications.initialize();
// Check for app updates
this.appVersion = await Utils.checkForUpdate();
// Set
this.setState({
showAppUpdateDialog: !!this.appVersion?.needsUpdate,
isSignedIn: true
});
}
public render() {
const { switchTheme, showAppUpdateDialog, isSignedIn } = this.state;
return switchTheme ? (
<NativeBaseProvider>
<GestureHandlerRootView style={{ flex: 1 }}>
{showAppUpdateDialog && (
<AppUpdateDialog appVersion={this.appVersion} close={() => this.setState({ showAppUpdateDialog: false })} />
)}
{isSignedIn == null ?
<Loading />
:
this.createRootNavigator()
}
</GestureHandlerRootView>
</NativeBaseProvider>
) : (
<NativeBaseProvider>
<View />
</NativeBaseProvider>
);
}
private buildLinking(): LinkingOptions<ReactNavigation.RootParamList> {
return (
{
prefixes: DeepLinkingManager.getAuthorizedURLs(),
getInitialURL: () => this.initialUrl,
subscribe: (listener) => {
// Listen for background notifications when the app is running,
const removeBackgroundNotificationListener = messaging().onNotificationOpenedApp(async (remoteMessage: Notification) => {
const canHandleNotification = await Notifications.canHandleNotificationOpenedApp(remoteMessage);
if (canHandleNotification) {
this.setState({isSignedIn: true}, () => listener(remoteMessage.data.deepLink));
}
});
return () => {
removeBackgroundNotificationListener();
};
},
config: {
screens: {
AuthNavigator: {
screens: {
Login: 'login'
}
},
AppDrawerNavigator: {
initialRouteName: 'ChargingStationsNavigator', // <-- Initial screen I would like to always be present as first screen when navigating
screens: {
ChargingStationsNavigator: {
initialRouteName: 'ChargingStations',
screens: {
ChargingStations: 'charging-stations/all'
}
},
InvoicesNavigator: 'invoices',
TransactionInProgressNavigator: {
screens: {
TransactionsInProgress: 'transactions/inprogress'
}
},
TransactionHistoryNavigator: {
screens: {
TransactionsHistory: 'transactions/history'
}
}
}
}
}
}
}
);
}
private createRootNavigator() {
const { isSignedIn } = this.state;
return (
<AuthContext.Provider value={this.appContext}>
<SafeAreaProvider>
<NavigationContainer
onReady={() => this.onReady()}
linking={this.buildLinking()}
ref={this.navigationRef}
onStateChange={(newState) => this.setState({navigationState: newState})}
initialState={this.state.navigationState}
>
<rootStack.Navigator initialRouteName="AuthNavigator" screenOptions={{ headerShown: false }}>
{isSignedIn ?
<rootStack.Screen name="AppDrawerNavigator" children={createAppDrawerNavigator} />
:
<rootStack.Screen options={{animationTypeForReplace: 'pop'}} name="AuthNavigator" children={createAuthNavigator} />
}
</rootStack.Navigator>
</NavigationContainer>
</SafeAreaProvider>
</AuthContext.Provider>
);
}
}
Expected behavior
I expect the ChargingStations screen to always be present as first screen, even from a quit state
Reproduction
https://github.com/sap-labs-france/ev-mobile/tree/upgrade_react_native
Platform
- [X] Android
- [X] iOS
Environment
- [x] I’ve removed the packages that I don’t use
package | version |
---|---|
@react-navigation/native | 6.0.14 |
@react-navigation/drawer | 6.5.1 |
@react-navigation/material-bottom-tabs | 6.2.5 |
@react-navigation/stack | 6.3.5 |
react-native-safe-area-context | 4.4.1 |
react-native-screens | 3.18.2 |
react-native-gesture-handler | 2.8.0 |
react-native-reanimated | 2.13.0 |
react-native | 0.70.6 |
node | 16.13.0 |
npm or yarn | 9.1.2 |
2
Answers
I have managed to obtain the desired behavior by using following code in the drawer navigator props.
You should define a nested linking route: Docs
Maybe you can try to redeclare initial route (unfortunately I have currently no access to verify my suggestion):