I'm trying to load a Twitter widget inside a WebView in my React Native app, but it seems that my injected Javascript is not working for some reason.
What I'm doing is loading Twitter script asynchronously (function taken from here), then executing twttr.widgets.load() function when script is loaded in order to draw the widget.
Is it possible to do it, or am I trying an impossible with default Webview component?
Here is my code:
render() {
let utils = ' \
function loadScript(src, callback) { \
var s, r, t; \
r = false; \
s = document.createElement("script"); \
s.type = "text/javascript"; \
s.src = src; \
s.onload = s.onreadystatechange = function() { \
if ( !r && (!this.readyState || this.readyState == "complete") ) { \
r = true; \
callback(); \
} \
}; \
t = document.getElementsByTagName("script")[0]; \
t.parentNode.insertBefore(s, t); \
} \
';
let twitter = ' \
loadScript("//platform.twitter.com/widgets.js", function () { \
twttr.widgets.load(); \
}); \
';
let JS = utils + twitter;
let source = '<blockquote class="twitter-tweet" data-lang="es"><p lang="en" dir="ltr">8 TED Talks to inspire projects with kids: https://twitter.com/TEDTalks/status/758116657638309896 pic.twitter.com/HMmYAeP7Km</p>— TED Talks (#TEDTalks) 27 de julio de 2016</blockquote>';
return (
<WebView
source={{html: source}}
javaScriptEnabled={true}
injectedJavascript={ JS }
/>
);
}
It seems like loading scripts inside a WebView is not working (I don't know why). So to make it work, I need to load Twitter's script using script tag and concatenating it to my HTML code.
render() {
let JS = '<script type="text/javascript" src="https://platform.twitter.com/widgets.js"></script>';
let source = JS + '<blockquote class="twitter-tweet" data-lang="es"><p lang="en" dir="ltr">8 TED Talks to inspire projects with kids: https://twitter.com/TEDTalks/status/758116657638309896 pic.twitter.com/HMmYAeP7Km</p>— TED Talks (#TEDTalks) 27 de julio de 2016</blockquote>';
return (
<WebView
source={{html: source}}
javaScriptEnabled={true}
/>
);
}
I hacked it together like this and it mostly works, although height isn't variable which isn't ideal:
import React from "react"
import ReactNative from "react-native"
const { View, WebView, StyleSheet, ActivityIndicator } = ReactNative
export default class EmbeddedTweet extends React.Component {
constructor(props) {
super(props)
this.state = {
loading: true,
embedHtml: null,
}
}
componentDidMount() {
this.setupEmbed()
}
setupEmbed() {
let tweetUrl =
"https://publish.twitter.com/oembed?url=" + encodeURIComponent(this.props.url)
fetch(tweetUrl, { method: "GET", headers: { Accepts: "application/json" } }).then(
resp => {
resp.json().then(json => {
let html = json.html
this.setState({
loading: false,
embedHtml: html,
})
})
}
)
}
renderLoading() {
if (this.state.loading) {
return (
<View style={styles.loadingWrap}>
<ActivityIndicator />
</View>
)
}
}
renderEmbed() {
if (this.state.embedHtml) {
let html = `<!DOCTYPE html>\
<html>\
<head>\
<meta charset="utf-8">\
<meta name="viewport" content="width=device-width, initial-scale=1.0">\
</head>\
<body>\
${this.state.embedHtml}\
</body>\
</html>`
return (
<View style={styles.webviewWrap}>
<WebView source={{ html: html }} style={styles.webview} />
</View>
)
}
}
render() {
return (
<View
style={[styles.container, { height: this.props.height || 300 }, this.props.style]}
>
{this.renderLoading()}
{this.renderEmbed()}
</View>
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
loadingWrap: {
flex: 1,
backgroundColor: "#999",
justifyContent: "center",
alignItems: "center",
},
webviewWrap: {
flex: 1,
borderWidth: 1,
borderRadius: 4,
borderColor: "#999",
},
webview: {
flex: 1,
},
})
Related
Voice recording comes empty in safari iphone always, my code is as below for my common component, its working in safari on mac and chrome easily
import React, { useEffect, useRef, useState } from "react";
import { useRouter } from "next/router";
import PropTypes from "prop-types";
import { useReactMediaRecorder } from "react-media-recorder";
import WaveSurfer from "wavesurfer.js";
import MicrophonePlugin from "wavesurfer.js/dist/plugin/wavesurfer.microphone";
import Button from "#mui/material/Button";
import Grid from "#mui/material/Grid";
import Typography from "#mui/material/Typography";
import StyledButton from "../StyledButton";
import Fab from "#mui/material/Fab";
// Icons
import MicIcon from "#mui/icons-material/Mic";
import StopIcon from "#mui/icons-material/Stop";
import DeleteIcon from "#mui/icons-material/Delete";
import Waveform from "./Waveform";
import styled from "#emotion/styled";
const FabAudio = styled(Fab)`
background-color: #fd0d1b;
color: #fff;
&:hover {
background-color: #fd0d1b;
color: #fff;
}
`;
const Small = styled("div")`
font-size: 10px;
`;
function VoiceInput({
title,
required,
id,
totalQuestions,
primaryButtonTitle,
secondaryButtonTitle,
nextRoute,
handleEndSurvey,
handleResponse,
}) {
const [error, setError] = useState(false);
const [errorMessage, setErrorMessage] = useState("");
const [blob, setBlob] = useState(null);
const router = useRouter();
const waveSurferRef = useRef();
const containerRef = useRef();
let timeout;
let context, processor;
const { status, startRecording, stopRecording, mediaBlobUrl, clearBlobUrl } =
useReactMediaRecorder({
audio: true,
onStop: (blobUrl, blob) => {
console.log("Testing blob" + blob);
setBlob(blob);
},
});
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
useEffect(() => {
if (status === "recording") {
if (isSafari) {
// Safari 11 or newer automatically suspends new AudioContext's that aren't
// created in response to a user-gesture, like a click or tap, so create one
// here (inc. the script processor)
let AudioContext = window.AudioContext || window.webkitAudioContext;
context = new AudioContext();
processor = context.createScriptProcessor(1024, 1, 1);
}
waveSurferRef.current = WaveSurfer.create({
container: containerRef.current,
responsive: true,
barWidth: 2,
height: 80,
barHeight: 3,
barMinHeight: 1,
barRadius: 3,
barWidth: 3,
barGap: 5,
cursorWidth: 0,
waveColor: "red",
plugins: [MicrophonePlugin.create()],
});
const microphone = waveSurferRef.current.microphone;
timeout = setTimeout(() => {
microphone.stop();
stopRecording();
waveSurferRef.current.destroy();
}, 60000);
microphone.start();
}
if (status === "stopped") {
const microphone = waveSurferRef.current.microphone;
microphone.stop();
waveSurferRef.current.destroy();
clearTimeout(timeout);
}
return () => {
if (status === "recording") {
const microphone = waveSurferRef.current.microphone;
microphone.stop();
waveSurferRef.current.destroy();
clearTimeout(timeout);
}
};
}, [status]);
const handleNext = async () => {
if (required && !mediaBlobUrl) {
setError(true);
setErrorMessage("Please record the message");
return;
}
if (["recording", "paused"].includes(status)) {
setError(true);
setErrorMessage("Please stop the recording");
return;
}
if (mediaBlobUrl) {
const uniqueId =
Date.now().toString(36) + Math.random().toString(36).substring(2);
const audiofile = new File([blob], `${uniqueId}.webm`, {
type: "audio/webm",
});
const isLastAnswer = +id === totalQuestions ? true : false;
const res = await handleResponse(audiofile, isLastAnswer);
if (isLastAnswer) {
res && handleEndSurvey();
} else {
res && router.push(nextRoute);
}
} else {
if (+id === totalQuestions) {
handleEndSurvey();
} else {
router.push(nextRoute);
}
}
};
const handlePrev = () => {
router.back();
};
const removeAudio = () => {
setError(false);
clearBlobUrl();
setBlob(null);
};
return (
<Grid container spacing={5} height="100%" alignItems="center">
{/* question section */}
<Grid item xs={12}>
<Typography variant="h2" fontWeight={550}>
{title}
</Typography>
</Grid>
{/*Input Section */}
<Grid item md={2} xs={0}></Grid>
<Grid item md={8} xs={12}>
{mediaBlobUrl && <Waveform audio={mediaBlobUrl} />}
{["recording", "paused"].includes(status) && (
<Grid container>
<Grid item xs={12} ref={containerRef}></Grid>
</Grid>
)}
{mediaBlobUrl && (
<div>
<FabAudio aria-label="add" sx={{ mt: 2 }} onClick={removeAudio}>
<DeleteIcon />
</FabAudio>
</div>
)}
{["idle", "stopped"].includes(status) && !mediaBlobUrl && (
<>
<FabAudio
color="secondary"
aria-label="add"
sx={{ mt: 2 }}
onClick={() => {
startRecording();
setError(false);
}}
>
<MicIcon />
</FabAudio>
<Typography mt={2}>Hit Record to Start</Typography>
<Small>Speak close to the microphone for better response.</Small>
</>
)}
{["recording", "paused"].includes(status) && (
<>
<FabAudio aria-label="add" onClick={stopRecording}>
<StopIcon />
</FabAudio>
</>
)}
{error && (
<Typography mt={2} color="red">
{errorMessage}
</Typography>
)}
</Grid>
<Grid item md={2} xs={0}></Grid>
{/* Button Section */}
<Grid item md={3} xs={0}></Grid>
{secondaryButtonTitle && (
<Grid item md={3} xs={12}>
<Button
sx={{ width: "160px" }}
variant="outlined"
onClick={handlePrev}
>
{secondaryButtonTitle}
</Button>
</Grid>
)}
<Grid item md={secondaryButtonTitle ? 3 : 6} xs={12}>
<StyledButton onClick={handleNext}>
{+id === totalQuestions ? "Submit" : primaryButtonTitle}
</StyledButton>
</Grid>
<Grid item md={3} xs={0}></Grid>
</Grid>
);
}
VoiceInput.propTypes = {
title: PropTypes.string,
required: PropTypes.bool,
id: PropTypes.string,
totalQuestions: PropTypes.number,
primaryButtonTitle: PropTypes.string,
secondaryButtonTitle: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
nextRoute: PropTypes.string,
};
export default VoiceInput;
The blob size shows up as 0, and shows type: audio/wav is that an issue?
I'm setting up a page where I can Id with a Google account. This is my code:
import React from "react"
import { StyleSheet, Text, View, Image, Button } from "react-native"
import * as Google from "expo-google-app-auth";
export default class App extends React.Component {
constructor(props) {
super(props)
this.state = {
signedIn: false,
name: "",
photoUrl: ""
}
}
signIn = async () => {
try {
const result = await Google.logInAsync({
androidClientId:
"1051966217196.apps.googleusercontent.com",
iosClientId:
"1051966217196.apps.googleusercontent.com",
scopes: ["profile", "email"]
})
if (result.type === "success") {
this.setState({
signedIn: true,
name: result.user.name,
photoUrl: result.user.photoUrl
})
} else {
console.log("cancelled")
}
} catch (e) {
console.log("error", e)
}
}
render() {
return (
<View style={styles.container}>
{this.state.signedIn ? (
<LoggedInPage name={this.state.name} photoUrl={this.state.photoUrl} />
) : (
<LoginPage signIn={this.signIn} />
)}
</View>
)
}
}
const LoginPage = props => {
return (
<View>
<Text style={styles.header}>Sign In With Google</Text>
<Button title="Sign in with Google" onPress={() => props.signIn()} />
</View>
)
}
const LoggedInPage = props => {
return (
<View style={styles.container}>
<Text style={styles.header}>Welcome:{props.name}</Text>
<Image style={styles.image} source={{ uri: props.photoUrl }} />
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
alignItems: "center",
justifyContent: "center"
},
header: {
fontSize: 25
},
image: {
marginTop: 15,
width: 150,
height: 150,
borderColor: "rgba(0,0,0,0.2)",
borderWidth: 3,
borderRadius: 150
}
})
On Android it's ok, everything is fine but in IOS I have that mistake :
Google 400 - That's an error - Error : redirect_uri_mismatch
Where is it goes wrong? I'm new to react native so I need detailed explanations.
I changed the bundle identifier to host.exp.exponent in Google credentials and it works. It's weird because I didn't change it in app.json. But it works.
With react-native, I implemented IOS CameraRoll that fetches 300 images from 'Camera Roll' Album on first and keep fetching 300 images whenever scroll reaches the end. Below is My code. SalmonCameraRoll.js
import React from 'react'
import {
View,
Text,
TouchableHighlight,
Modal,
StyleSheet,
Button,
CameraRoll,
Image,
Dimensions,
ScrollView,
FlatList,
} from 'react-native'
import Share from 'react-native-share';
import RNFetchBlob from 'react-native-fetch-blob';
let styles
const { width, height } = Dimensions.get('window')
const fetchAmount = 300;
class SalmonCameraRoll extends React.Component {
static navigationOptions = {
title: 'Salmon Camera Roll',
}
constructor(props) {
super(props);
this.state = {
photos: [],
// index: null,
lastCursor: null,
noMorePhotos: false,
loadingMore: false,
refreshing: false,
};
this.tryGetPhotos = this.tryGetPhotos.bind(this);
this.getPhotos = this.getPhotos.bind(this);
this.appendPhotos = this.appendPhotos.bind(this);
this.renderImage = this.renderImage.bind(this);
this.onEndReached = this.onEndReached.bind(this);
this.getPhotos({first: fetchAmount, assetType: 'Photos'});
}
componentDidMount() {
this.subs = [
this.props.navigation.addListener('didFocus', () => {
this.getPhotos({first: fetchAmount, assetType: 'Photos'});
}),
];
}
componentWillUnmount() {
this.subs.forEach(sub => sub.remove());
}
tryGetPhotos = (fetchParams) => {
if (!this.state.loadingMore) {
this.setState({ loadingMore: true }, () => { this.getPhotos(fetchParams)})
}
}
getPhotos = (fetchParams) => {
if (this.state.lastCursor) {
fetchParams.after = this.state.lastCursor;
}
CameraRoll.getPhotos(fetchParams).then(
r => this.appendPhotos(r)
)
}
appendPhotos = (data) => {
const photos = data.edges;
const nextState = {
loadingMore: false,
};
if (!data.page_info.has_next_page) {
nextState.noMorePhotos = true;
}
if (photos.length > 0) {
nextState.lastCursor = data.page_info.end_cursor;
nextState.photos = this.state.photos.concat(photos);
this.setState(nextState);
}
}
onEndReached = () => {
if (!this.state.noMorePhotos) {
this.tryGetPhotos({first: fetchAmount, assetType: 'Photos'});
}
}
renderImage = (photo, index) => {
return (
<TouchableHighlight
style={{borderTopWidth: 1, borderRightWidth: 1, borderColor: 'white'}}
key={index}
underlayColor='transparent'
onPress={() => {
this.props.navigation.navigate('Camera', { backgroundImageUri: photo.node.image.uri })
}
}
>
<Image
style={{
width: width/3,
height: width/3
}}
representation={'thumbnail'}
source={{uri: photo.node.image.uri}}
/>
</TouchableHighlight>
)
}
render() {
return (
<View style={styles.container}>
<View style={styles.modalContainer}>
<FlatList
numColumns={3}
data={this.state.photos}
initialNumToRender={fetchAmount}
onEndReachedThreshold={500}
onEndReached={this.onEndReached}
refreshing={this.state.refreshing}
onRefresh={() => this.tryGetPhotos({first: fetchAmount, assetType: 'Photos'})}
keyExtractor={(item, index) => index}
renderItem={({ item, index }) => (
this.renderImage(item, index)
)}
/>
</View>
</View>
)
}
}
styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center'
},
modalContainer: {
// paddingTop: 20,
flex: 1,
},
scrollView: {
flexWrap: 'wrap',
flexDirection: 'row'
},
shareButton: {
position: 'absolute',
width,
padding: 10,
bottom: 0,
left: 0
}
})
export default SalmonCameraRoll
Problem is that in circumstance of a lot of images(about 10000 images) in 'Camera Roll' album, each image component was loaded so slowly that it was also loaded too slowly when scrolling accordingly.
In other famous apps like Facebook or Instagram, it loads all images quickly at once without fetching whenever scroll reaches end.
How can i make my Image component load fast? Or best of all(if possible), how can i make my CameraRoll load all images quickly at once without fetching whenever scroll reaches end?
Thank you.
I'm building an app with React-Native.
I'm trying to render a URL with a Webview, even some basic HTML or a site like m.facebook.com will not render in iOS. Tried different solution like other Webviews but none give results.
When in Android i don't have any problems and the page(s) will render just fine. Am i missing some key information.
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { actions } from 'react-native-navigation-redux-helpers';
import { Container, Header, Title, Content, Button, Icon, Footer } from 'native-base';
import { Platform, WebView } from 'react-native';
import FooterTabs from '../../components/footerTabs/FooterTabs';
import { openDrawer } from '../../actions/drawer';
import { setWebsiteUrl } from '../../actions/website';
import styles from './styles';
const {
pushRoute,
popRoute,
} = actions;
class Website extends Component {
constructor(props, context) {
super(props, context);
this.state = {};
this.openFrame = this.openFrame.bind(this);
}
popRoute() {
this.props.popRoute(this.props.navigation.key);
}
pushRoute(route, index) {
this.props.pushRoute({ key: route, index: 1 }, this.props.navigation.key);
}
openFrame(url, name) {
this.props.setWebsiteUrl(url, name);
this.pushRoute('website', 2);
}
render() {
const { props } = this;
const { website, totalQuantity } = props;
const { frameUrl, frameName } = website;
return (
<Container style={styles.container} theme={deenTheme}>
<Header toolbarDefaultBg="#FFF" toolbarTextColor="FBFAFA">
<Button transparent onPress={this.props.openDrawer}>
<Icon name="ios-menu" />
</Button>
<Title style={styles.headerText}>{frameName}</Title>
<Button transparent onPress={() => this.openFrame('/cart', 'Winkelwagen')}>
<Icon style={{ fontSize: 25 }} name="md-basket" />
</Button>
<Button transparent>
<Icon style={{ fontSize: 25 }} name="md-search" />
</Button>
</Header>
<Content>
<WebView
source={{ uri: frameUrl }}
startInLoadingState
javaScriptEnabledAndroid
javaScriptEnabled
domStorageEnabled
scrollEnabled
style={{ flex: 1, width: 320 }}
/>
</Content>
<Footer theme={deenTheme}>
<FooterTabs />
</Footer>
</Container>
);
}
}
Website.propTypes = {
totalQuantity: React.PropTypes.number,
openDrawer: React.PropTypes.func,
setWebsiteUrl: React.PropTypes.func,
popRoute: React.PropTypes.func,
pushRoute: React.PropTypes.func,
navigation: React.PropTypes.shape({
key: React.PropTypes.string,
}),
};
function bindAction(dispatch) {
return {
openDrawer: () => dispatch(openDrawer()),
popRoute: key => dispatch(popRoute(key)),
pushRoute: (route, key) => dispatch(pushRoute(route, key)),
setWebsiteUrl: (url, name) => dispatch(setWebsiteUrl(url, name)),
};
}
const mapStateToProps = state => ({
navigation: state.cardNavigation,
website: state.website,
totalQuantity: state.cart.totalQuantity,
});
export default connect(mapStateToProps, bindAction)(Website);
UPDATE!
in iOS i need to configure style width & height else it won't work.
I'm implementing a WebView with dynamic height. I found the solution that works like a charm on iOS and doesn't work on android. The solution uses JS inside the WV to set the title to the value of the content height. Here's the code:
...
this.state = {webViewHeight: 0};
...
<WebView
source={{html: this.wrapWevViewHtml(this.state.content)}}
style={{width: Dimensions.get('window').width - 20, height: this.state.webViewHeight}}
scrollEnabled={false}
javaScriptEnabled={true}
injectedJavaScript="window.location.hash = 1;document.title = document.height;"
onNavigationStateChange={this.onWebViewNavigationStateChange.bind(this)}
/>
...
onWebViewNavigationStateChange(navState) {
// navState.title == height on iOS and html content on android
if (navState.title) {
this.setState({
webViewHeight: Number(navState.title)
});
}
}
...
But on android the value of the title inside onWebViewNavigationStateChange is equal to page content.
What am I doing wrong?
I was baffled by this too. It actually works but it's hard to debug why it does not work because Chrome remote debugging is not enabled for the React Native WebViews on Android.
I had two issues with this:
The script I injected to the Webview contained some single line comments and on Android all the line breaks are removed (another bug?). It caused syntax errors in the WebView.
On the first call the title content indeed is the full content of the Webview. No idea why but on latter calls it's the height. So just handle that case.
Here's the code I'm using now which on works on React Native 0.22 on Android and iOS
import React, {WebView, View, Text} from "react-native";
const BODY_TAG_PATTERN = /\<\/ *body\>/;
// Do not add any comments to this! It will break line breaks will removed for
// some weird reason.
var script = `
;(function() {
var wrapper = document.createElement("div");
wrapper.id = "height-wrapper";
while (document.body.firstChild) {
wrapper.appendChild(document.body.firstChild);
}
document.body.appendChild(wrapper);
var i = 0;
function updateHeight() {
document.title = wrapper.clientHeight;
window.location.hash = ++i;
}
updateHeight();
window.addEventListener("load", function() {
updateHeight();
setTimeout(updateHeight, 1000);
});
window.addEventListener("resize", updateHeight);
}());
`;
const style = `
<style>
body, html, #height-wrapper {
margin: 0;
padding: 0;
}
#height-wrapper {
position: absolute;
top: 0;
left: 0;
right: 0;
}
</style>
<script>
${script}
</script>
`;
const codeInject = (html) => html.replace(BODY_TAG_PATTERN, style + "</body>");
/**
* Wrapped Webview which automatically sets the height according to the
* content. Scrolling is always disabled. Required when the Webview is embedded
* into a ScrollView with other components.
*
* Inspired by this SO answer http://stackoverflow.com/a/33012545
* */
var WebViewAutoHeight = React.createClass({
propTypes: {
source: React.PropTypes.object.isRequired,
injectedJavaScript: React.PropTypes.string,
minHeight: React.PropTypes.number,
onNavigationStateChange: React.PropTypes.func,
style: WebView.propTypes.style,
},
getDefaultProps() {
return {minHeight: 100};
},
getInitialState() {
return {
realContentHeight: this.props.minHeight,
};
},
handleNavigationChange(navState) {
if (navState.title) {
const realContentHeight = parseInt(navState.title, 10) || 0; // turn NaN to 0
this.setState({realContentHeight});
}
if (typeof this.props.onNavigationStateChange === "function") {
this.props.onNavigationStateChange(navState);
}
},
render() {
const {source, style, minHeight, ...otherProps} = this.props;
const html = source.html;
if (!html) {
throw new Error("WebViewAutoHeight supports only source.html");
}
if (!BODY_TAG_PATTERN.test(html)) {
throw new Error("Cannot find </body> from: " + html);
}
return (
<View>
<WebView
{...otherProps}
source={{html: codeInject(html)}}
scrollEnabled={false}
style={[style, {height: Math.max(this.state.realContentHeight, minHeight)}]}
javaScriptEnabled
onNavigationStateChange={this.handleNavigationChange}
/>
{process.env.NODE_ENV !== "production" &&
<Text>Web content height: {this.state.realContentHeight}</Text>}
</View>
);
},
});
export default WebViewAutoHeight;
As gist https://gist.github.com/epeli/10c77c1710dd137a1335
Loading a local HTML file on the device and injecting JS was the only method I found to correctly set the title / hash in Android.
/app/src/main/assets/blank.html
<!doctype html>
<html>
<head>
<title id="title">Go Web!</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
</style>
</head>
<body>
<div id="content"></div>
<script>
var content = document.getElementById('content');
var fireEvent = function(event, data) {
document.title = data;
window.location.hash = event;
};
var setContent = function(html) {
content.innerHTML = html;
};
</script>
</body>
</html>
And the component
class ResizingWebView extends Component {
constructor(props) {
super(props)
this.state = {
height: 0
}
}
onNavigationStateChange(navState) {
var event = navState.url.split('#')[1]
var data = navState.title
console.log(event, data)
if (event == 'resize') {
this.setState({ height: data })
}
}
render() {
var scripts = "setContent('<h1>Yay!</h1>');fireEvent('resize', '300')";
return (
<WebView
source={{ uri: 'file:///android_asset/blank.html' }}
injectedJavaScript={ scripts }
scalesPageToFit={ false }
style={{ height: this.state.height }}
onNavigationStateChange={ this.onNavigationStateChange.bind(this) }
/>
)
}
}