I have this code in react native i want to make a test:
const granted = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
{
title: 'Device current location permission',
message: 'Allow app to get your current location',
buttonNeutral: 'Ask Me Later',
buttonNegative: 'Cancel',
buttonPositive: 'OK',
}
);
i am looking at an answer here but seem not working: How to mock PermissionAndroid from react native
jest.doMock('react-native/Libraries/PermissionsAndroid/PermissionsAndroid', () => ({
...jest.requireActual('react-native/Libraries/PermissionsAndroid/PermissionsAndroid'),
request: () => ({
[PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION]: PermissionsAndroid.RESULTS.GRANTED,
}),
check: () => true,
}));
on ios i am doing it like this which was quick:
jest.spyOn(Geolocation, 'requestAuthorization').mockResolvedValue('granted');
i can't seem to think of a way how to do it in android?
I could recommend you to try with react-native-permissions. It already have a mock file that you can use. You can use Jest + React Testing Library Native.
In my case I used a jest-setup.js file
import {jest} from '#jest/globals';
jest.mock('react-native-permissions', () =>
require('react-native-permissions/mock'),
);
Then configure jest inside you package.json file
"jest": {
"preset": "react-native",
"setupFilesAfterEnv": [
"#testing-library/jest-native/extend-expect"
],
"transformIgnorePatterns": [
"node_modules/(?!(jest-)?react-native-permissions|)"
],
"setupFiles": [
"./jest-setup.js"
]
}
For Geolocation service I created a react-native-geolocation-service inside __mocks__/react-native-geolocation-service.js
export default {
getCurrentPosition: jest.fn().mockImplementation(successCallback => {
const position = {
coords: {
latitude: 57.7,
longitude: 11.93,
},
};
successCallback(position);
}),
};
In your App component you could have this
import React, {useEffect, useState} from 'react';
import { View } from 'react-native';
import {check, request, PERMISSIONS, RESULTS} from 'react-native-permissions';
import Geolocation from 'react-native-geolocation-service';
const App = () => {
const [location, setLocation] = useState(null);
const handleLocationPermission = async () => {
let permissionCheck = '';
if (Platform.OS === 'ios') {
permissionCheck = await check(PERMISSIONS.IOS.LOCATION_WHEN_IN_USE);
if (permissionCheck === RESULTS.DENIED) {
const permissionRequest = await request(
PERMISSIONS.IOS.LOCATION_WHEN_IN_USE,
);
permissionRequest === RESULTS.GRANTED
? console.warn('Location permission granted.')
: console.warn('Location perrmission denied.');
}
}
if (Platform.OS === 'android') {
permissionCheck = await check(PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION);
if (permissionCheck === RESULTS.DENIED) {
const permissionRequest = await request(
PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION,
);
permissionRequest === RESULTS.GRANTED
? console.warn('Location permission granted.')
: console.warn('Location perrmission denied.');
}
}
};
useEffect(() => {
handleLocationPermission();
}, []);
useEffect(() => {
Geolocation.getCurrentPosition(
position => {
const {latitude, longitude} = position.coords;
setLocation({latitude, longitude});
},
error => {
console.log(error.code, error.message);
},
{enableHighAccuracy: true, timeout: 15000, maximumAge: 10000},
);
}, []);
return (<View></View>)
}
Then in you test file you can test the behaviour using React Testing Library Native.
import React from 'react';
import {render, waitFor} from '#testing-library/react-native';
import App from '../App';
import {check} from 'react-native-permissions';
import Geolocation from 'react-native-geolocation-service';
describe('<UserScreen />', () => {
test('should check for permissions', async () => {
render(<App />);
await waitFor(() => {
expect(check).toHaveBeenCalledTimes(1);
expect(Geolocation.getCurrentPosition).toHaveBeenCalledTimes(1);
});
});
});
I wrote a similar and more detailed Post on my website.
Related
Let me start by saying I see other answered questions with this a similar title but nothing is working for me.
I'm trying to get the users Birthday from Facebooks API, I'm using expo-auth-session.
I can successfully get the users ID, email and name but nothing else.
Here is my code, what am I missing?
const [request, response, promptAsync] = Facebook.useAuthRequest({
// clientId: '2877415105738397',
androidClientId: FacebookClientId,
iosClientId: FacebookClientId,
expoClientId: FacebookClientId,
scopes: ['user_birthday '],
redirectUri: makeRedirectUri({ useProxy: true })
});
Once I have the access_token
await axios.get(`https://graph.facebook.com/v15.0/me?fields=id,name,email,birthday&access_token=${token}`)
But I only get the {"email": "email#gmail.com", "id": "123415656565", "name": "Name"}
I do have app review permissions
UPDATE, FULL COMPONENT BELOW
import * as React from 'react';
import { View } from 'react-native';
import * as AppAuth from 'expo-auth-session';
import { useAuthRequest } from 'expo-auth-session/build/providers/Facebook';
import { FacebookClientId } from '../config/constant';
import { TouchableOpacity } from 'react-native';
import { FacebookIcon } from '../assets/SvgIcons/FacebookIcon';
import { Styling } from '../assets/style/styling';
import { useEffect, useState } from 'react';
import { onSignupExternal } from '../store/user/user.action';
import { useNavigation } from '#react-navigation/core';
import { useDispatch, useSelector } from 'react-redux';
const useProxy = Platform.select({ web: false, default: true, 'android': true, 'ios': true });
const discovery = {
authorizationEndpoint: 'https://www.facebook.com/v6.0/dialog/oauth',
tokenEndpoint: 'https://graph.facebook.com/v6.0/oauth/access_token',
};
export const FaceBook = ({ setLoading }) => {
const [userData, setUserData] = useState({})
const navigation = useNavigation()
const dispatch = useDispatch();
const { user } = useSelector((storeState) => storeState.userModule);
const clientId = FacebookClientId;
const scopes = ['public_profile', 'email', 'user_birthday'];
const [request, response, promptAsync] = useAuthRequest(
{
clientId,
scopes,
useProxy,
redirectUri: AppAuth.makeRedirectUri({
native: 'com.myapp.app:/',
useProxy,
}),
},
{useProxy: useProxy},
discovery
);
async function handlePress() {
try {
const result = await promptAsync();
if (result.type === 'success') {
setLoading(true)
getUserData(result.params.access_token)
} else {
// The user cancelled the login or an error occurred. You can inspect the result object for more details.
console.log('Login failed :(', result);
}
} catch (e) {
console.error(e);
}
}
async function getUserData(token) {
let response = await fetch(`https://graph.facebook.com/me?fields=id,name,email,birthday&access_token=${token}`);
const userInfo = await response.json()
console.log(userInfo)
setUserData({
user_email: userInfo.email,
user_id: userInfo.id,
user_full_name: userInfo.name,
user_ref_id: null,
auth_system: 'Facebook',
user_dob: null
})
}
useEffect(() => {
const getUser = async () => {
let updateUserData = userData
// let ref_id = await utilService.convertMailToAsciiCode(userData.user_id)
// updateUserData.user_ref_id = ref_id
return Object.keys(userData).length !== 0 && dispatch(onSignupExternal(userData))
};
getUser()
}, [userData])
useEffect(() => {
if (user !== null && user?.user_id !== undefined) {
setLoading(false)
return navigation.navigate("Dashboard")
}
}, [user])
return (
<TouchableOpacity style={Styling.facebookLoginBtn}
onPress={handlePress}>
<View style={Styling.googleIcon}><FacebookIcon /></View>
</TouchableOpacity>
)
}
I successfully implemented the agora SDK videocall module with virtual background to my react.js web app, but when I try to implement it to the react-native mobile version I keep getting erros I don't know how to solve. I'm a bit new to react-native so this migth be an easy fix but I can't find it.
Basically, after submitting a form with the uid, channel, role, and token (I have a token service) the videocall component is rendered.
These are my dependencies
"agora-access-token": "^2.0.4",
"agora-react-native-rtm": "^1.5.0",
"agora-extension-virtual-background": "^1.1.1",
"agora-rtc-sdk-ng": "^4.14.0",
"agora-rn-uikit": "^4.0.0",
"axios": "^0.27.2",
"react": "18.0.0",
"react-native": "0.69.4",
"react-native-agora": "^3.7.1",
"react-native-dotenv": "^3.3.1"
This is the main videocall component.
import React,{ useEffect } from "react";
import { useState } from "react";
import axios from "axios";
import { Call } from "./components/Call";
const VideoCallApp = ({ videoCallData }) => {
const [token, setToken] = useState("");
const [virtualBackgroundData, setVirtualBackgroundData] = useState({
type: "img",
// example
// type: 'img',
// value: ''
//
// type: 'blur',
// value: integer // blurring degree, low (1), medium (2), or high (3).
//
// type: 'color',
// value: string // color on hex or string
});
useEffect(() => {
const getToken = async () => {
const url = `${process.env.REACT_APP_AGORA_TOKEN_SERVICE}/rtc/${videoCallData.channel}/${videoCallData.role}/uid/${videoCallData.uid}`;
try {
const response = await axios.get(url);
const token = response.data.rtcToken;
setToken(token);
} catch (err) {
alert(err);
}
};
getToken();
}, []);
return (
token && (
<Call
rtcProps={{
appId: process.env.REACT_APP_AGORA_APP_ID,
channel: videoCallData.channel,
token: token,
uid: videoCallData.uid,
}}
virtualBackground={virtualBackgroundData}
/>
)
);
};
export default VideoCallApp;
Which renders the Call component, it has more functionality for the virtual background.
import { useEffect, useState } from 'react'
import AgoraRTC from 'agora-rtc-sdk-ng';
import VirtualBackgroundExtension from 'agora-extension-virtual-background';
import { LocalVideo } from './LocalVideo';
import { RemoteVideo } from './RemoteVideo';
import { VideoControllers } from './VideoButtons';
import { View } from 'react-native';
const client = AgoraRTC.createClient({ mode: "rtc", codec: "vp8" });
const extension = new VirtualBackgroundExtension();
AgoraRTC.registerExtensions([extension]);
export const Call = ({ rtcProps = {}, virtualBackground = {} }) => {
const [localTracks, setLocalTracks] = useState({
audioTrack: null,
videoTrack: null
});
const [processor, setProcessor] = useState(null);
useEffect(() => {
if (processor) {
try {
const initProcessor = async () => {
// Initialize the extension and pass in the URL of the Wasm file
await processor.init(process.env.PUBLIC_URL + "/assets/wasms");
// Inject the extension into the video processing pipeline in the SDK
localTracks.videoTrack.pipe(processor).pipe(localTracks.videoTrack.processorDestination);
playVirtualBackground();
}
initProcessor()
} catch (e) {
console.log("Fail to load WASM resource!"); return null;
}
}
}, [processor]);
useEffect(() => {
if (localTracks.videoTrack && processor) {
setProcessor(null);
}
}, [localTracks]);
const playVirtualBackground = async () => {
try {
switch (virtualBackground.type) {
case 'color':
processor.setOptions({ type: 'color', color: virtualBackground.value });
break;
case 'blur':
processor.setOptions({ type: 'blur', blurDegree: Number(virtualBackground.value) });
break;
case 'img':
const imgElement = document.createElement('img');
imgElement.onload = async () => {
try {
processor.setOptions({ type: 'img', source: imgElement });
await processor.enable();
} catch (error) {
console.log(error)
}
}
imgElement.src = process.env.PUBLIC_URL + '/assets/backgrounds/background-7.jpg';
imgElement.crossOrigin = "anonymous";
break;
default:
break;
}
await processor.enable();
} catch (error) {
console.log(error)
}
}
const join = async () => {
await client.join(rtcProps.appId, rtcProps.channel, rtcProps.token, Number(rtcProps.uid));
}
const startVideo = () => {
AgoraRTC.createCameraVideoTrack()
.then(videoTrack => {
setLocalTracks(tracks => ({
...tracks,
videoTrack
}));
client.publish(videoTrack);
videoTrack.play('local');
})
}
const startAudio = () => {
AgoraRTC.createMicrophoneAudioTrack()
.then(audioTrack => {
setLocalTracks(tracks => ({
...tracks,
audioTrack
}));
client.publish(audioTrack);
});
}
const stopVideo = () => {
localTracks.videoTrack.close();
localTracks.videoTrack.stop();
client.unpublish(localTracks.videoTrack);
}
const stopAudio = () => {
localTracks.audioTrack.close();
localTracks.audioTrack.stop();
client.unpublish(localTracks.audioTrack);
}
const leaveVideoCall = () => {
stopVideo();
stopAudio();
client.leave();
}
async function startOneToOneVideoCall() {
join()
.then(() => {
startVideo();
startAudio();
client.on('user-published', async (user, mediaType) => {
if (client._users.length > 1) {
client.leave();
alert('Please Wait Room is Full');
return;
}
await client.subscribe(user, mediaType);
if (mediaType === 'video') {
const remoteVideoTrack = user.videoTrack;
remoteVideoTrack.play('remote');
}
if (mediaType === 'audio') {
user.audioTrack.play();
}
});
});
}
// Initialization
function setProcessorInstance() {
if (!processor && localTracks.videoTrack) {
// Create a VirtualBackgroundProcessor instance
setProcessor(extension.createProcessor());
}
}
async function setBackground() {
if (localTracks.videoTrack) {
setProcessorInstance()
}
}
useEffect(() => {
startOneToOneVideoCall();
}, []);
return (
<View >
<View>
<LocalVideo />
<RemoteVideo />
<VideoControllers
actions={{
startAudio,
stopAudio,
startVideo,
stopVideo,
leaveVideoCall,
startOneToOneVideoCall,
setBackground
}}
/>
</View>
</View>
)
}
The local an remote video component are emty Views where the videos are displayed and the VideoControllers are Buttons that manage the videocall.
When I run the app the form works fine but as soon as I subbmit it the app crashes with these errors.
WARN `new NativeEventEmitter()` was called with a non-null argument without the required `addListener` method.
WARN `new NativeEventEmitter()` was called with a non-null argument without the required `removeListeners` method.
LOG Running "videocall" with {"rootTag":1}
ERROR TypeError: window.addEventListener is not a function. (In 'window.addEventListener("online", function () {
_this32.networkState = EB.ONLINE;
})', 'window.addEventListener' is undefined)
VideoCallApp
Form#http://localhost:8081/index.bundle?platform=android&dev=true&minify=false&app=com.videocall&modulesOnly=false&runModule=true:121056:41
RCTView
View
RCTView
View
AppContainer#http://localhost:8081/index.bundle?platform=android&dev=true&minify=false&app=com.videocall&modulesOnly=false&runModule=true:78626:36
videocall(RootComponent)
ERROR TypeError: undefined is not an object (evaluating '_$$_REQUIRE(_dependencyMap[5], "./components/Call").Call')
Something is happening at the Call component and I think it migth be the DOM manipulation for the videos but I can't find an example of a react-native project with agora SDK.
I don't want to use the UIkit because, eventhough it works, I can't use the virtual background which I need for this project. Can anyone help me?
I am receiving the following error message when loading a url mp3 file using expo-av in React Native.
Possible Unhandled Promise Rejection (id: 2):
Error: The AVPlayerItem instance has failed with the error code -1004 and domain "NSURLErrorDomain".```
Here is a simplified version of my code:
import { Text, View } from 'react-native';
import React, { useState, useEffect } from 'react';
import { Audio } from 'expo-av';
const AudioPlayer = ({ audioURL }) => {
const [playbackInstance, setPlaybackInstance] = useState();
const [playbackInstanceInfo, setPlaybackInstanceInfo] = useState({
position: 0,
duration: 0,
state: 'Buffering',
});
async function togglePlay() {
if (playbackInstanceInfo.state === 'Playing') {
setPlaybackInstanceInfo({
...playbackInstanceInfo,
state: 'Paused',
});
await playbackInstance.pauseAsync();
} else {
setPlaybackInstanceInfo({
...playbackInstanceInfo,
state: 'Play',
});
await playbackInstance.playAsync();
}
}
const onPlaybackStatusUpdate = status => {
if (status.isLoaded) {
setPlaybackInstanceInfo({
...playbackInstanceInfo,
position: status?.positionMillis,
duration: status?.durationMillis || 0,
state: status.didJustFinish
? 'Ended'
: status.isBuffering
? 'Buffering'
: status.shouldPlay
? 'Playing'
: 'Paused',
});
} else {
if (status.isLoaded === false && status.error) {
const errorMsg = `Encountered a fatal error
during playback: ${status.error}`;
console.log(errorMsg, 'error');
}
}
};
useEffect(() => {
const loadAudio = async () => {
const { sound } = await Audio.Sound.createAsync(
{ uri: audioURL },
{ shouldPlay: false },
onPlaybackStatusUpdate
);
setPlaybackInstance(sound);
};
loadAudio();
}, []);
useEffect(() => {
return playbackInstance
? () => {
playbackInstance.unloadAsync();
}
: undefined;
}, [playbackInstance]);
return (
<View></View>
)};
export default AudioPlayer;
Dependency Versions:
"expo": "^46.0.0",
"expo-av": "~12.0.4",
"react-native": "0.69.6"
I tried to delete node modules and package-lock.json, then reinstall, but that didn't help. I tried to update expo-av to 13.0.1 but I get a warning telling me that some dependencies I have are not compatible with it, so just went back to ~12.0.4. I'm using Node.js 15.0.1 with Express ^4.17.1
I'm trying to use SVG in my project with react-native-svg and react-native-svg-transformer
, but I'm getting error React is not defined in the SVG file.
These are my configurations
metro.config.js
module.exports = (async () => {
const {
resolver: { sourceExts, assetExts }
} = await getDefaultConfig();
const config1 =
{transformer: {
babelTransformerPath: require.resolve("react-native-svg-transformer")
},
resolver: {
assetExts: assetExts.filter(ext => ext !== "svg"),
sourceExts: [...sourceExts, "svg"]
}}
const config2 ={
transformer: {
// eslint-disable-next-line #typescript-eslint/require-await
getTransformOptions: async () => ({
transform: {
experimentalImportSupport: false,
inlineRequires: true
}
}),
babelTransformerPath: require.resolve('react-native-typescript-transformer')
}}
return mergeConfig(config1, config2)
})();
declarations.d.ts
declare module "*.svg" {
import React from 'react';
import { SvgProps } from "react-native-svg";
const content: React.FC<SvgProps>;
export default content;
}
I have tried the command yarn start --reset-cache but it didn't work
I am a new in react native Redux Newest Documentation. I want to use createAsyncThunk
to get my api data using Axios.
Below my code :
App.js
import React, { useState } from 'react';
import { Provider } from 'react-redux';
import { configureStore } from '#reduxjs/toolkit';
import ApiChartingSlice from './redux/slice/api/ApiChartingSlice';
import LoginNavigator from './navigation/LoginNavigator';
const store = configureStore({
reducer: {
ApiChartingMenu: ApiChartingSlice
}
});
export default function App() {
return (
<Provider store={store}>
<LoginNavigator />
</Provider>
);
}
ApiChartingAxios.js
import axios from "axios";
import { BasicAuthUsername, BasicAuthPassword } from "../utility/utility";
export default axios.create({
baseURL: 'https://blablabla.id/index.php',
headers: {
auth: {
username: BasicAuthUsername,
password: BasicAuthPassword
}
}
});
SubApiChartingAxios.js
import ApiChartingAxios from "../ApiChartingAxios";
export const SubApiChartingMenu = async () => {
const response = await ApiChartingAxios.get('/ApiChartingMenu',{
params: null
});
return response;
};
ApiChartingSlice.js
import { createAsyncThunk, createSlice } from '#reduxjs/toolkit'
import { SubApiChartingMenu } from '../../../api/subapi/SubApiChartingAxios';
export const ApiChartingMenuThunk = createAsyncThunk(
'ApiChartingMenu',
async () => {
const response = await SubApiChartingMenu();
console.log(response);
return response.data.Data;
}
)
// status: 'idle' | 'loading' | 'succeeded' | 'failed',
const ApiChartingMenuSlice = createSlice({
name: 'ApiChartingMenu',
initialState: {
apiData: {},
status: 'idle',
error: null
},
reducers: {},
extraReducers: {
[ApiChartingMenuThunk.pending.type]: (state, action) => {
state.playerList = {
status: "loading",
apiData: {},
error: {},
};
},
[ApiChartingMenuThunk.fulfilled.type]: (state, action) => {
state.playerList = {
status: "idle",
apiData: action.payload,
error: {},
};
},
[ApiChartingMenuThunk.rejected.type]: (state, action) => {
state.playerList = {
status: "idle",
apiData: {},
error: action.payload,
};
},
}
});
export default ApiChartingMenuSlice.reducer;
And the last is my screen output:
ChartScreen.js
import { useNavigation } from '#react-navigation/native';
import React, { useEffect, useState, useCallback } from 'react';
import { View, Text, StyleSheet, TouchableOpacity, KeyboardAvoidingView, TextInput, Button } from 'react-native';
import { useDispatch, useSelector } from 'react-redux';
import { ApiChartingMenuSlice, ApiChartingMenuThunk } from '../../redux/slice/api/ApiChartingSlice';
const ChartScreen = () => {
console.log('ChartScreen');
const dispatch = useDispatch();
console.log(dispatch(ApiChartingMenuThunk()));
const chartData = useSelector(state => state.ApiChartingMenu.apiData);
console.log(chartData);
return (
<View>
<Button title="test" onPress={() => {}} />
<ChartComponent />
</View>
);
};
export default ChartScreen;
Problem:
I don't know why in my ChartScreen.js this line : console.log(dispatch(ApiChartingMenuThunk()));
return :
Promise {
"_U": 0,
"_V": 0,
"_W": null,
"_X": null,
"abort": [Function abort],
"arg": undefined,
"requestId": "oqhkA7eyL_VV_ea4FDxr3",
"unwrap": [Function unwrap],
}
But in ApiChartingSlice.js in this line console.log(response);
return the correct value.
So, what is the correct way to retrive the value from the createAsyncThunk from my ChartScreen.js
The Api content is a list menu.
I want when first open the apps It execute the redux and show all my list menu.
But now just try to console.log the ApiChartingMenuThunk in ApiChartingSlice.js is not working.
Can anybody solve and guide me to a solution ? Thank You
I figured it out myself the problem is on this file :
ApiChartingSlice.js
and this line should be :
[ApiChartingMenuThunk.fulfilled.type]: (state, action) => {
state.playerList = {
status: "idle",
apiData: state.apiData.push(action.payload),
error: {},
};
also you need to dispatch using this code :
in file ChartScreen.js
this is how we dispatch it.
const toggleGetMenuHandler = useCallback(() => {
dispatch(ApiChartingMenuThunk())
.unwrap()
.then((originalPromiseResult) => {
// console.log(originalPromiseResult);
})
.catch((rejectedValueOrSerializedError) => {
console.log(rejectedValueOrSerializedError);
})
}, [dispatch]);
useEffect(() => {
toggleGetMenuHandler();
}, [toggleGetMenuHandler]);
},
Now this code : const chartData = useSelector(state => state.ApiChartingMenu.apiData);
will have a correct data.