I am trying to pass token and other user details from React Web application to React Native application with Webview (react-native).
When I do
window.postMessage(JSON.stringify(reactNativeObj), '*');
and log the event in React native application with
console.log( "On Message", event.nativeEvent.data );
It is printing the log as
On Message: setImmediate$0.4162155499933975$1
It should print my object instead.
I have tried almost everything and re-read the document https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage but couldn't make it work.
Any help would be appreciated.
If you're using RN >= 0.60 then you're using react-native-webview >= 8.0.0, which you'll have to use window.ReactNativeWebView.postMessage.
If you have access and can edit the website code, then you should add a postMessage call like this:
if (window.ReactNativeWebView) {
window.ReactNativeWebView.postMessage(JSON.stringify(reactNativeObj));
}
If you don't have access to the website code or if you don't want to alter its' contents, then you can inject some code that modifies window.postMessage to use window.ReactNativeWebView.postMessage along with the default method:
const WebView_Injection_postMessage = `
(function() {
window.default_postMessage = window.postMessage;
window.postMessage = function(data, domain) {
window.default_postMessage(data, domain);
if (window.ReactNativeWebView) {
window.ReactNativeWebView.postMessage(data);
}
}
})();
`;
const App: () => React$Node = () => {
const [result, setResult] = useState('');
function onWebViewMessage(event) {
console.log('onWebViewMessage', event.nativeEvent.data);
setResult(JSON.stringify(JSON.parse(event.nativeEvent.data), null, 2));
}
return (
<>
<StatusBar barStyle="dark-content" />
<SafeAreaView style={styles.container}>
<View style={styles.webViewContainer}>
<WebView
source={{ uri: 'https://zikro.gr/dbg/so/60052061/react' }}
style={styles.webWiew}
injectedJavaScript={WebView_Injection_postMessage}
onMessage={onWebViewMessage}
/>
</View>
<View style={styles.resultView}>
<Text style={styles.resultText}>{result}</Text>
</View>
</SafeAreaView>
</>
);
};
`;
The web app at https://zikro.gr/dbg/so/60052061/react is a React 16 development app with the following code:
<script src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/#babel/standalone/babel.min.js"></script>
<script type="text/babel">
class App extends React.Component {
postMessage = () => {
console.log('Posting message...');
const reactNativeObj = {
location: window.location.href,
title: document.title,
dt: (new Date).toISOString()
}
window.postMessage(JSON.stringify(reactNativeObj), '*');
}
render() {
return (
<button onClick={this.postMessage} id="post-message">Post Message</button>
)
};
}
ReactDOM.render(<App/>, document.getElementById('main'));
</script>
Last but not least, window.ReactNativeWebView.postMessage accepts only one parameter with the data to be send and it has to be a string.
window.ReactNativeWebView.postMessage only accepts one argument which must be a string.
Here is a screen capture with the working app:
Related
I am trying to get some position information from a device, I am using React-native, Expo, and ExpoGo on android mobile, I am not using simulators, the application is working with ExpoGo on Android, but geolocation is not working, it sends this error message every time:
window.navigator.geolocation.getCurrentPosition is not available. Import and execute installWebGeolocationPolyfill() from expo-location to add it, or use the expo-location APIs instead.
Please help me find the solution to this issue.
This is the App code used:
import { Text, View} from "react-native";
import styles from "./styles";
const API_KEY = "";
const URL = `https://maps.google.com/maps/api/geocode/json?key=${API_KEY}&latlng=`;
export default function WhereAmI() {
const [address, setAddress] = useState("loading...");
const [longitude, setLongitude] = useState();
const [latitude, setLatitude] = useState();
useEffect(() => {
function setPosition({ coords: { latitude, longitude } }) {
setLongitude(longitude);
setLatitude(latitude);
fetch(`${URL}${latitude},${longitude}`)
.then(
(resp) => resp.json(),
(e) => console.error(e)
)
.then(({ results }) => {
setAddress(results[0].formatted_address);
});
}
navigator.geolocation.getCurrentPosition(setPosition);
let watcher = navigator.geolocation.watchPosition(
setPosition,
(err) => console.error(err),
{ enableHighAccuracy: true }
);
return () => {
navigator.geolocation.clearWatch(watcher);
};
});
return (
<View style={styles.container}>
<Text style={styles.label}>Address: {address}</Text>
<Text style={styles.label}>Latitude: {latitude}</Text>
<Text style={styles.label}>Longitude: {longitude}</Text>
</View>
);
}
I fixed this problem, and this worked for me.
geolocation on iOS and Android:
I installed the expo-location package by running expo install expo-location.
I imported expo location in App.js as Location: import * as Location from 'expo-location'.
I used the #installWebGeolocationPolyfill function that polyfills #navigator.geolocation for interop with the core React Native
and Web API approach to geolocation just before using of the #navigator.geolocation.getCurrentPosition like
this:
# ... some code
Location.installWebGeolocationPolyfill()
navigator.geolocation.getCurrentPosition(setPosition);
# ... some code
2.The error 'formatted_address' is undefined:
That because there is no API_KEY, you should get an API key from Google, instead, I just Added it manually into the #setPosition function like this:
#...some code
.then(
({ results }) => {
results[0]={formatted_address:"here"}
setAddress(results[0].formatted_address)
},
#...some code
I have a requirement to open an App screen using user selections on a website, where when user select an item it should openup the relevant page in the Mobile App.
But the website should be loaded inside a webview within the same APP.
Here I used Firebase dynamic links to manage this navigation.
I can open up the website from chrome browser in the emulator and open up the App navigated to exact page as required.
But when I click the same component from the webview within the app it throws up the following error and App wont open up the page that the firebase dynamic link should open up as in the first case of the chrome browser.
Can't open url: intent://example.page.link/getapp#Intent;package=com.google.android.gms;action=com.google.firebase.dynamiclinks.VIEW_DYNAMIC_LINK;scheme=https;S.browser_fallback_url=https://play.google.com/store/apps/details%3Fid%3Dcom.ubercab;end;
My implementation is as follows,
WebApp
App.js
function App() {
return (
<div>
<Button variant="primary">
Open this in your App
</Button>
<Banner url={"https://testingapp.page.link/getapp"}/>
<Banner url={"https://testingapp.page.link/getapp"}/>
<Banner url={"https://testingapp.page.link/getapp"}/>
<Banner url={"https://testingapp.page.link/getapp"}/>
</div>
);
}
Banner.js
const Banner=(props)=>{
const classes = useStyles();
const [expanded, setExpanded] = React.useState(false);
const styles = {
cardAction: {
display: 'block',
textAlign: 'initial',
height: '100%'
}
}
return (
<div onClick={() => window.open(props.url, "_blank")}>
<Card className={classes.root}>
<CardMedia
className={classes.media}
image= {banner}
title="Offer"
/>
</Card>
</div>
);
};
export default Banner;
On the Mobile App side:
App.jsx
import React, { useEffect } from 'react';
import { StyleSheet, Text, View, Button } from 'react-native';
import dynamicLinks from '#react-native-firebase/dynamic-links';
import { WebView } from 'react-native-webview';
import { NavigationContainer } from '#react-navigation/native';
import { createStackNavigator } from '#react-navigation/stack';
function DetailsScreen({ navigation }) {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Details Screen</Text>
</View>
);
}
function HomeScreen({ navigation }) {
async function buildLink() {
console.log('building link');
const link = await dynamicLinks().buildLink({
link: 'https://invertase.io',
// domainUriPrefix is created in your firebase console
domainUriPrefix: 'https://testingapp.page.link',
// optional set up which updates firebase analytics campaign
// "banner". This also needs setting up before hand
analytics: {
campaign: 'banner',
},
});
return link;
}
const handleDynamicLink = link => {
// Handle dynamic link inside your own application
console.log('Received URfffL:' + link.url);
if (link.url === 'https://example.com/packageview') {
console.log('hit package view');
navigation.push('packageview');
}
};
useEffect(() => {
const unsubscribe = dynamicLinks().onLink(handleDynamicLink);
// When the is component unmounted, remove the listener
dynamicLinks()
.getInitialLink()
.then(link => {
console.log('Received URL:' + link.url);
if (link.url === 'https://example.com/packageview') {
console.log('hit package view');
navigation.push('packageview');
}
});
return () => unsubscribe();
}, []);
return (
<WebView source={{ uri: 'http://10.0.2.2:3000/' }}/>
);
}
export default function App() {
const Stack = createStackNavigator();
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="packageview" component={DetailsScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
Do anyone have any experience in anything related to this? On what's going wrong in here?
There is a solution, But more like a work around so it seems like webview blocking up requests with "intent://" head, as a solution "intent://" can be added to whitelist of the WebView. check the code below,
<WebView
originWhitelist={['intent://']}
source={{ uri: 'http://10.0.2.2:3000/' }}/>
But the user experience might not be that great as this makes the URL to be opened from the web browser and web browser will the one doing the naigation to the page inside the app.
Hey for me this worked .
add your dynamic link in webview orginWhitlelist without https/http
add intent:// too
<WebView
source={{ uri: redirect_url }}
originWhitelist={["intent://*", "applink.page.link/back"]}
/>
then follow https://rnfirebase.io/dynamic-links/usage documentication to Listen for Dynamic Links
make sure to select Open the deep link in your Android App for android and Open the deep link in your Apple app for ios while creating dynamic link in firbase
Is there any way to abort a fetch request on react-native app ?
class MyComponent extends React.Component {
state = { data: null };
componentDidMount = () =>
fetch('http://www.example.com')
.then(data => this.setState({ data }))
.catch(error => {
throw error;
});
cancelRequest = () => {
//???
};
render = () => <div>{this.state.data ? this.state.data : 'loading'}</div>;
}
i tried the abort function from AbortController class but it's not working !!
...
abortController = new window.AbortController();
cancelRequest = () => this.abortController.abort();
componentDidMount = () =>
fetch('http://www.example.com', { signal: this.abortController.signal })
....
Any help please !
You don't need any polyfill anymore for abort a request in React Native 0.60 changelog
Here is a quick example from the doc of react-native:
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* #format
* #flow
*/
'use strict';
const React = require('react');
const {Alert, Button, View} = require('react-native');
class XHRExampleAbortController extends React.Component<{}, {}> {
_timeout: any;
_submit(abortDelay) {
clearTimeout(this._timeout);
// eslint-disable-next-line no-undef
const abortController = new AbortController();
fetch('https://facebook.github.io/react-native/', {
signal: abortController.signal,
})
.then(res => res.text())
.then(res => Alert.alert(res))
.catch(err => Alert.alert(err.message));
this._timeout = setTimeout(() => {
abortController.abort();
}, abortDelay);
}
componentWillUnmount() {
clearTimeout(this._timeout);
}
render() {
return (
<View>
<Button
title="Abort before response"
onPress={() => {
this._submit(0);
}}
/>
<Button
title="Abort after response"
onPress={() => {
this._submit(5000);
}}
/>
</View>
);
}
}
module.exports = XHRExampleAbortController;
I've written quite a bit actually about this subject.
You can also find the first issue about the OLD lack of AbortController in React Native opened by me here
The support landed in RN 0.60.0 and you can find on my blog an article about this and another one that will give you a simple code to get you started on making abortable requests (and more) in React Native too. It also implements a little polyfill for non supporting envs (RN < 0.60 for example).
You can Actually achieve this by installing this polyfill abortcontroller-polyfill
Here is a quick example of cancelling requests:
import React from 'react';
import { Button, View, Text } from 'react-native';
import 'abortcontroller-polyfill';
export default class HomeScreen extends React.Component {
state = { todos: [] };
controller = new AbortController();
doStuff = () => {
fetch('https://jsonplaceholder.typicode.com/todos',{
signal: this.controller.signal
})
.then(res => res.json())
.then(todos => {
alert('done');
this.setState({ todos })
})
.catch(e => alert(e.message));
alert('calling cancel');
this.controller.abort()
}
render(){
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Details Screen</Text>
<Button title="Do stuff" onPress={() => { this.doStuff(); }} />
</View>
)
}
}
So basically in this example, once you click the 'doStuff' button, the request is immediately cancelled and you never get the 'done' alert. To be sure, it works, try and comment out these lines and click the button again:
alert('calling cancel');
this.controller.abort()
This time you will get the 'done' alert.
This is a simple example of hoe you can cancel a request using fetch in react native, feel free to adopt this to your own use case.
Here is a link to a demo on snackexpo https://snack.expo.io/#mazinoukah/fetch-cancel-request
hope it helps :)
the best solution is using rxjs observables + axios/fetch instead of promises, abort a request => unsubscribe an observable :
import Axios from "axios";
import {
Observable
} from "rxjs";
export default class HomeScreen extends React.Component {
subs = null;
doStuff = () => {
let observable$ = Observable.create(observer => {
Axios.get('https://jsonplaceholder.typicode.com/todos', {}, {})
.then(response => {
observer.next(response.data);
observer.complete();
})
});
this.subs = observable$.subscribe({
next: data => console.log('[data] => ', data),
complete: data => console.log('[complete]'),
});
}
cancel = () =>
if (this.subs) this.subs.unsubscribe()
componentWillUnmount() {
if (this.subs) this.subs.unsubscribe();
}
}
That is it :)
Let's say I have this View containing a WebView and a Cart button over the WebView
export default class App extends Component<{}> {
constructor(props){
super(props);
}
render() {
return (
<View style={styles.parent}>
<WebView
source={{uri: 'https://mywebsite.com'}} style={styles.fullScreen}
injectedJavaScript={jsCode}
javaScriptEnabledAndroid={true}
/>
<View style={styles.floatView}>
<Button
title="Cart"
onPress={toggleCart}
color="black"
/>
</View>
</View>
);
}
}
And when the user click on the button I want to execute this function
const toggleCart = function() {
let jsCode = "app.trigger('toggle:cart');";
//execute javascript code on webView
}
Is it something possible to do on React-Native?
Thanks
First get a reference to your webview and then do this:
this.webview.postMessage("Hello from RN");
//to get this data into webview
...
<script>
document.addEventListener("message", function(data) {
// call whatever function you need
});
</script>
...
That one is nice.
You can use react-native-webview-bridge
module which provides communication between react natibe code and webview, so you can send a message on click of button.
const injectScript = `
(function () {
if (WebViewBridge) {
WebViewBridge.onMessage = function (message) {
if (message === "hello from react-native") {
WebViewBridge.send("got the message inside webview");
}
};
WebViewBridge.send("hello from webview");
}
}());
`;
var Sample2 = React.createClass({
onBridgeMessage(message){
const { webviewbridge } = this.refs;
switch (message) {
case "hello from webview":
webviewbridge.sendToBridge("hello from react-native");
break;
case "got the message inside webview":
console.log("we have got a message from webview! yeah");
break;
}
},
render() {
return (
<WebViewBridge
ref="webviewbridge"
onBridgeMessage={this.onBridgeMessage.bind(this)}
injectedJavaScript={injectScript}
source={{uri: "http://google.com"}}/>
);
}
});
Above example explains it clearly and you can use it.
You can simply use the injectJavaScript method.
Give your WebView ref, then when you click the button:
this.webview.injectJavaScript("<your code here>");
I am using React-Native to build an Android application. I am also using the DocuSign Java API.
My workflow submits a RecipientViewRequest and receives a URL that the android app must view within a WebView to perform the signing:
https://github.com/docusign/docusign-rest-recipes/blob/master/core_recipes/CoreRecipes.java
https://facebook.github.io/react-native/docs/webview.html
This is the code:
'use strict';
var React = require('react-native');
var {
StyleSheet,
View,
WebView,
Component,
} = React;
class DocuSignView extends Component {
render() {
var recipientViewURL = this.props.route.data.url;
console.log('Recipient URL is: ' + recipientViewURL);
return (
<View style={styles.container}>
<WebView
source={{uri: recipientViewURL, method: 'GET'}}
scalesPageToFit={true}
javaScriptEnabled={true}
domStorageEnabled={true}
>
</WebView>
</View>
);
}
}
The DocuSign servers redirect the WebView to a returnURL that I provide.
How can I specify a returnURL that will return control back to my React-Native android application?
(A generic answer for React-Native either iOS or Android would be even better).
One option is to use the onNavigationStateChange event handler to intercept and seize control from the WebView:
render() {
var recipientViewURL = this.props.route.data.url;
console.log('Recipient URL is: ' + recipientViewURL);
return (
<View style={styles.container}>
<WebView
source={{uri: recipientViewURL, method: 'GET'}}
scalesPageToFit={true}
javaScriptEnabled={true}
domStorageEnabled={true}
onNavigationStateChange={this.whenNavigationStateChanges.bind(this)}
>
</WebView>
</View>
);
}
whenNavigationStateChanges(navState){
var navigator = this.props.navigator;
var parsed = Url.parse(navState.url, true);
if (parsed.hostname == 'YOUR_HOSTNAME'){
console.log("Event is: " + parsed.query.event);
navigator.pop();
}
}
You will need to configure Custom URL Schemes.
For Android: http://developer.android.com/training/basics/intents/filters.html
For iOS: http://iosdevelopertips.com/cocoa/launching-your-own-application-via-a-custom-url-scheme.html