Im adapting a project created by the agora.io community. the base project is here: https://github.com/AgoraIO-Community/Agora-RN-Quickstart. A quick look at the App.tsx file there will give you the file im adapting. also easy to download and run
now initially, the project works fine, both start and end call buttons work out of the box. it needs some UI and functional updates though for my endgame. in the process of updating dynamic call start/end buttons, and adding a call time ui piece.
problem: all of sudden the end call button doesn't want to execute its onPress once the view returned by _renderVideos()
is loaded. the start call function is async, so it takes a second or two for the new view to render. in this couple second window, the end call button can be pressed, and its function executed.
here's some of my adaptations:
import React from 'react';
import { View, Text, NativeModules, ScrollView, ActivityIndicator, TouchableOpacity, Dimensions, Platform, StyleSheet } from 'react-native';
import RtcEngine, { RtcLocalView, RtcRemoteView, VideoRenderMode } from 'react-native-agora';
import requestCameraAndAudioPermission from '../androidChatPermissions.js'
import * as Colors from '../assets/colors'
const AGORA_APP_ID = '<...>';
const { height, width } = Dimensions.get('window');
interface Props {
userId: string
}
interface State {
appId: string,
channelName: string,
joinSucceed: boolean,
peerIds: number[],
callStarted: boolean,
continuityModal: boolean,
endCallModal: boolean,
microphoneMuted: boolean,
roomNotification: string,
clockRunning: boolean,
clockTime: string,
chatSec: number,
chatMin: number
}
export default class Chatroom extends React.Component<Props, State> {
_engine?: RtcEngine
clock: Timestamp
static navigationOptions = {
headerShown: false
}
constructor(props) {
super(props)
this.state = {
appId: AGORA_APP_ID,
joinSucceed: false,
peerIds: [],
channelName: 'channel-x',
callStarted: false,
continuityModal: false,
endCallModal: false,
microphoneMuted: false,
roomNotification: null,
clockRunning: false,
clockTime: null,
chatSec: null,
chatMin: null
}
if (Platform.OS === 'android') [
requestCameraAndAudioPermission().then(_ => {
console.log('permissions requested')
})
]
}
componentDidMount() {
this.init()
}
init = async () => {
// no changes from base project's App.tsx
}
startCall = async () => {
await this._engine?.joinChannel(null, this.state.channelName, null, 0)
this.setState({ callStarted: true }, () => this.runClock())
}
endCall = async () => {
console.log("end call pressed")
await this._engine?.leaveChannel()
this.setState({ peerIds: [], joinSucceed: false, callStarted: false })
}
runClock() {
console.log("clock running")
}
render() {
return (
<View style={styles.max}>
<View style={[styles.max, { backgroundColor: 'blue' }]}>
<View style={styles.detailsWrapper}>
{!this.state.callStarted ? (
<TouchableOpacity
onPress={this.startCall}
style={[styles.button, { backgroundColor: Colors.PRIMARY_GREEN }]}>
<Text style={styles.buttonText}> Start Call </Text>
</TouchableOpacity>
) : (
<View style={{ marginTop: 20 }}>
<Text>{this.state.chatTime}</Text>
<TouchableOpacity
onPress={this.endCall}
style={[styles.button, { backgroundColor: Colors.PRIMARY_ACTION }]}>
<Text style={styles.buttonText}> End Call </Text>
</TouchableOpacity>
</View>
)}
</View>
{this._renderVideos()}
</View>
</View>
)
}
_renderVideos = () => {
const { joinSucceed } = this.state
return joinSucceed ? (
<View style={styles.fullView}>
<RtcLocalView.SurfaceView
style={styles.max}
channelId={this.state.channelName}
renderMode={VideoRenderMode.Hidden} />
{this._renderRemoteVideos()}
</View>
) : null
}
_renderRemoteVideos = () => {
const { peerIds } = this.state
return (
<ScrollView
style={styles.remoteContainer}
contentContainerStyle={{ paddingHorizontal: 2.5 }}
horizontal={true}>
{peerIds.map((value, index, array) => {
return (
<RtcRemoteView.SurfaceView
style={styles.remote}
uid={value}
channelId={this.state.channelName}
renderMode={VideoRenderMode.Hidden}
zOrderMediaOverlay={true} />
)
})}
</ScrollView>
)
}
}
const styles = StyleSheet.create({
max: {
flex: 1,
},
detailsWrapper: {
position: 'absolute',
top: 50,
alignSelf: 'center',
height: 100,
width: width,
alignItems: 'center',
flex: 1,
flexDirection: 'row',
justifyContent: 'space-evenly',
borderWidth: 1,
borderColor: 'red'
},
button: {
paddingHorizontal: 20,
paddingVertical: 10,
borderRadius: 10,
},
buttonText: {
color: '#fff',
fontWeight: 'bold'
},
fullView: {
width: width,
height: 50,
backgroundColor: '#333'
},
remoteContainer: {
width: '100%',
height: 150,
position: 'absolute',
top: 5
},
remote: {
width: 150,
height: 150,
marginHorizontal: 2.5
},
noUserText: {
paddingHorizontal: 10,
paddingVertical: 5,
color: '#0093E9',
},
})
As you can see the only changes ive made so far were a couple logs, additional function call from after setState
, dynamic button rendering, and some style adjustments. It's weird, everything seems frozen or blocked, Command+R and Command+I don't work either
Why is the onPress of the endCall button only executing before the new view renders?
The issue here, relates to the absolute positioning I had updated the detailsWrapper with. the way in which the details wrappers, and _renderVideos was ordered in the render function affected the end call button by overlaying an invisible portion of the lower end of the renderVideos container. wasn't able to see it because the inspector was not working.
ultimately there are two fixes:
what I did was to add a zIndex on the details wrapper style
but I suppose you could also mess with the order of the views in the render. as an absolutely positioned view will be absolutely positioned relevant to the encasing parent view