How to I send a message from the WebView to React Native? - react-native

I’ve successfully managed to send a message from React Native (RN) to a WebView.
What I’m struggling with, is getting the message back from the WebView to RN. There’s no errors showing - it’s just that the message never gets through.
Here is the code which I’m using:
React Native code
<WebView
source={Platform.OS === 'ios' ?
{ uri: RNFS.LibraryDirectoryPath + "/offlineplayer/index.html" } :
{ uri: 'file:///android_asset/offlineplayer/index.html' }
}
ref={(webView) => this.webView = webView}
originWhitelist={["*"]}
javaScriptEnabled={true}
domStorageEnabled={true}
startInLoadingState={true}
useWebKit={true}
//scrollEnabled={false}
onLoad={() => this.sendPostMessage()}
allowFileAccess={true}
allowUniversalAccessFromFileURLs={true}
allowFileAccessFromFileURLs={true}
allowingReadAccessToURL={RNFS.LibraryDirectoryPath}
onMessage={this.onMessage}
/>
onMessage(event) {
alert(event.nativeEvent.data);
}
WebView Code
window.postMessage("Post message from web", "*");

The only way to communicate the web with react native is by using window.ReactNativeWebView.postMessage and the onMessage prop.
but window.ReactNativeWebView.postMessage only accepts one argument, which must be a string.
So change window.postMessage to window.ReactNativeWebView.postMessage to fix your issue.
For more information check this sample code
import React, { Component } from 'react';
import { View } from 'react-native';
import { WebView } from 'react-native-webview';
export default class App extends Component {
render() {
const html = `
<html>
<head></head>
<body>
<script>
setTimeout(function () {
window.ReactNativeWebView.postMessage("Hello!")
}, 2000)
</script>
</body>
</html>
`;
return (
<View style={{ flex: 1 }}>
<WebView
source={{ html }}
onMessage={event => {
alert(event.nativeEvent.data);
}}
/>
</View>
);
}
}
Hope this helps you. Feel free for doubts.

Related

useEffect won't update code in webview automatically in React Native

I use a stack navigation from reactnavigation. When opening Screen X I want to automatically update "let userAnswer = "";" with a string within a webview. However, the code won't run. It does run and updates the variable when I save my file in visual studio.
I make the code run via useEffect. This is the webview in my WebViewComponent:
import { useState, useContext, useEffect } from "react";
import { StyleSheet, Text, View, Pressable } from "react-native";
import { WebView } from "react-native-webview";
import { FontAwesome } from "#expo/vector-icons";
import DarkModeContext from "../store/darkmode";
const html = `
<!DOCTYPE html>
<html>
...
<script>
function update() {
var idoc = document.getElementById('iframe').contentWindow.document;
idoc.open();
idoc.write(editor.getValue());
idoc.close();
}
let userAnswer = "";
Here is the useEffect that is supposed to run in WebViewComponent:
useEffect(() => {
webref.injectJavaScript(props.defaultContent);
console.log("mounted")
},[]);
return (
<View>
<View style={{ width: "100%", height: "65%", marginTop: 0 }}>
<WebView
ref={(r) => (webref = r)}
androidHardwareAccelerationDisabled={true} // To prevent crash when opening screen
scalesPageToFit={false}
scrollEnabled={false}
source={{ html }}
/>
</View>
I get default content prop from the screen I am using the wWebViewComponent in:
<WebViewComponent
defaultContent = {`userAnswer += "Hello World"
editor.setValue(userAnswer, 1);
`}
/>
It acts as if the component doesn't mount when navigating to it via reactnavigation, though this is of course not the case. The console.log does return "mounted". Any ideas?
Thanks!

What is the best way to indicate loading while a video is loading on Expo React Native app?

I wanted to ask what would be the best way to handle loading for videos on Expo / React Native.
Expo has good documentation on the Video and AV components to handle video / audio:
https://docs.expo.io/versions/latest/sdk/video/
https://docs.expo.io/versions/latest/sdk/av/
I've tried two things so far: '
Using posterSource in a Video component. The problem here is that the poster image doesn't format properly.
This is what my Video component looks like:
const videoStyle = { width: '100%', height: '100%', display: display};
return (
<Video
ref={playbackObject}
source={{uri: source}}
posterSource={require('path/to/file')}
rate={1.0}
volume={1.0}
isMuted={isMuted}
resizeMode="cover"
usePoster={true}
shouldPlay={shouldPlay}
onPlaybackStatusUpdate={_onPlaybackStatusUpdate}
progressUpdateIntervalMillis={50}
isLooping
style={videoStyle}
posterStyle={videoStyle}
>
</Video>
)
I’ve also tried using playbackStatus to see if the video is loaded or buffering and have an activity indicator when the video is loaded or buffering, but because I use states, there is some lag.
My implementation for (2) looks like this:
const [loaded, setLoaded] = useState(false);
const _onPlaybackStatusUpdate = playbackStatus => {
if(playbackStatus.isBuffering){
if(loaded){
setLoaded(false);
}
} else {
if(!loaded){
setLoaded(true);
}
}
}
If loaded = true, we do not show an activity indicator. Else, we do show an activity indicator. The main problem here is there is a lag, which is not great UI.
So with that in mind, what would be people’s recommendation of handling loading for videos? Thanks!!
What you can do is to render an <ActivityIndicator /> as background and when it finishes loading the asset, it will get behind the video (or you could just check if the asset was loaded or not -> optionally rendering it inside <Video />.
<Video
ref={handleVideoRef}
>
<ActivityIndicator size="large" />
</Video>
const handleVideoRef = async component => {
const playbackObject = component;
if (playbackObject) {
await playbackObject.loadAsync(
{ uri: currentVideoURI },
);
}
};
here's my solution for that :
Video component has onLoadStart and onReadyForDisplay props, which indicate when the loading starts and when it's finished.
So we could create a custom component, which would support loading indicator using the Video component imported from expo. So in the end, this would looksomething like this :
import React, {useState} from "react";
import { ActivityIndicator } from "react-native";
import { Video } from "expo-av";
const AppVideo = ({style, ...rest}) => {
return (
<View style={style}>
{isPreloading &&
<ActivityIndicator
animating
color={"gray"}
size="large"
style={{ flex: 1, position:"absolute", top:"50%", left:"45%" }}
/>
}
<Video
{...rest}
onLoadStart={() => setIsPreloading(true)}
useNativeControls
onReadyForDisplay={() => setIsPreloading(false)}
resizeMode="contain"
isLooping
/>
</View>
);
}
export default AppVideo;

React Native: I am getting error while trying to get image from https://cataas.com api

I am getting SyntaxError: Json Parse error: JSON Parse error: Unrecognized token '<'
I'm using https://cataas.com api for a react native app, my task is to generate a list of random kitten images. I tried using fetch method, but also i get error sorce.uri should not be an empty string. How can i solve this problem?
Here is my code:
import React, { Component } from 'react';
import {
Image,
StyleSheet,
Text,
View,
FlatList
} from 'react-native';
class App extends Component {
state = {
photos: '',
}
componentDidMount() {
fetch('https://cataas.com/cat?width=100')
.then(res => res.json())
.then(data => {
this.setState({
photos: data
})
.catch(err => {
console.log('error', err);
alert(err)
})
})
}
render() {
console.log(this.state.photos)
return (
<View style={styles.container}>
<Image
source={{url: this.state.photos}}
style={{height: 100, width: 100}}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#ecf0f1',
}
});
export default App;
There is a typo in your code
Replace url with uri as in the docs
<Image
source={{uri: this.state.photos}}
style={{height: 100, width: 100}}
/>
You don't have to call this api manually, you could directly use the link in the Image component :
<Image
source={{uri: "https://picsum.photos/100/100"}}
style={{height: 100, width: 100}}
/>
EDIT:
Ok it's not as easy as I thought !
I created a first basic version : https://snack.expo.io/#sanjar/so-53434400
And contrary to what I thought it's always the same picture that is displayed.
It's because of react-native cache system that see the same url and decide to not execute the http request again.
then I checked the doc and founda way to fix this issue, but for ios only
I just had to change :
source={{uri: "https://source.unsplash.com/random"}}
by :
source={{uri: "https://source.unsplash.com/random", cache: 'reload'}}
It should work on ios (I don't have a mac with me now), for android I don't know yet, I'll probably investigate later.

How to change uri value of Video component dynamically

I am working with react native, and I am using a video component from the expo. during this how I can change the value for URI attribute dynamically
(As you mentioned in the comment that you already solved the problem and want to play youtube videos)
You can use WebView to play Youtube video.
Working demo: https://snack.expo.io/Syhzx-VvX
Here is the sample code:
import React, { Component } from 'react';
import { StyleSheet, View, WebView, Platform } from 'react-native';
export default class App extends Component<{}> {
render() {
return (
<View style={{ height: 300 }}>
<WebView
style={ styles.WebViewContainer }
javaScriptEnabled={true}
domStorageEnabled={true}
source={{uri: 'https://www.youtube.com/embed/YE7VzlLtp-4' }}
/>
</View>
);
}
}
const styles = StyleSheet.create({
WebViewContainer: {
marginTop: (Platform.OS == 'android') ? 20 : 0,
}
});
Otherwise if you don't want to use WebView, use a third party package like react-native-youtube

Can't pause in react native video

I want to play an audio file, But it's playing automaticaly and I can't pause That.
How Can I Fix That?
That Must be Paused at the begin
My Code:
import Video from 'react-native-video';
export default class Android extends Component {
constructor(props) {
super(props)
this.state = {
paused: true,
}
}
video: Video;
render() {
return (
<View style={styles.container}>
<Text style={styles.welcome}>
Welcome to React Native!
</Text>
<Text style={styles.instructions}>
To get started, edit index.ios.js
</Text>
<Text style={styles.instructions}>
Press Cmd+R to reload,{'\n'}
Cmd+D or shake for dev menu
</Text>
<Video
ref={(ref: Video) => { this.video = ref }}
source={{ uri: "http://s3.picofile.com/d/7376893331/8b7bc5b4-4b5e-47c4-96dd-b0c13fd18157/Sara_Diba_Delbare_Man.mp3", mainVer: 1, patchVer: 0 }}
paused={this.state.paused}
/>
</View>
);
}
}
There's currently a bug in react-native-video where the pause flag is ignored when the component is first loaded. You have to change pause AFTER the component has loaded.
First, make sure your this.state.pause = false. Then:
<Video
paused={this.state.paused}
onLoad={() => {
this.setState({
paused: true
});
}}
</Video>
Context: https://github.com/react-native-community/react-native-video/issues/494#issuecomment-281853423
Use ref attribute to create a link to the video and using that reference we can able to use video controls on the video component
Try this code,
import React from "react";
class VideoDemo extends React.Component {
getVideo = elem => {
this.video = elem
}
playVideo = () => {
// You can use the play method as normal on your video ref
this.video.play()
};
pauseVideo = () => {
// Pause as well
this.video.pause();
};
render = () => {
return (
<div>
<video
ref={this.getVideo}
src="http://techslides.com/demos/sample-videos/small.mp4"
type="video/mp4"
/>
<div>
<button onClick={this.playVideo}>
Play!
</button>
<button onClick={this.pauseVideo}>
Pause!
</button>
</div>
</div>
);
};
}
export default VideoDemo;