Im starting to develop a mobile application with expo/react native, but I'm having some problems handling the camera object:
I have a camera object that I start recording (recordAsync) at componentDidMount and I stop it (stopRecording) at componentWillUnmount. however the promise is never resolved (neither the then, catch no finally are called)
am I doing something wrong?
here's the code:
import { Camera, Permissions } from 'expo';
import React from 'react';
export default class CameraReaction extends React.Component {
constructor(props){
super(props)
this.takeFilm = this.takeFilm.bind(this)
this.isFilming=false
this.cameraScreenContent = this.renderCamera()
}
componentDidMount(){
if (this.props.shouldrecording && !this.isFilming ){
this.takeFilm()
}
}
componentWillUnmount(){
this.camera.stopRecording()
}
saveMediaFile = async video => {
console.log("=======saveMediaFile=======");
}
renderCamera = () => {
let self = this
return (
<View style={{ flex: 1 }}>
<Camera
ref={ref => {self.camera=ref}}
style={styles.camera}
type='front'
whiteBalance='off'
ratio='4:3'
autoFocus='off'
>
</Camera>
</View>
);
}
takeFilm(){
let self = this
try{
self.camera.recordAsync()
.then(data => {
self.saveMediaFile(data),
self.isFilming=false
})
.catch(error => {console.log(error)})
this.isFilming = true
}
catch(e){
this.isFilming = false
}
};
render() {
return <View style={styles.container}>{this.cameraScreenContent}</View>;
}
}
anyone has any clue of what I'm doing wrong?
thanks in advance
I finally realised that we can't start recording directly when a component is rendered. An by 'directly' I mean without any further action from the user. If I do it in two steps (p.e. waiting for the user to click somewhere), if works perfectly. But I don't see any reference to this behaviour / limitation in the documentation.
The working code bellow:
import React from 'react';
import { StyleSheet, Text, View , TouchableOpacity} from 'react-native';
import { Camera, Permissions} from 'expo';
export default class App extends React.Component {
constructor(props){
super(props)
this.camera=undefined
this.state = {permissionsGranted:false,bcolor:'red'}
this.takeFilm = this.takeFilm.bind(this)
}
async componentWillMount() {
let cameraResponse = await Permissions.askAsync(Permissions.CAMERA)
if (cameraResponse.status == 'granted'){
let audioResponse = await Permissions.askAsync(Permissions.AUDIO_RECORDING);
if (audioResponse.status == 'granted'){
this.setState({ permissionsGranted: true });
}
}
}
takeFilm(){
let self = this;
if (this.camera){
this.camera.recordAsync().then(data => self.setState({bcolor:'green'}))
}
}
render() {
if (!this.state.permissionsGranted){
return <View><Text>Camera permissions not granted</Text></View>
} else {
return (
<View style={{flex: 1}}>
<View style={{ flex: 1 }}>
<Camera ref={ref => this.camera = ref} style={{flex: 0.3}} ></Camera>
</View>
<TouchableOpacity style={{backgroundColor:this.state.bcolor, flex:0.3}} onPress={() => {
if(this.state.cameraIsRecording){
this.setState({cameraIsRecording:false})
this.camera.stopRecording();
}
else{
this.setState({cameraIsRecording:true})
this.takeFilm();
}
}} />
</View>)
}
}
}
Related
I’m trying to make a prototype application that over and over
1- record a video with the camera for x seconds
2- displays this video
For this I use the components Camera from expo-camera and Video from expo-av
For this I have two views :
I use in my code the stateSequence property and the sequencer() function which displays alternately the view with the Camera component which films for x seconds , and the video view which allows me to display the video.
Sequencer() is triggered with setInterval( this.sequencer , 10000) found in the componentWillMount()
I can switch alternately from a View with the Camera component to a View with the Video component.
To record a video with the Camera component I use recordAsync(), but I get the following error:
Unhandled promise rejection: Error: Camera is not running
I’m using an android phone for my tests.
Can’t you help me
this is my code
import { StyleSheet, Text, View ,TouchableOpacity} from 'react-native';
import * as Permissions from 'expo-permissions';
import { Camera } from 'expo-camera';
import { Video } from 'expo-av';
import { Logs } from 'expo';
export default class SequenceViewer extends Component {
constructor(props) {
super(props);
this.state = {
stateSequence: "SHOOT ",
hasCameraPermission: null,
type: Camera.Constants.Type.front,
}
this.recordVideo = this.recordVideo.bind(this)
}
sequencer = () => {
if(this.state.stateSequence==="WATCH"){
this.setState({ stateSequence: "SHOOT",})
this.recordVideo(); // Error message Camera is not running
} else {
this.setState({stateSequence: "WATCH"})
}
}
async componentWillMount() {
let rollStatus = await Permissions.askAsync(Permissions.CAMERA_ROLL);
let cameraResponse = await Permissions.askAsync(Permissions.CAMERA)
if (rollStatus.status=='granted'){
if (cameraResponse.status == 'granted' ){
let audioResponse = await Permissions.askAsync(Permissions.AUDIO_RECORDING);
if (audioResponse.status == 'granted'){
this.setState({ permissionsGranted: true });
setInterval( this.sequencer , 10000);
}
}
}
}
recordVideo = async () => {
if(this.state.cameraIsRecording){
this.setState({cameraIsRecording:false})
this.camera.stopRecording();
}
else {
this.setState({cameraIsRecording:true})
if (this.camera) {
let record = await this.camera.recordAsync(quality='480p',maxDuration=5,mute=true).then( data =>{
this.setState( {recVideoUri :data.uri})
})
}
}
};
render() {
const { hasCameraPermission } = this.state
if(this.state.stateSequence=="WATCH")
{
return(
<View style={styles.container}>
<Video
source={{ uri:this.state.recVideoUri }}
rate={1.0}
volume={1.0}
isMuted={false}
resizeMode="cover"
shouldPlay
isLooping
style={{ width: 300, height: 300 }}
ref={(ref) => { this.player = ref }}
/>
</View>
)
} else
{
return(
<View style={{ flex: 1 }}>
<Camera style={{ flex: 1 }} type={this.state.type} ref={ref => {this.camera = ref; }}></Camera>
</View>
)
}
}
}
const styles = StyleSheet.create({
viewerText: {
fontSize: 20,
fontWeight: 'bold',
},
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
Thank you
I had the same problem, my solution was by default camera type have to be "back" and you could change to "front" by:
componentDidMount = () => {
this.props.navigation.addListener('didFocus', async () => {
await setTimeout(() => {
this.setState({ cameraType: Camera.Constants.Type.front })
}, 100)
});
}
I was getting the "Camera is not running" error when i changed screens. So for functional components instead of withNavigationFocus(Camera) use this method:
import { useIsFocused } from '#react-navigation/native';
function MyComponent() {
const isFocused = useIsFocused()
return (
<View>
{ isFocused && <RNCamera /> }
</View>
)
}
I copied fallowing code from a github project and tried using expo. The project executed without error but when i press button nothing happens. not even error this is my code
NB- I stetted an alert inside onChooseImagePress and alert is working fine
import React from 'react';
import { Image, StyleSheet, Button, Text, View, Alert, } from 'react-native';
import { ImagePicker } from 'expo';
import * as firebase from 'firebase';
import {firebaseConfig} from "./ApiKeys";
export default class HomeScreen extends React.Component {
static navigationOptions = {
header: null,
};
onChooseImagePress = async () => {
let result = await ImagePicker.launchCameraAsync();
//let result = await ImagePicker.launchImageLibraryAsync();
if (!result.cancelled) {
this.uploadImage(result.uri, "test-image")
.then(() => {
Alert.alert("Success");
})
.catch((error) => {
Alert.alert(error);
});
}
}
uploadImage = async (uri, imageName) => {
const response = await fetch(uri);
const blob = await response.blob();
var ref = firebase.storage().ref().child("images/" + imageName);
return ref.put(blob);
}
render() {
return (
<View style={styles.container}>
<Button title="Choose image..." onPress={this.onChooseImagePress} />
</View>
);
}
}
const styles = StyleSheet.create({
container: { flex: 1, paddingTop: 50, alignItems: "center", },
});
}
Multiple syntactical issues in your code:
const styles... should be defined inside the render function currently its dangling outside the class
Brackets mismatch
return (
<View style={styles.container}>
<Button title="Choose image..." onPress={this.onChooseImagePress} />
</View>
);
}
} // the class ends here
Please let me know if it still doesn't work
Try to use below code
constructor() {
super();
this.state = { };
this.onChooseImagePress= this.onChooseImagePress.bind(this);
}
<Button title="Choose image..." onPress={() => this.onChooseImagePress()} />
I am new in react-native and i want to open url in default browser like Chrome in Android and iPhone both.
We open url via intent in Android same like functionality i want to achieve.
I have search many times but it will give me the result of Deepklinking.
You should use Linking.
Example from the docs:
class OpenURLButton extends React.Component {
static propTypes = { url: React.PropTypes.string };
handleClick = () => {
Linking.canOpenURL(this.props.url).then(supported => {
if (supported) {
Linking.openURL(this.props.url);
} else {
console.log("Don't know how to open URI: " + this.props.url);
}
});
};
render() {
return (
<TouchableOpacity onPress={this.handleClick}>
{" "}
<View style={styles.button}>
{" "}<Text style={styles.text}>Open {this.props.url}</Text>{" "}
</View>
{" "}
</TouchableOpacity>
);
}
}
Here's an example you can try on Expo Snack:
import React, { Component } from 'react';
import { View, StyleSheet, Button, Linking } from 'react-native';
import { Constants } from 'expo';
export default class App extends Component {
render() {
return (
<View style={styles.container}>
<Button title="Click me" onPress={ ()=>{ Linking.openURL('https://google.com')}} />
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
},
});
A simpler way which eliminates checking if the app can open the url.
loadInBrowser = () => {
Linking.openURL(this.state.url).catch(err => console.error("Couldn't load page", err));
};
Calling it with a button.
<Button title="Open in Browser" onPress={this.loadInBrowser} />
Try this:
import React, { useCallback } from "react";
import { Linking } from "react-native";
OpenWEB = () => {
Linking.openURL(url);
};
const App = () => {
return <View onPress={() => OpenWeb}>OPEN YOUR WEB</View>;
};
Hope this will solve your problem.
In React 16.8+, the following can be used to create an ExternalLinkBtn component for opening external links in the browser.
import React from 'react';
import { Button, Linking } from 'react-native';
const ExternalLinkBtn = (props) => {
return <Button
title={props.title}
onPress={() => {
Linking.openURL(props.url)
.catch(err => {
console.error("Failed opening page because: ", err)
alert('Failed to open page')
})}}
/>
}
Below is an example of using our ExternalLinkBtn component
export default function exampleUse() {
return (
<View>
<ExternalLinkBtn title="Example Link" url="https://example.com" />
</View>
)
}
I have this piece of react-native code:
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
ToolbarAndroid,
ListView,
Text,
View
} from 'react-native';
let styles = require('./styles/styles');
class Sunshine extends Component {
constructor(props) {
super(props);
this.state = {isLoading: true, jsonData: ''}
}
componentDidMount() {
this.setState({jsonData: this.getMoviesFromApiAsync()})
}
render() {
if(this.state.isLoading != true) {
return (
<View style={styles.container}>
<ToolbarAndroid
style={styles.baseToolbar}
logo={require('./ic_launcher.png')}
title="Sunshine"
titleTextColor="red"/>
<View style={styles.viewcontainer}>
<Text>{this.state.jsonData.city.id}</Text>
<ListView
dataSource={this.state.jsonData.list}
renderRow={(rowData) => <Text>{rowData.dt}</Text>}
/>
</View>
</View>
);
} else {
return (
<View style={styles.container}>
<ToolbarAndroid
style={styles.baseToolbar}
logo={require('./ic_launcher.png')}
title="Sunshine"
titleTextColor="red"/>
<View style={styles.singleviewcontainer}>
<Text>Loading...</Text>
</View>
</View>
);
}
}
getMoviesFromApiAsync() {
return fetch('http://api.openweathermap.org/data/2.5/forecast/daily?q=94043&mode=json&units=metric&cnt=14&APPID=18dcba27e5bca83fe4ec6b8fbeed7827')
.then((response) => response.json())
.then((responseJson) => {
this.setState({isLoading: false, jsonData: responseJson});
console.log(responseJson);
return responseJson;
})
.catch((error) => {
console.error(error);
});
}
}
AppRegistry.registerComponent('Sunshine', () => Sunshine);
What I think it should happen is that when an answer arrives from the server, the list is populated with it's result. But that's not what's going on. Intsead i get this error:
undefined is not an object (evaluating 'allRowIDs.length')
So what exactly am i doing wrong here?
You have to create a ListViewDataSource with the data list.
constructor (props) {
super(props)
this.dataSource = new ListView.DataSource({
rowHasChanged: (r1, r2) => r1 !== r2
})
}
componentDidMount () {
// You don't need to assign the return value to the state
this.getMoviesFromApiAsync()
}
render () {
// Use the dataSource
const rows = this.dataSource.cloneWithRows(this.state.jsonData.list || [])
...
return (
...
<ListView
dataSource={rows}
/>
)
}
Full docs here.
I try to make a example of navigator ,but the scene is always blank.Some advices told me to set flex:1 to the navigator,but it doesn't work!
Here is my code:
index.android.js:
'use strict';
import React, {
AppRegistry,
Component,
StyleSheet,
Navigator,
Text,
View
} from 'react-native';
import Homepage from './Homepage' ;
class NavigatorTest extends Component {
render() {
var defaultName = 'Homepage' ;
var defaultCom = Homepage ;
return (
<Navigator style = {{flex:1}}
initialRoute = {{name: defaultName,component:Homepage }}
configureScene = {() => {
return Navigator.SceneConfigs.VerticalDownSwipeJump ;
}}
renderScene = {(route,navigator) => {
let Component = route.component ;
if (route.component) {
return <Component {...route.params} navigator = {navigator} />
}
}} />
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F5FCFF',
},
});
AppRegistry.registerComponent('NavigatorTest', () => NavigatorTest);
Homepage.js
import React, {
Component,
StyleSheet,
TouchableOpacity,
Text,
View
} from 'react';
import Detailpage from './Detailpage' ;
export class Homepage extends React.Component {
static propTypes = {
};
constructor(props) {
super(props);
}
_pressButton() {
const {navigator} = this.props ;
if(navigator) {
navigator.push({
name: 'Detailpage',
component:Detailpage,
})
}
}
render() {
return (
<View>
<TouchableOpacity onPress = {this._pressButton}>
<Text>click to jump to Detailpage!</Text>
</TouchableOpacity>
</View>
);
}
}
Detailpage.js
import React, {
Component,
StyleSheet,
TouchableOpacity,
Text,
View
} from 'react';
import Homepage from './Homepage' ;
export class Detailpage extends React.Component {
static propTypes = {
};
constructor(props) {
super(props);
}
_pressButton() {
const {navigator} = this.props ;
if (navigator) {
navigator.pop() ;
}
}
render() {
return (
<View>
<TouchableOpacity onPress = {this._pressButton}>
<Text>click to get back</Text>
</TouchableOpacity>
</View>
);
}
}
I'm a newcomer of react-native ,please give me some advice ,thanks in advance!
Try simplifying and separating your Navigator logic a bit. Debugging will also be easier this way.
class NavigatorTest extends Component {
render() {
return (
<Navigator
initialRoute={{id: 'Homepage'}}
configureScene={this.configureScene}
renderScene={this.renderScene}
/>
);
}
configureScene() {
return Navigator.SceneConfigs.VerticalDownSwipeJump;
}
renderScene(route, navigator) {
switch(route.id) {
case 'Homepage':
return this.renderHomepage(navigator);
case 'Detailpage':
return this.renderDetailpage(navigator);
default:
throw new Error('No route found for id ' + route.id);
}
}
renderHomepage(navigator) {
return <Homepage navigator={navigator} />;
}
renderDetailpage(navigator) {
return <Detailpage navigator={navigator} />;
}
}
Homepage.js
...
_pressButton() {
this.props.navigator.push({id: 'Detailpage'});
}
...
Separate the Navigator logic as Villeaka proposed is much more clear for reading and analysing.
The method "this.renderHomepage(navigator)" was called in "renderScene" scope, so "this" in it refer to "renderScene" not the NavigatorTest component, pass "this" to a new variable like below:
renderScene(route, navigator) {
var me = this;
switch(route.id) {
case 'Homepage':
return me.renderHomepage(navigator);
case 'Detailpage':
return me.renderDetailpage(navigator);
default:
throw new Error('No route found for id ' + route.id);
}}
I think your navigation look a little messed up, I found this video make it so easy
https://www.youtube.com/watch?v=jGOst2TLha0
By the way, my quick though on your code is
<Navigator style = {{flex:1}}
initialRoute = {{name: defaultName,component:Homepage }}
configureScene = {() => {
return Navigator.SceneConfigs.VerticalDownSwipeJump ;
}}
renderScene = {(route,navigator) => {
let Component = route.component ;
if (route.component) {
return <Component {...route.params} navigator = {navigator} />
}
}} />
That "let Component" doesn't look right because variable name should not be same as "Component" library that imported on the top of your code, cause ambiguous