I am creating a music player for a streaming app and I want to make a dynamic playlist. I am using Expo AV and Firebase to store music and information.
I already have everything configured but I can't pass the "cancionesPlaylist" prop (which is already an array) from the parent functional component to the child class component. This is the code I have:
import { StyleSheet, TouchableOpacity, View, Image } from "react-native";
import { Title, Text } from "react-native-paper";
import { LinearGradient } from "expo-linear-gradient";
import { Button } from "../components/Button";
import { Audio, Video } from "expo-av";
import firebase from "../utils/firebase";
import "firebase/firestore";
import { Ionicons } from "#expo/vector-icons";
export default function ReproductorAudio(props) {
const { route } = props;
const { canciones } = route.params;
const cancionesPlaylist = canciones;
return <ReproductorMusica cancionesPlaylist={cancionesPlaylist} />;
}
class ReproductorMusica extends React.Component {
constructor(props) {
super(props);
const cancionesPlaylist = props.cancionesPlaylist;
console.log(cancionesPlaylist);
}
state = {
isPlaying: false,
playbackInstance: null,
currentIndex: 0,
volume: 1.0,
isBuffering: false,
};
async componentDidMount() {
try {
await Audio.setAudioModeAsync({
allowsRecordingIOS: false,
interruptionModeIOS: Audio.INTERRUPTION_MODE_IOS_DO_NOT_MIX,
playsInSilentModeIOS: true,
interruptionModeAndroid: Audio.INTERRUPTION_MODE_ANDROID_DUCK_OTHERS,
shouldDuckAndroid: true,
staysActiveInBackground: true,
playThroughEarpieceAndroid: true,
});
this.loadAudio();
} catch (e) {
console.log(e);
}
}
async loadAudio() {
const { currentIndex, isPlaying, volume } = this.state;
try {
const playbackInstance = new Audio.Sound();
const source = {
uri: cancionesPlaylist[currentIndex].uri,
};
const status = {
shouldPlay: isPlaying,
volume,
};
playbackInstance.setOnPlaybackStatusUpdate(this.onPlaybackStatusUpdate);
await playbackInstance.loadAsync(source, status, false);
this.setState({ playbackInstance });
} catch (e) {
console.log(e);
}
}
onPlaybackStatusUpdate = (status) => {
this.setState({
isBuffering: status.isBuffering,
});
};
handlePlayPause = async () => {
const { isPlaying, playbackInstance } = this.state;
isPlaying
? await playbackInstance.pauseAsync()
: await playbackInstance.playAsync();
this.setState({
isPlaying: !isPlaying,
});
};
handlePreviousTrack = async () => {
let { playbackInstance, currentIndex } = this.state;
if (playbackInstance) {
await playbackInstance.unloadAsync();
currentIndex < cancionesPlaylist.length - 1
? (currentIndex -= 1)
: (currentIndex = 0);
this.setState({
currentIndex,
});
this.loadAudio();
}
};
handleNextTrack = async () => {
let { playbackInstance, currentIndex } = this.state;
if (playbackInstance) {
await playbackInstance.unloadAsync();
currentIndex < cancionesPlaylist.length - 1
? (currentIndex += 1)
: (currentIndex = 0);
this.setState({
currentIndex,
});
this.loadAudio();
}
};
renderFileInfo() {
const { playbackInstance, currentIndex } = this.state;
return playbackInstance ? (
<View style={styles.trackInfo}>
<Text style={[styles.trackInfoText, styles.largeText]}>
{cancionesPlaylist[currentIndex].name}
</Text>
<Text style={[styles.trackInfoText, styles.smallText]}>
{cancionesPlaylist[currentIndex].author}
</Text>
<Text style={[styles.trackInfoText, styles.smallText]}>
{cancionesPlaylist[currentIndex].source}
</Text>
</View>
) : null;
}
render() {
return (
<View style={styles.container}>
<Image
style={styles.albumCover}
source={{
uri:
"http://www.archive.org/download/LibrivoxCdCoverArt8/hamlet_1104.jpg",
}}
/>
<View style={styles.controls}>
<TouchableOpacity
style={styles.control}
onPress={this.handlePreviousTrack}
>
<Ionicons name="arrow-back-circle-outline" size={48} color="#444" />
</TouchableOpacity>
<TouchableOpacity
style={styles.control}
onPress={this.handlePlayPause}
>
{this.state.isPlaying ? (
<Ionicons name="ios-pause" size={48} color="#444" />
) : (
<Ionicons name="ios-play-circle" size={48} color="#444" />
)}
</TouchableOpacity>
<TouchableOpacity
style={styles.control}
onPress={this.handleNextTrack}
>
<Ionicons
name="arrow-forward-circle-outline"
size={48}
color="#444"
/>
</TouchableOpacity>
</View>
{this.renderFileInfo()}
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#aaa",
alignItems: "center",
justifyContent: "center",
},
albumCover: {
width: 250,
height: 250,
},
trackInfo: {
padding: 40,
backgroundColor: "#aaa",
},
trackInfoText: {
textAlign: "center",
flexWrap: "wrap",
color: "#550088",
},
largeText: {
fontSize: 22,
},
smallText: {
fontSize: 16,
},
control: {
margin: 20,
},
controls: {
flexDirection: "row",
},
});
You almost have it you are just missing one conceptual difference between the class and functional components. Namely how props are used inside the component (docs).
With a simple functional component, it is simply that, a function, and all the props are accessible anywhere in the component. But with a class component the props are bound to the class instance via this.props.
So now looking at your code, you are creating a local variable cancionesPlaylist inside of the class constructor but that variable is scoped only to within the constructor function block.
constructor(props) {
super(props);
const cancionesPlaylist = props.cancionesPlaylist;
console.log(cancionesPlaylist);
}
So when you try to access that local variable anywhere in your class you are actually referencing a likely nonexistent (i.e. undefined) global variable named cancionesPlaylist. What you need to do instead is just access the props on the class instance via this.props.cancionesPlaylist. You have access to this (aka the component/class instance) anywhere inside the class component, but it may not always be the same 🤭 see this for some general gotchas not specific to react.
Altogether with a little cleanup...
import { StyleSheet, TouchableOpacity, View, Image } from "react-native";
import { Title, Text } from "react-native-paper";
import { LinearGradient } from "expo-linear-gradient";
import { Button } from "../components/Button";
import { Audio, Video } from "expo-av";
import firebase from "../utils/firebase";
import "firebase/firestore";
import { Ionicons } from "#expo/vector-icons";
export default function ReproductorAudio(props) {
const { route } = props;
const { canciones } = route.params;
const cancionesPlaylist = canciones;
return <ReproductorMusica cancionesPlaylist={cancionesPlaylist} />;
}
class ReproductorMusica extends React.Component {
state = {
isPlaying: false,
playbackInstance: null,
currentIndex: 0,
volume: 1.0,
isBuffering: false,
};
async componentDidMount() {
try {
await Audio.setAudioModeAsync({
allowsRecordingIOS: false,
interruptionModeIOS: Audio.INTERRUPTION_MODE_IOS_DO_NOT_MIX,
playsInSilentModeIOS: true,
interruptionModeAndroid: Audio.INTERRUPTION_MODE_ANDROID_DUCK_OTHERS,
shouldDuckAndroid: true,
staysActiveInBackground: true,
playThroughEarpieceAndroid: true,
});
this.loadAudio();
} catch (e) {
console.log(e);
}
}
async loadAudio() {
const { currentIndex, isPlaying, volume } = this.state;
try {
const playbackInstance = new Audio.Sound();
const source = {
uri: this.props.cancionesPlaylist[currentIndex].uri,
};
const status = {
shouldPlay: isPlaying,
volume,
};
playbackInstance.setOnPlaybackStatusUpdate(this.onPlaybackStatusUpdate);
await playbackInstance.loadAsync(source, status, false);
this.setState({ playbackInstance });
} catch (e) {
console.log(e);
}
}
onPlaybackStatusUpdate = (status) => {
this.setState({
isBuffering: status.isBuffering,
});
};
handlePlayPause = async () => {
const { isPlaying, playbackInstance } = this.state;
isPlaying
? await playbackInstance.pauseAsync()
: await playbackInstance.playAsync();
this.setState({
isPlaying: !isPlaying,
});
};
handlePreviousTrack = async () => {
let { playbackInstance, currentIndex } = this.state;
if (playbackInstance) {
await playbackInstance.unloadAsync();
currentIndex < this.props.cancionesPlaylist.length - 1
? (currentIndex -= 1)
: (currentIndex = 0);
this.setState({
currentIndex,
});
this.loadAudio();
}
};
handleNextTrack = async () => {
let { playbackInstance, currentIndex } = this.state;
if (playbackInstance) {
await playbackInstance.unloadAsync();
currentIndex < this.props.cancionesPlaylist.length - 1
? (currentIndex += 1)
: (currentIndex = 0);
this.setState({
currentIndex,
});
this.loadAudio();
}
};
renderFileInfo() {
const { playbackInstance, currentIndex } = this.state;
return playbackInstance ? (
<View style={styles.trackInfo}>
<Text style={[styles.trackInfoText, styles.largeText]}>
{this.props.cancionesPlaylist[currentIndex].name}
</Text>
<Text style={[styles.trackInfoText, styles.smallText]}>
{this.props.cancionesPlaylist[currentIndex].author}
</Text>
<Text style={[styles.trackInfoText, styles.smallText]}>
{this.props.cancionesPlaylist[currentIndex].source}
</Text>
</View>
) : null;
}
render() {
return (
<View style={styles.container}>
<Image
style={styles.albumCover}
source={{
uri:
"http://www.archive.org/download/LibrivoxCdCoverArt8/hamlet_1104.jpg",
}}
/>
<View style={styles.controls}>
<TouchableOpacity
style={styles.control}
onPress={this.handlePreviousTrack}
>
<Ionicons name="arrow-back-circle-outline" size={48} color="#444" />
</TouchableOpacity>
<TouchableOpacity
style={styles.control}
onPress={this.handlePlayPause}
>
{this.state.isPlaying ? (
<Ionicons name="ios-pause" size={48} color="#444" />
) : (
<Ionicons name="ios-play-circle" size={48} color="#444" />
)}
</TouchableOpacity>
<TouchableOpacity
style={styles.control}
onPress={this.handleNextTrack}
>
<Ionicons
name="arrow-forward-circle-outline"
size={48}
color="#444"
/>
</TouchableOpacity>
</View>
{this.renderFileInfo()}
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#aaa",
alignItems: "center",
justifyContent: "center",
},
albumCover: {
width: 250,
height: 250,
},
trackInfo: {
padding: 40,
backgroundColor: "#aaa",
},
trackInfoText: {
textAlign: "center",
flexWrap: "wrap",
color: "#550088",
},
largeText: {
fontSize: 22,
},
smallText: {
fontSize: 16,
},
control: {
margin: 20,
},
controls: {
flexDirection: "row",
},
});
Removed the constructor as it was not needed and changed global variable lookup of cancionesPlaylist to prop lookup on class via this.props.cancionesPlaylist.
Related
I'm trying to create react app with expo using expo-camera for taking pictures. I have separately components MeasurementCameraScreen and MeasurementCamera. I'm using useRef() hook to be able to call takePictureAsync() from the MeasuremenCameraScreen.
When pressing the take image -button takePicture() console.logs the ref, so I assume the onPress gets there, but then I get the following error message:
[Unhandled promise rejection: TypeError: ref.current.takePictureAsync is not a function. (In 'ref.current.takePictureAsync(options)', 'ref.current.takePictureAsync' is undefined)]
I saw that people have also had same issues with takePictureAcync(), but I haven't found solution to my problem. I also tired to combine the MeasurementCameraScreen and MeasurementCamera components to one component, and with that I got the camera working, but I'm curious of why it doesn't work now? refs are new thing for me so I think there is something wrong with them.
Here are the components:
MeasurementCameraScreen
import { useRef } from 'react'
import { StyleSheet, TouchableOpacity, View } from 'react-native'
import MeasurementCamera from '../components/MeasurementCamera'
import Text from '../components/Text'
const MeasurementCameraScreen = () => {
const cameraRef = useRef(null)
return (
<View style={styles.container}>
<View style={styles.cameraContainer}>
<MeasurementCamera ref={cameraRef}/>
</View>
<View>
</View>
<TouchableOpacity
onPress={() => cameraRef.current.takePicture()}
style={styles.buttonContainer}
>
<Text>
Take image
</Text>
</TouchableOpacity>
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
cameraContainer: {
flex: 1,
},
buttonContainer: {
width: '100%',
height: 70,
backgroundColor: 'white',
justifyContent: 'center',
alignItems: 'center',
alignSelf: 'flex-end'
},
})
export default MeasurementCameraScreen
MeasurementCamera
import { useState, useEffect, useImperativeHandle, forwardRef } from 'react'
import { StyleSheet } from "react-native"
import { Camera } from 'expo-camera'
import Text from './Text'
const MeasurementCamera = forwardRef((props, ref) => {
const [hasPermission, setHasPermission] = useState(null)
useEffect(() => {
const getPermission = async () => {
const { status } = await Camera.requestCameraPermissionsAsync()
setHasPermission(status === 'granted')
}
getPermission()
}, [])
const takePicture = async () => {
if (ref) {
console.log(ref.current)
const options = {
quality: 1,
base64: true
}
const picture = await ref.current.takePictureAsync(options)
console.log(picture.uri)
}
}
useImperativeHandle(ref, () => ({
takePicture
}))
if (hasPermission === null) {
return <Text>Requesting for camera permission</Text>
} if (hasPermission === false) {
return <Text>No access to camera</Text>
}
return (
<Camera
ref={ref}
style={StyleSheet.absoluteFillObject}
/>
)
})
MeasurementCamera.displayName = 'MeasurementCamera'
export default MeasurementCamera
EDIT: Solved the problem, check out the comment for solution! :)
Okay I found the solution!
After reading the medium article #LouaySleman recommended about the expo-camera I understood that to be able to use the Expo Camera components functions I need to use ref. So what I needed was two refs, one for the components to communicate and another one to be able to use the Camera takePictureAsync() function.
Now that we have permissions to access the Camera, you should get familiar with the ref props on line 132, in the Camera component. There we have passed the cameraRef that was previously defined with useRef. In doing so, we will have access to interesting methods that we can call to control the Camera.
What you needed is to add two refs, one for the components to communicate and another one to be able to use the Camera takePictureAsync() function Please check this example from medium:
import React, { useState, useRef, useEffect } from "react";
import {
View,
Text,
TouchableOpacity,
SafeAreaView,
StyleSheet,
Dimensions,
} from "react-native";
import { Camera } from "expo-camera";
import { Video } from "expo-av";
export default function CameraScreen() {
const [hasPermission, setHasPermission] = useState(null);
const [cameraType, setCameraType] = useState(Camera.Constants.Type.back);
const [isPreview, setIsPreview] = useState(false);
const [isCameraReady, setIsCameraReady] = useState(false);
const [isVideoRecording, setIsVideoRecording] = useState(false);
const [videoSource, setVideoSource] = useState(null);
const cameraRef = useRef();
useEffect(() => {
(async () => {
const { status } = await Camera.requestPermissionsAsync();
setHasPermission(status === "granted");
})();
}, []);
const onCameraReady = () => {
setIsCameraReady(true);
};
const takePicture = async () => {
if (cameraRef.current) {
const options = { quality: 0.5, base64: true, skipProcessing: true };
const data = await cameraRef.current.takePictureAsync(options);
const source = data.uri;
if (source) {
await cameraRef.current.pausePreview();
setIsPreview(true);
console.log("picture", source);
}
}
};
const recordVideo = async () => {
if (cameraRef.current) {
try {
const videoRecordPromise = cameraRef.current.recordAsync();
if (videoRecordPromise) {
setIsVideoRecording(true);
const data = await videoRecordPromise;
const source = data.uri;
if (source) {
setIsPreview(true);
console.log("video source", source);
setVideoSource(source);
}
}
} catch (error) {
console.warn(error);
}
}
};
const stopVideoRecording = () => {
if (cameraRef.current) {
setIsPreview(false);
setIsVideoRecording(false);
cameraRef.current.stopRecording();
}
};
const switchCamera = () => {
if (isPreview) {
return;
}
setCameraType((prevCameraType) =>
prevCameraType === Camera.Constants.Type.back
? Camera.Constants.Type.front
: Camera.Constants.Type.back
);
};
const cancelPreview = async () => {
await cameraRef.current.resumePreview();
setIsPreview(false);
setVideoSource(null);
};
const renderCancelPreviewButton = () => (
<TouchableOpacity onPress={cancelPreview} style={styles.closeButton}>
<View style={[styles.closeCross, { transform: [{ rotate: "45deg" }] }]} />
<View
style={[styles.closeCross, { transform: [{ rotate: "-45deg" }] }]}
/>
</TouchableOpacity>
);
const renderVideoPlayer = () => (
<Video
source={{ uri: videoSource }}
shouldPlay={true}
style={styles.media}
/>
);
const renderVideoRecordIndicator = () => (
<View style={styles.recordIndicatorContainer}>
<View style={styles.recordDot} />
<Text style={styles.recordTitle}>{"Recording..."}</Text>
</View>
);
const renderCaptureControl = () => (
<View style={styles.control}>
<TouchableOpacity disabled={!isCameraReady} onPress={switchCamera}>
<Text style={styles.text}>{"Flip"}</Text>
</TouchableOpacity>
<TouchableOpacity
activeOpacity={0.7}
disabled={!isCameraReady}
onLongPress={recordVideo}
onPressOut={stopVideoRecording}
onPress={takePicture}
style={styles.capture}
/>
</View>
);
if (hasPermission === null) {
return <View />;
}
if (hasPermission === false) {
return <Text style={styles.text}>No access to camera</Text>;
}
return (
<SafeAreaView style={styles.container}>
<Camera
ref={cameraRef}
style={styles.container}
type={cameraType}
flashMode={Camera.Constants.FlashMode.on}
onCameraReady={onCameraReady}
onMountError={(error) => {
console.log("camera error", error);
}}
/>
<View style={styles.container}>
{isVideoRecording && renderVideoRecordIndicator()}
{videoSource && renderVideoPlayer()}
{isPreview && renderCancelPreviewButton()}
{!videoSource && !isPreview && renderCaptureControl()}
</View>
</SafeAreaView>
);
}
const WINDOW_HEIGHT = Dimensions.get("window").height;
const closeButtonSize = Math.floor(WINDOW_HEIGHT * 0.032);
const captureSize = Math.floor(WINDOW_HEIGHT * 0.09);
const styles = StyleSheet.create({
container: {
...StyleSheet.absoluteFillObject,
},
closeButton: {
position: "absolute",
top: 35,
left: 15,
height: closeButtonSize,
width: closeButtonSize,
borderRadius: Math.floor(closeButtonSize / 2),
justifyContent: "center",
alignItems: "center",
backgroundColor: "#c4c5c4",
opacity: 0.7,
zIndex: 2,
},
media: {
...StyleSheet.absoluteFillObject,
},
closeCross: {
width: "68%",
height: 1,
backgroundColor: "black",
},
control: {
position: "absolute",
flexDirection: "row",
bottom: 38,
width: "100%",
alignItems: "center",
justifyContent: "center",
},
capture: {
backgroundColor: "#f5f6f5",
borderRadius: 5,
height: captureSize,
width: captureSize,
borderRadius: Math.floor(captureSize / 2),
marginHorizontal: 31,
},
recordIndicatorContainer: {
flexDirection: "row",
position: "absolute",
top: 25,
alignSelf: "center",
justifyContent: "center",
alignItems: "center",
backgroundColor: "transparent",
opacity: 0.7,
},
recordTitle: {
fontSize: 14,
color: "#ffffff",
textAlign: "center",
},
recordDot: {
borderRadius: 3,
height: 6,
width: 6,
backgroundColor: "#ff0000",
marginHorizontal: 5,
},
text: {
color: "#fff",
},
});
Here is my code screen code
import React, {Component} from 'react';
import {StyleSheet, Text, TouchableOpacity, View, ScrollView, Alert} from 'react-native';
import Sound from 'react-native-sound';
import BgSoundPlayer from '../../../../Components/BgSoundPlayer/BgSoundPlayer';
const audioTests = [
{
title: 'mp3 remote download',
url: 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3',
},
{
title: "mp3 remote - file doesn't exist",
url: 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-9.mp3',
},
];
const Button = ({title, onPress}) => (
<TouchableOpacity onPress={onPress}>
<Text style={styles.button}>{title}</Text>
</TouchableOpacity>
);
const Header = ({children, style}) => <Text style={[styles.header, style]}>{children}</Text>;
const Feature = ({
title,
onPress,
buttonLabel = 'PLAY',
status,
volumeIncrease,
volumeDecrease,
}) => (
<View style={styles.feature}>
<Header style={{flex: 1}}>{title}</Header>
{status ? <Text style={{padding: 5}}>{resultIcons[status] || ''}</Text> : null}
<Button title={buttonLabel} onPress={onPress} />
<Button title="Volume Increase" onPress={volumeIncrease} />
<Button title="volume Decrease" onPress={volumeDecrease} />
</View>
);
const resultIcons = {
'': '',
pending: '?',
playing: '\u25B6',
win: '\u2713',
fail: '\u274C',
};
function setTestState(testInfo, component, status) {
component.setState({tests: {...component.state.tests, [testInfo.title]: status}});
}
/**
* Generic play function for majority of tests
*/
function playSound(testInfo, component) {
setTestState(testInfo, component, 'pending');
BgSoundPlayer.setSound(testInfo);
}
class MainView extends Component {
constructor(props) {
super(props);
Sound.setCategory('Playback', true); // true = mixWithOthers
// Special case for stopping
this.stopSoundLooped = () => {
if (!this.state.loopingSound) {
return;
}
this.state.loopingSound.stop().release();
this.setState({
loopingSound: null,
tests: {...this.state.tests, ['mp3 in bundle (looped)']: 'win'},
});
};
this.state = {
loopingSound: undefined,
tests: {},
};
}
render() {
return (
<View style={styles.container}>
<ScrollView style={styles.container} contentContainerStyle={styles.scrollContainer}>
{audioTests.map(testInfo => {
return (
<Feature
status={this.state.tests[testInfo.title]}
key={testInfo.title}
title={testInfo.title}
onPress={() => {
playSound(testInfo, this);
}}
volumeIncrease={() => {
BgSoundPlayer.increaseVolume();
}}
volumeDecrease={() => {
BgSoundPlayer.decreaseVolume();
}}
/>
);
})}
<Feature
title="mp3 in bundle (looped)"
buttonLabel={'STOP'}
onPress={() => {
BgSoundPlayer.pouse();
}}
/>
</ScrollView>
</View>
);
}
}
export default MainView;
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: 'gray',
},
scrollContainer: {},
title: {
fontSize: 20,
fontWeight: 'bold',
paddingTop: 30,
padding: 20,
textAlign: 'center',
backgroundColor: 'rgba(240,240,240,1)',
},
button: {
fontSize: 20,
backgroundColor: 'rgba(220,220,220,1)',
borderRadius: 4,
borderWidth: 1,
borderColor: 'rgba(80,80,80,0.5)',
overflow: 'hidden',
padding: 7,
},
header: {
textAlign: 'left',
},
feature: {
padding: 10,
borderTopWidth: 1,
borderBottomWidth: 1,
},
});
And my BgSoundPlayer file
import {Alert} from 'react-native';
import Sound from 'react-native-sound';
Sound.setCategory('Playback', true);
class BgSoundPlayer1 {
setSound(soundURL) {
try {
this.soundURL = soundURL;
if (soundURL.isRequire) {
this.soundRef = new Sound(soundURL.url, error =>
this.callback(error, this.soundRef),
);
} else {
this.soundRef = new Sound(soundURL.url, Sound.MAIN_BUNDLE, error =>
this.callback(error, this.soundRef),
);
}
} catch (error) {
console.log('SOUNDREFERROR::', error);
}
}
callback(error, sound) {
try {
if (error) {
Alert.alert('error', error.message);
return;
}
//this.soundURL.onPrepared && this.soundURL.onPrepared(sound);
sound.play(() => {
sound.release();
});
} catch (error) {
console.log('CALL_BACKERROR::', error);
}
}
getVolume() {
return this.soundRef?.getVolume();
}
increaseVolume(soundURL) {
console.log('CHECKREF', this.soundRef);
let sound = this.soundRef?.getVolume();
if (sound < 1 || sound == 1) {
this.soundRef?.setVolume(sound + 0.1);
}
}
decreaseVolume(soundURL) {
console.log('CHECKREF', this.soundRef);
let sound = this.soundRef?.getVolume();
if (sound > 0 || sound == 0) {
this.soundRef?.setVolume(sound - 0.1);
}
}
pouse() {
this.soundRef?.pause();
}
}
const BgSoundPlayer = new BgSoundPlayer1();
export default BgSoundPlayer;
So what is happening right now if, when I am playing one audio track and increasing decreasing volume of track its working fine
but problem occurre's when I play second track and decrease sound of first one, it dosent works it decrease volume of second track
but when I tried to debug problem, I got to know that my this.soundRef variable is taking the refrance of latest one sound, so how can solve this
I need a function that creates new screens with a webview in it.
The main screen of my expo app contains a button "add new Page" which links to a page with a form to add domain, Username, Password.
if this is done, I want a list of pages in my main screen with all pages. for example if I click on "myWebsite1" with the generated link http://user:password#myWebsite1.com this page should be shown in a webview. same for website2, 3 and so on.
Anyone an idea how to do this?
EDIT: I add some code I have right now. For each Task I create, the Domain, User and Password for the webview file should change and be saved at this specific task. (and also open onclick). I turn in a circle
this is my app.js which expo opens first, it contains a Flatlist:
import React, { Component } from "react";
import { AppRegistry, StyleSheet, Text, View, FlatList, AsyncStorage, Button, TextInput, Keyboard, Platform, TouchableWithoutFeedback } from "react-native";
const isAndroid = Platform.OS == "android";
const viewPadding = 10;
const things = {things}
export default class NodeList extends Component {
state = {
tasks: [ ],
text: ""
};
changeTextHandler = text => {
this.setState({ text: text });
};
addTask = () => {
let notEmpty = this.state.text.trim().length > 0;
if (notEmpty) {
this.setState(
prevState => {
let { tasks, text } = prevState;
return {
tasks: tasks.concat({ key: tasks.length, text: text }),
text: ""
};
},
() => Tasks.save(this.state.tasks)
);
}
};
deleteTask = i => {
this.setState(
prevState => {
let tasks = prevState.tasks.slice();
tasks.splice(i, 1);
return { tasks: tasks };
},
() => Tasks.save(this.state.tasks)
);
};
componentDidMount() {
Keyboard.addListener(
isAndroid ? "keyboardDidShow" : "keyboardWillShow",
e => this.setState({ viewMargin: e.endCoordinates.height + viewPadding })
);
Keyboard.addListener(
isAndroid ? "keyboardDidHide" : "keyboardWillHide",
() => this.setState({ viewMargin: viewPadding })
);
Tasks.all(tasks => this.setState({ tasks: tasks || [] }));
}
render() {
return (
<View
style={[styles.container, { paddingBottom: this.state.viewMargin }]}
>
<Text style={styles.appTitle}>Nodes</Text>
<FlatList
style={styles.list}
data={this.state.tasks}
renderItem={({ item, index }) =>
<View>
<View style={styles.listItemCont}>
<TouchableWithoutFeedback onPress={() => Linking.openURL('')}>
<Text style={styles.listItem}>
{item.text}
</Text>
</TouchableWithoutFeedback>
<Button color= "#00BC8C" title="✖" onPress={() => this.deleteTask(index)} />
</View>
<View style={styles.hr} />
</View>}
/>
<TextInput
style={styles.textInput}
onChangeText={this.changeTextHandler}
onSubmitEditing={this.addTask}
value={this.state.text}
placeholder="+ add Node"
returnKeyType="done"
returnKeyLabel="done"
/>
</View>
);
}
}
let Tasks = {
convertToArrayOfObject(tasks, callback) {
return callback(
tasks ? tasks.split("||").map((task, i) => ({ key: i, text: task })) : []
);
},
convertToStringWithSeparators(tasks) {
return tasks.map(task => task.text).join("||");
},
all(callback) {
return AsyncStorage.getItem("TASKS", (err, tasks) =>
this.convertToArrayOfObject(tasks, callback)
);
},
save(tasks) {
AsyncStorage.setItem("TASKS", this.convertToStringWithSeparators(tasks));
}
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
alignItems: "center",
backgroundColor: "#252525",
padding: viewPadding,
paddingTop: 24
},
list: {
width: "100%"
},
listItem: {
paddingTop: 4,
paddingBottom: 2,
fontSize: 18,
color:"#ffff"
},
hr: {
height: 1,
backgroundColor: "gray"
},
listItemCont: {
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
paddingTop: 10,
},
textInput: {
height: 40,
paddingRight: 10,
paddingLeft: 10,
borderColor: "gray",
borderWidth: isAndroid ? 0 : 1,
width: "100%",
color:"#ffff"
},
appTitle: {
alignItems:"center",
justifyContent: "center",
paddingBottom: 45,
marginTop: 50,
fontSize: 25,
color:"#ffff"
}
});
AppRegistry.registerComponent("NodeList", () => NOdeList);
and here is my second screen, which contains the component for the WebView:
import React, { Component, useState } from "react";
import { BackHandler } from 'react-native';
import { WebView } from 'react-native-webview';
var $ = require( "jquery" );
export default class nodeView extends Component {
constructor(props) {
super(props);
this.WEBVIEW_REF = React.createRef();
}
componentDidMount() {
BackHandler.addEventListener('hardwareBackPress', this.handleBackButton);
}
componentWillUnmount() {
BackHandler.removeEventListener('hardwareBackPress', this.handleBackButton);
}
handleBackButton = ()=>{
this.WEBVIEW_REF.current.goBack();
return true;
}
onNavigationStateChange(navState) {
this.setState({
canGoBack: navState.canGoBack
});
}
render(){
return (
<WebView
source={{ uri: `https://${user}:${password}#${domain}` }}
ref={this.WEBVIEW_REF}
style={{ marginTop: 20 }}
onNavigationStateChange={this.onNavigationStateChange.bind(this)}
/>
)
}
}
Create WebView. Create State Which Controls WebView. Add Listener To Button
I am trying to run the following code by I get an Invariant Violation error:
import React, { Component } from "react";
import { View, Text, TouchableOpacity } from "react-native";
import { Camera, MediaLibrary } from "expo";
import * as Permissions from 'expo-permissions';
class MyCam extends Component {
state = {
video: null,
picture: null,
recording: false
};
_saveVideo = async () => {
const { video } = this.state;
const asset = await MediaLibrary.createAssetAsync(video.uri);
if (asset) {
this.setState({ video: null });
}
};
_StopRecord = async () => {
this.setState({ recording: false }, () => {
this.cam.stopRecording();
});
};
_StartRecord = async () => {
if (this.cam) {
this.setState({ recording: true }, async () => {
const video = await this.cam.recordAsync();
this.setState({ video });
});
}
};
toogleRecord = () => {
const { recording } = this.state;
if (recording) {
this._StopRecord();
} else {
this._StartRecord();
}
};
render() {
const { recording, video } = this.state;
return (
<Camera
ref={cam => (this.cam = cam)}
style={{
justifyContent: "flex-end",
alignItems: "center",
flex: 1,
width: "100%"
}}
>
{video && (
<TouchableOpacity
onPress={this._saveVideo}
style={{
padding: 20,
width: "100%",
backgroundColor: "#fff"
}}
>
<Text style={{ textAlign: "center" }}>save</Text>
</TouchableOpacity>
)}
<TouchableOpacity
onPress={this.toogleRecord}
style={{
padding: 20,
width: "100%",
backgroundColor: recording ? "#ef4f84" : "#4fef97"
}}
>
<Text style={{ textAlign: "center" }}>
{recording ? "Stop" : "Record"}
</Text>
</TouchableOpacity>
</Camera>
);
}
}
class RecVideo extends Component {
state = {
showCamera: false
};
_showCamera = async () => {
const { status } = await Permissions.askAsync(Permissions.CAMERA_ROLL);
if (status === "granted") {
this.setState({ showCamera: true });
}
};
render() {
const { showCamera } = this.state;
return (
<View
style={{
justifyContent: "center",
alignItems: "center",
flex: 1,
width: "100%"
}}
>
{showCamera ? (
<MyCam />
) : (
<TouchableOpacity onPress={this._showCamera}>
<Text> Show Camera </Text>
</TouchableOpacity>
)}
</View>
);
}
}
export default RecVideo;
This is the error: Invariant Violation
I found this piece of code in here: https://forums.expo.io/t/how-to-record-video/18834
I only made a change on how the permission are imported but the rest is the same.
I'm guessing the error is related to the import since that's what I found online but I don't know how to fix it because everything seems to be right.
import React from 'react';
import { Text, View, TouchableOpacity,Vibration } from 'react-native';
import * as Permissions from 'expo-permissions';
import { Camera } from 'expo-camera';
import { AsyncStorage } from 'react-native';
import { ToastAndroid } from 'react-native';
import { widthPercentageToDP as wp, heightPercentageToDP as hp } from 'react-native-responsive-screen';
const PATTERN = [1000, 2000, 3000];
export default class OpenCamera extends React.Component {
state = {
hasCameraPermission: null,
type: Camera.Constants.Type.back,
};
async componentDidMount() {
const { status } = await Permissions.askAsync(Permissions.CAMERA);
this.setState({ hasCameraPermission: status === 'granted' });
}
// get the uri;
async clicked1() {
Vibration.vibrate(PATTERN)
if (this.camera) {
let photo = await this.camera.takePictureAsync();
console.log('array>>',photo)
console.log('image path>>>>>>>', photo.uri)
await AsyncStorage.setItem('image_path_key', photo.uri);
if(photo.uri!=null){
this.props._changeCameraVisbilty(false);
this.props._setImageData(photo.uri)
// this.props.navigation.goBack()
}else{
ToastAndroid.show('There some problem to taking a photo,please try again', ToastAndroid.SHORT);
}
}
};
// _closeCamera=()=>{
// }
// get the photo
async snapPhoto() {
console.log('Button Pressed');
if (this.camera) {
console.log('Taking photo');
const options = { quality: 1, base64: true, fixOrientation: true,
exif: true};
await this.camera.takePictureAsync(options).then(photo => {
photo.exif.Orientation = 1;
console.log(photo);
});
}
}
render() {
//const { navigate } = this.props.navigation;
const { hasCameraPermission } = this.state;
if (hasCameraPermission === null) {
return <View />;
} else if (hasCameraPermission === false) {
return <Text>No access to camera</Text>;
} else {
return (
<View style={{ flex: 1 }}>
<Camera style={{ flex: 1 }}
ref={ (ref) => {this.camera = ref} }
type={this.state.type} >
<View
style={{
flex: 1,
backgroundColor: 'transparent',
flexDirection: 'row',
}}>
<TouchableOpacity
style={{
flex: 0.1,
alignSelf: 'flex-end',
alignItems: 'center',
}}
onPress={() => {
this.setState({
type:
this.state.type === Camera.Constants.Type.back
? Camera.Constants.Type.front
: Camera.Constants.Type.back,
});
}}>
<Text style={{ fontSize: 18, marginBottom: 10, color: 'white' }}> Flip </Text>
</TouchableOpacity>
</View>
</Camera>
<View style={{flexDirection:'row',alignItems:'center',justifyContent:'center'}}>
<TouchableOpacity
onPress={this.clicked1.bind(this)} >
<Text style={{fontSize:hp('3%'),marginTop:hp('2.3%'),marginBottom:hp('2.3%')}}>Take photo</Text>
</TouchableOpacity>
</View>
</View>
);
}
}
}
Open custom camera code
I want to know how can I use the "userPrincipalName" on other pages.
what do I need to do to make it work?
in my example, I try to use the "userPrincipalName" object on the MainScreenpage but I don't understand how to do it.
this my example of the Modal page which it has the object "userPrincipalName":
import React,
{ Component } from 'react';
import {
Text,
TouchableOpacity,
StyleSheet,
View,
} from 'react-native';
import Modal from 'react-native-modal';
import PlacesNavigator from '../navigation/PlacesNavigator';
import { LinearGradient } from 'expo-linear-gradient';
import { AzureInstance, AzureLoginView } from 'react-native-azure-ad-2';
const credentials = {
client_id: 'ea00ca9e-8c37-4520-8d80-2c2bb9239bf8',
scope: 'User.Read',
};
export default class Example extends Component {
constructor(props) {
super(props);
this.state = {
visibleModal: 3,
azureLoginObject: {},
loginSuccess: false
};
this.azureInstance = new AzureInstance(credentials);
this._onLoginSuccess = this._onLoginSuccess.bind(this);
}
_renderButton = () => (
<TouchableOpacity
onPress={() => this.setState({ visibleModal: false })}>
<LinearGradient
colors={['#4c669f', '#3b5998', '#192f6a']}
style={{
height: 80,
width: 180,
borderRadius: 10,
backgroundColor: "#2196F3",
justifyContent: 'center',
alignItems: 'center',
marginTop: 50,
}}>
<Text style={{ color: 'white', fontSize: 20, fontWeight: 'bold' }}>כניסה</Text>
</LinearGradient>
</TouchableOpacity>
);
_onLoginSuccess() {
this.azureInstance.getUserInfo().then(result => {
this.setState({
loginSuccess: true,
azureLoginObject: result,
});
console.log(result);
}).catch(err => {
console.log(err);
})
}
renderWelcomeMsg = (currentTime = new Date()) => {
const currentHour = currentTime.getHours()
const splitAfternoon = 12;
const splitEvening = 17;
if (currentHour >= splitAfternoon && currentHour <= splitEvening) {
return 'צהריים טובים,';
} else if (currentHour >= splitEvening) {
return 'ערב טוב,';
}
return 'בוקר טוב,';
}
render() {
if (!this.state.loginSuccess) {
return (
<AzureLoginView
azureInstance={this.azureInstance}
onSuccess={this._onLoginSuccess}
/>)
}
if (this.state.visibleModal === 3) {
const { givenName } = this.state.azureLoginObject;
const { userPrincipalName } = this.state.azureLoginObject;////THIS IS THE OBJECT I WANT
return (
<View style={styles.container}>
<Modal
isVisible={this.state.visibleModal === 3}
animationInTiming={1000}
animationOutTiming={1000}
backdropTransitionInTiming={4000}
backdropTransitionOutTiming={4000}
animationIn={'flipInY'}
>
<LinearGradient
colors={['#43D4FF', 'white']}
style={{ borderRadius: 10 }}>
<View style={styles.modalContent}>
<Text style={{
fontWeight: "bold",
fontSize: 35,
justifyContent: 'center',
alignItems: 'center',
}}>{this.renderWelcomeMsg()} {givenName}
</Text>
<View style={styles.buttonContainer}>
{this._renderButton()}
</View>
</View>
</LinearGradient>
</Modal>
</View>
);
}
return (
<PlacesNavigator />
);
}
}
And this is the MainScreen page that i want to use the object "userPrincipalName" in the Axios:
import React, { Component } from "react";
import {
View,
Text,
StyleSheet,
ActivityIndicator,
Platform,
FlatList,
TouchableOpacity,
TouchableHighlight,
WebView
} from "react-native";
import { HeaderButtons, Item } from "react-navigation-header-buttons";
import HeaderButton from "../components/HeaderButton";
import axios from "axios";
import moment from 'moment'
import storeService from '../components/storeService'
export default class MainScreen extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
userPrincipalName: null
};
}
getData = () => {
this.setState({ isLoading: true, data: [] })
axios.get("https://harigotphat1.mekorot.co.il/ConfirmPackaotWS/OrderApprove/OrderApp_Get_Orders_To_Approve/" + userPrincipalName.split('#')[0])
.then(res => {
this.setState({
isLoading: false,
data: res.data
});
console.log(res.data);
});
}
componentDidMount() {
this.props.navigation.setParams({ getData: this.getData });
// now we load the data we stored in the async storage
storeService.loadKey('userPrincipalName').then((res) => {
console.log("THIS IS THE userPrincipalName", res) //res will contain the value given the key, store this value in your state and use it any where in the component
})
this.getData()
// this.postData()
}
renderItems = (item, index) => {
const { merhavid, yaamID, ezorID, shemEzor } = item;
return (
<TouchableHighlight style={{
backgroundColor: '#ffff78'
}}>
<TouchableOpacity
style={{
paddingVertical: 15,
paddingHorizontal: 10,
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
borderWidth: 0.8,
borderColor: '#d5d7db',
backgroundColor: index % 2 == 0 ? '#d1f0da' : '#f2f5f3',
}}
onPress={() => this.props.navigation.navigate("Info")}>
<Text style={styles.name}>
{ezorID + "" + " |" + " " + merhavid + " " + yaamID + " " + shemEzor}
</Text>
</TouchableOpacity>
</TouchableHighlight>
);
}
render() {
if (this.state.isLoading) {
return (
<View style={{ flex: 0, paddingTop: 300 }}>
<Text style={{ alignSelf: "center", fontWeight: "bold", fontSize: 20 }}>טוען נתונים...</Text>
<ActivityIndicator size={'large'} color={'#08cbfc'} />
</View>
);
}
return (
<>
<View style={styles.container}>
<FlatList
data={this.state.data}
keyExtractor={(_, index) => String(index)}
renderItem={({ item, index }) => { return this.renderItems(item, index) }}
/>
</View>
<View style={styles.bottomMainContainer}>
<View style={styles.bottomView} >
<Text style={styles.bottomTextStyle}>סה"כ: {this.state.data.length} רשומות</Text>
</View>
</View>
</>
);
}
}
This is what React Context was designed for:
Context provides a way to pass data through the component tree without having to pass props down manually at every level.
This also applies to updating data from nested components e.g.
const UserContext = React.createContext({
user: null,
setUser: () => {}
});
function UserContextProvider {
const [user, setUser] = useState(null);
return (
<UserContext.Provider value={{ user, setUser }}>
{props.children}
</UserContext.Provider>
)
}
function App() {
return (
<UserContextProvider>
<MainScreen />
</UserContextProvider>
);
}
class MainScreen extends Component {
static contextType = UserContext;
getData() {
// We should see the user details
console.log(this.context.user);
}
render() {
return (
<div>
<Example />
</div>
)
}
}
class Example extends Component {
static contextType = UserContext
_onLoginSuccess() {
this.azureInstance.getUserInfo().then(result => {
this.setState(...);
// set user in UserContext
this.context.setUser(result);
});
}
}
The best way would be use redux store which helps you create immutable data object which can be update only based on user action https://redux.js.org/basics/store/.
Another simple but not efficient way is to use the react native async storage, Where you store the object and later load it up in the componentDidMount() of your new component.
Another way is to pass these props as ScreenProps in your child component(this is only possible if the screens have a parent child relation)
solution - Sharing the object between components using asnc storage
import AsyncStorage from '#react-native-community/async-storage';
const storeService = {
async saveItem(key, value) {
try {
await AsyncStorage.setItem(key, value);
} catch (error) {
console.log('AsyncStorage Error: ' + error.message);
}
},
loadKey(key) {
return new Promise((resolve, reject) => {
AsyncStorage.getItem(key)
.then(res => {
resolve(res)
})
.catch(err => reject(err));
});
}
};
export default storeService;
Note that these stateless component has 2 methods, One is to save against a key and another is to load.
Now to save a value against a key use
import React,
{ Component } from 'react';
....
export default class Example extends Component {
constructor(props) {
super(props);
....
}
....
render() {
...
if (this.state.visibleModal === 3) {
const { givenName } = this.state.azureLoginObject;
const { userPrincipalName } = this.state.azureLoginObject;
//this is how we will store the value when this component Example loads
storeService.saveItem('userPrincipalName', userPrincipalName)
return (
....
);
}
return (
<PlacesNavigator />
);
}
}
And to load this item again use
import React, { Component } from "react";
...
export default class MainScreen extends Component {
constructor(props) {
super(props);
this.state = {
userPricipalName: null //initialize this state variable
data: []
};
}
getData = () => {
...
var userPrincipalName = this.state.userPrincipalName;
axios.get("https://harigotphat1.mekorot.co.il/ConfirmPackaotWS/OrderApprove/OrderApp_Get_Orders_To_Approve/"+userPrincipalName.split('#')[0])
....
}
componentDidMount() {
// now we load the data we stored in the async storage
storeService.loadKey('userPrincipalName').then((res) => {
this.setState({userPricipalName: res}) //store like this
})
this.getData()
}
...
render() {
....
return (
....
);
}
}
Note that in order to be able to save a whole object in async store you will first need to stringify the object which storing and json parse while reading it again.