Expo react native Android Deep linking not working - react-native

I am trying to implement a deep link on Android ( Similar to iOS universal link). Please find below the SDK details.
SDK Version: 37.0.12
Platforms(Android/iOS/web/all):
Android
app.json contains an intent filter as suggested
"intentFilters": [
{
"action": "VIEW",
"data": [
{
"scheme": "https",
"host": "**************",
"pathPrefix": "/records"
}
],
"category": ["BROWSABLE", "DEFAULT"]
}
]
below is app.js code.
const Stack = createStackNavigator()
function App() {
const linking = { prefixes: ['*******','exps://********'] }
return (
<NavigationContainer linking={linking} fallback={<Text>Loading...</Text>}>
<Stack.Navigator>
<Stack.Screen name='Home' component={HomeScreen} />
</Stack.Navigator>
</NavigationContainer>
)
}
export default App
and the server contains asset links hosted on well-known/assetlinks.json
[
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "************",
"sha256_cert_fingerprints": [ "5C:AF:56:11:A5:3C:82:A7:9D:18:23:6E:CC:67:08:16**********"
]
}
}
]
The app runs as expected however, Deep link doesn’t seem to work.
am I missing something? Any leads/suggestion/input will be highly appreciated
Regards,
H

Related

Jest (ESM) can't import React Native components

Trying to use Jest with ESM configuration to test React Native components and getting errors that Jest can't parse the imports from 'react-native'. The errors look like this: SyntaxError: The requested module 'react-native' does not provide an export named 'StyleSheet'. Any idea what I'm doing wrong?
RN version: 0.70.4
Example component:
import React, { ReactElement } from 'react';
import { StyleSheet, Text, View } from 'react-native';
const { container } = StyleSheet.create({
container: {
padding: 50,
},
});
const ThisComponent = (): ReactElement => (
<View style={container}>
<Text>Hello World!</Text>
</View>
);
export default ThisComponent;
Example test:
import React, { ReactElement } from 'react';
import { render } from '#testing-library/react-native';
import ThisComponent from '../ThisComponent';
describe('<OTPInputs>', () => {
test('Renders without exploding', async () => {
const { getByText } = render(
<ThisComponent />,
);
expect(getByText('Hello World!')).not.toBeDisabled();
});
});
Jest config (in package.json):
"jest": {
"haste": {
"defaultPlatform": "ios",
"platforms": [
"android",
"ios",
"native"
]
},
"resetMocks": true,
"testEnvironment": "node",
"testMatch": [
"**/src/**/*.(spec|test).[tj]s?(x)"
],
"preset": "ts-jest/presets/default-esm",
"transform": {
"^.+\\.js$": "babel-jest"
},
"transformIgnorePatterns": [
"node_modules/(?!((jest-)?react-native|#react-native(-community)?)/)"
],
"extensionsToTreatAsEsm": [
".ts",
".tsx"
],
"globals": {
"ts-jest": {
"useESM": true
}
},
"setupFiles": [
"<rootDir>/node_modules/react-native/jest/setup.js"
],
"setupFilesAfterEnv": [
"#testing-library/jest-native/extend-expect"
],
"moduleNameMapper": {
"^~/(.*)$": "<rootDir>/src/$1",
"^~components/(.*)$": "<rootDir>/src/components/$1",
"^~util/(.*)$": "<rootDir>/src/util/$1",
"^~types/(.*)$": "<rootDir>/src/types/$1"
}
}
EDIT: Looks like the main RN export is a cjs file with some weird syntax in it. Here is a mock that makes it work (with const ThisComponent = (await import('../ThisComponent').default):
jest.unstable_mockModule('react-native', () => ({
__esModule: true,
StyleSheet: jest.requireActual(
'react-native/Libraries/StyleSheet/StyleSheet',
),
Text: jest.requireActual('react-native/Libraries/Text/Text'),
TextInput: jest.requireActual(
'react-native/Libraries/Components/TextInput/TextInput',
),
TouchableOpacity: jest.requireActual(
'react-native/Libraries/Components/Touchable/TouchableOpacity',
),
View: jest.requireActual('react-native/Libraries/Components/View/View'),
}));
Your Jest config looks quite complex and I suspect that it might be the reason. In general importing from react-native package should work out of the box in when using React Native Testing Library.
In case of config issue its always a good idea to start with a working step and then add additional config entries you need. In RNTL we have a basic example app that is useful for that purpose.

Unable to get to another screen from react-navigation deep linking

Given the following setup:
const config = {
screens: {
Discover: 'discover',
Theater: {
screens: {
Watch: 'watch/:name?',
},
},
},
}
export const linking: LinkingOptions<any> = {
prefixes: ['test://'],
config,
}
const Tab = createBottomTabNavigator()
export function TheaterStackScreen({route}: Props) {
return (
<Tab.Navigator screenOptions={{headerShown: false}}>
<Tab.Screen name="Watch" component={WatchScreen} initialParams={{name: route.params.name}} />
</Tab.Navigator>
)
}
const Stack = createNativeStackNavigator()
export function StackScreen() {
return (
<Stack.Navigator screenOptions={{headerShown: false}}>
<Stack.Screen name="Discover" component={DiscoverScreen} />
<Stack.Screen name="Theater" component={TheaterStackScreen} />
</Stack.Navigator>
)
}
...and running:
npx uri-scheme open "test://watch/test" --android
I recieve:
Android: Opening URI "test://watch/test" in emulator
...with no errors, however, there is no navigation within the app...
and this warning:
WARN The navigation state parsed from the URL contains routes not present in the root navigator. This usually means that the linking configuration doesn't match the navigation structure. See https://reactnavigation.org/docs/configuring-links for more details on how to specify a linking configuration.
Does something look inherently wrong with this setup?
current setup:
"#react-navigation/bottom-tabs": "6.3.1",
"#react-navigation/native": "6.0.10",
"#react-navigation/native-stack": "6.6.2",
"react-native": "0.68.2",
This is being run on device, but I don't get any different behavior on sim either. Ive attempted with debugger on and off with no difference.
As always any and all direction is appreciated so thanks in advance!
Edit with more details:
const {createNavigationContainer} = Bugsnag.getPlugin('reactNavigation') as BugsnagPluginReactNavigationResult
const BugsnagNavigationContainer = createNavigationContainer(NavigationContainer)
const Stack = createNativeStackNavigator()
...
<BugsnagNavigationContainer linking {linking} onStateChange={(state: any) => {
const newRouteNam = Analytics.getActiveRouteName(state)
if (routeName !== newRouteName) {
analyticsClient.screen(newRouteName)
setRouteName(newRouteName)
}
}}>
<Stack.Navigator screenOptions={{headerShown: false}}>
<Stack.Screen name="Main" component={StackScreen} />
</Stack.Navigator>
</BugsnagNavigationContainer>
Edit 2:
Unfortunately, updating the config had no effect, I still see the tracking but no navigation or opening of the specified path:
const config = {
screens: {
Main: {
screens: {
Discover: 'discover',
Theater: {
screens: {
Watch: 'watch/:name',
},
},
},
},
},
}
results:
TRACK (Deep Link Opened) event saved {"event": "Deep Link Opened", "properties": {"referring_application": "android-app://com.android.shell", "url": "test://watch"}, "type": "track"}

Not getting route.params when using deep linking

I'm trying to implement Deep Linking on my APP, I'm following expo-cli react-native-navigation documentation about this subject. After basic configuration I can't get params from route.params from any of the links I set.
This is an example of my code
linking.js
import * as Linking from "expo-linking";
const prefix = Linking.createURL("/");
const config = {
screens: {
userStartSession: {
path: "home/:itemInfo",
parse: { itemInfo: (itemInfo) => `${itemInfo}` },
},
},
};
const linking = {
prefixes: [prefix],
config,
};
export default linking;
Docs here: https://reactnavigation.org/docs/deep-linking/
And here: https://reactnavigation.org/docs/configuring-links/
Then import linking into Navigation.js
import React from "react";
import linking from "../utils/linking/linking";
export default function Navigation() {
return (
<NavigationContainer linking={linking} fallback={<Text>Cargando...</Text>}>
<Stack.Navigator>
<Stack.Screen
name="userStartSession"
options={{ headerShown: false, headerLeft: null }}
>
{(props) => (
<UserSessionStack
{...props}
somePropsHere={somePropsHere}
/>
)}
</Stack.Screen>
</Stack.Navigator>
</NavigationContainer>
);
}
UserSessionStack.js
import React, { useState, useEffect } from "react";
import { createStackNavigator } from "#react-navigation/stack";
import { useRoute } from "#react-navigation/native";
const Stack = createStackNavigator();
export default function UserSessionStack(props) {
const { somePropsHere } = props;
const route = useRoute();
console.log(route.params);
return (
<Stack.Navigator
initialRouteName="slide-home"
>
<Stack.Screen
name="slide-home"
component={SlideHome}
options={{ headerShown: false }}
/>
</Stack.Navigator>
);
}
Then following react-navigation-native I run this comand to test the link
npx uri-scheme open exp://192.168.1.101:19000/--/home/this_is_a_test --ios
Docs here:
https://reactnavigation.org/docs/deep-linking/#test-deep-linking-on-ios
As you may see I'm passing "this_is_a_test" as a param to home link, but if I do console.log(route) in userStartSession component, I always get undefined in params
This is the response
Object {
"key": "userStartSession-CLSUaGx7QkCe8qlrvuvTf",
"name": "userStartSession",
"params": undefined,
"state": Object {
"index": 0,
"key": "stack-cyZDx2Q4gdGaZ1dMr2V1Q",
"routeNames": Array [
"slide-home",
"walkthrough-slides",
"sign-up-invitation",
"login-form",
"recover-account-password",
"sign-up-form",
"sign-up-payment-form",
"sign-up-payment-success",
],
"routes": Array [
Object {
"key": "sign-up-invitation-SSf39_Tj79VFv34ydou3N",
"name": "sign-up-invitation",
"params": undefined,
},
],
"stale": false,
"type": "stack",
},
}

React navigation webpack linking "CANNOT GET /..."

I am trying to configure react-navigation for a web app with react native. For that I set up the linking options on a NavigationContainer so that I can access my pages from a browser url, using this code :
const linking = {
prefixes: ['http://localhost:8080/', 'http://localhost:8080', 'localhost:8080'],
// prefixes: [prefix],
config: {
screens: {
SignIn: "SignIn",
SignUp: "SignUp",
Landing: '*',
},
}
};
function AppContainer() {
return (
<NavigationContainer linking={linking} fallback={<Text>Loading...</Text>}>
<AppStack.Navigator>
<AppStack.Screen name="SignIn" component={SignInPage}/>
<AppStack.Screen name="Landing" component={LandingPage}/>
<AppStack.Screen name="SignUp" component={SignUpPage}/>
<AppStack.Screen name="Home" component={HomePage}/>
</AppStack.Navigator>
</NavigationContainer>
);
}
When I go to "http://localhost:8080/", I am redirected to "http://localhost:8080/SignIn" ( which is fine), and the app is working. The problem is that if I go from my browser to "http://localhost:8080/SignIn" I get "Cannot GET /SignIn", and the app is not working...
I am using these versions :
"#react-navigation/bottom-tabs": "^5.11.1",
"#react-navigation/native": "^5.8.9",
"#react-navigation/stack": "^5.12.5",
Found a solution on How to tell webpack dev server to serve index.html for any route.
I was using webpack-dev-server, which needs some configurations in the webpack.config.js to map all the url to / and serve the index.html .. These are the configurations :
devServer: {
port: 3000,
historyApiFallback: {
index: 'index.html'
}
}
Adding the cli option --history-api-fallback also do the trick.

React Native Expo: Authentication with Identity Server 4

I am trying to use the sample code from expo documentation "https://docs.expo.io/guides/authentication/" for IdentityServer4. I have created a React template from ASP.NET Core Web Application. When I run the react native app to redirect to the login page of the ASP application I can login but the problem is the window does not close to return the token. It logs in and then I go to the home page of the ASP application. I believe I am missing the redirect URL or the client is not setup the right way. Can someone help me how to set this up? Or what part am I missing?
Here is my client set up from the ASP application from the appsettings.json:
"IdentityServer": {
"Clients": {
"AAA": {
"ClientId": "myclientid,
"ClientName": "Native Client (Code with PKCE)",
"RequireClientSecret": false,
"RedirectUris": [ "io.expo.auth/#username/Auth" ],
"AllowedGrantTypes": "GrantTypes.Code",
"RequirePkce": "true",
"AllowedScopes": ['openid', 'profile', 'email', 'offline_access'],
"AllowOfflineAccess": "true",
"Profile": "IdentityServerSPA",
"ClientSecrets": [
{
"Value": "secretvalue"
}
]
}
},
Below is my React Native app:
import * as React from 'react';
import { Button, Text, View } from 'react-native';
import * as AuthSession from 'expo-auth-session';
import * as WebBrowser from 'expo-web-browser';
WebBrowser.maybeCompleteAuthSession();
const useProxy = true;
const redirectUri = AuthSession.makeRedirectUri({
native: 'myApp://io.expo.auth/#username/Auth',
useProxy,
});
export default function App() {
const discovery = AuthSession.useAutoDiscovery('https://demo.identityserver.io');
// Create and load an auth request
const [request, result, promptAsync] = AuthSession.useAuthRequest(
{
clientId: 'myclientid',
redirectUri,
scopes: ['openid', 'profile', 'email', 'offline_access'],
},
discovery
);
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Button title="Login!" disabled={!request} onPress={() => promptAsync({ useProxy })} />
{result && <Text>{JSON.stringify(result, null, 2)}</Text>}
</View>
);
}
Here is what you need to fix on ur code:
On appsettings.json change RedirectUris to be a list, result would be like this:
"IdentityServer": {
"Clients": {
"AAA": {
"ClientId": "myclientid,
"ClientName": "Native Client (Code with PKCE)",
"RequireClientSecret": false,
"RedirectUris": [ "io.expo.auth/#username/Auth" ],
"AllowedGrantTypes": "GrantTypes.Code",
"RequirePkce": "true",
"AllowedScopes": [ "api" ],
"AllowOfflineAccess": "true",
"Profile": "IdentityServerSPA",
"ClientSecrets": [
{
"Value": "secretvalue"
}
]
}
},
Read more here
Make sure URL listed on RedirectUris exist, and it match the value set for redirectUri on the native client. I'm no expo expert, you may use some sort of middleware but as far as I understand from your code above these URLs dont match.
#nahidf: I have updated the url from the react native and the ASP app. The log in still is fine on the android app, but after I log in, the app stays open with the home page of that ASP app and not closing to return to react app and also return the token. Does the expo app have to be published because I am running it locally? Also, the ASP I have published it on a local server.