I am new in React Native, so I don't master all related to background tasks, when I have to keep the app track playing even the user locks or hibernates his device.
Actually I achieved in a project that just plays a bundle track, like you can see:
import * as React from 'react';
import { Text, View, StyleSheet, Button } from 'react-native';
import { Audio } from 'expo-av';
export default function App() {
const [sound, setSound] = React.useState();
async function playSound() {
console.log('Loading Sound');
//On function below, could be used the following line below to get some file from ./assets folder, from device storage using require()
//method, or using loadAsync({uri:url}) to get the file from web by its url, what can be noticed in the delimited block with "*** ***"
//const { sound } = await Audio.Sound.createAsync( require('./assets/Hello.mp3')
//***
const sound = new Audio.Sound();
await sound.loadAsync({
uri: 'https://sound-library.net/wp-content/uploads/2022/08/oneheart-reidenshi-snowfall.mp3',
});
//***
setSound(sound);
console.log('Playing Sound');
await sound.playAsync();
}
React.useEffect(() => {
return sound
? () => {
console.log('Unloading Sound');
sound.unloadAsync();
}
: undefined;
}, [sound]);
return (
<View style={styles.container}>
<Button title="Play Sound" onPress={playSound} />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
I would like to know how I could do that with this feature.
I am trying to pass token and other user details from React Web application to React Native application with Webview (react-native).
When I do
window.postMessage(JSON.stringify(reactNativeObj), '*');
and log the event in React native application with
console.log( "On Message", event.nativeEvent.data );
It is printing the log as
On Message: setImmediate$0.4162155499933975$1
It should print my object instead.
I have tried almost everything and re-read the document https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage but couldn't make it work.
Any help would be appreciated.
If you're using RN >= 0.60 then you're using react-native-webview >= 8.0.0, which you'll have to use window.ReactNativeWebView.postMessage.
If you have access and can edit the website code, then you should add a postMessage call like this:
if (window.ReactNativeWebView) {
window.ReactNativeWebView.postMessage(JSON.stringify(reactNativeObj));
}
If you don't have access to the website code or if you don't want to alter its' contents, then you can inject some code that modifies window.postMessage to use window.ReactNativeWebView.postMessage along with the default method:
const WebView_Injection_postMessage = `
(function() {
window.default_postMessage = window.postMessage;
window.postMessage = function(data, domain) {
window.default_postMessage(data, domain);
if (window.ReactNativeWebView) {
window.ReactNativeWebView.postMessage(data);
}
}
})();
`;
const App: () => React$Node = () => {
const [result, setResult] = useState('');
function onWebViewMessage(event) {
console.log('onWebViewMessage', event.nativeEvent.data);
setResult(JSON.stringify(JSON.parse(event.nativeEvent.data), null, 2));
}
return (
<>
<StatusBar barStyle="dark-content" />
<SafeAreaView style={styles.container}>
<View style={styles.webViewContainer}>
<WebView
source={{ uri: 'https://zikro.gr/dbg/so/60052061/react' }}
style={styles.webWiew}
injectedJavaScript={WebView_Injection_postMessage}
onMessage={onWebViewMessage}
/>
</View>
<View style={styles.resultView}>
<Text style={styles.resultText}>{result}</Text>
</View>
</SafeAreaView>
</>
);
};
`;
The web app at https://zikro.gr/dbg/so/60052061/react is a React 16 development app with the following code:
<script src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/#babel/standalone/babel.min.js"></script>
<script type="text/babel">
class App extends React.Component {
postMessage = () => {
console.log('Posting message...');
const reactNativeObj = {
location: window.location.href,
title: document.title,
dt: (new Date).toISOString()
}
window.postMessage(JSON.stringify(reactNativeObj), '*');
}
render() {
return (
<button onClick={this.postMessage} id="post-message">Post Message</button>
)
};
}
ReactDOM.render(<App/>, document.getElementById('main'));
</script>
Last but not least, window.ReactNativeWebView.postMessage accepts only one parameter with the data to be send and it has to be a string.
window.ReactNativeWebView.postMessage only accepts one argument which must be a string.
Here is a screen capture with the working app:
I am new to React Native and struggling a little to get this working. I have realtime database in Firebase which contains 'mechanic' names. I would like to retrieve these names and display them in a list.
I would like to display this data in a list and then execute some function when the user clicks on either name. I thought adding the database data to an array then looping through the array to add it to my FlatList.
The problem now is that when I execute the code, the this.setState({ mechanicsList: mechanicsTemp }); returns an error.
Error
[Unhandled promise rejection: TypeError: this.setState is not a function.
(In 'this.setState({]
* src\screens\FindMechanics.js:28:30 in <unknown>
- node_modules\promise\setimmediate\core.js:37:14 in tryCallOne
- node_modules\promise\setimmediate\core.js:123:25 in <unknown>
- ... 8 more stack frames from framework internals
Full Code
import React, { Component } from 'react';
import { View, Text, SafeAreaView, TouchableOpacity, ScrollView, StyleSheet } from "react-native";
import { Card } from 'react-native-elements'
import firebase from "firebase/app";
export default class FindMechanics extends Component {
constructor(props) {
super(props);
this.state = {
mechanicsList: [],
isDataLoaded: false
}
}
componentDidMount() {
var query = firebase.database().ref("MechanicList").orderByKey();
query.once("value")
.then(function (snapshot) {
let mechanicsTemp = [];
snapshot.forEach(function (childSnapshot) {
// key will be the auth ID for each user
var key = childSnapshot.key;
var mechanicName = snapshot.child(key + '/name').val();
mechanicsTemp.push({ _name: mechanicName, _key: key });
});
mechanicsList = mechanicsTemp;
() => this.setState({ mechanicsList: mechanicsTemp }); // This does not execute it seems - main problem I believe
//this.setState({ mechanicsList: mechanicsTemp }); - This return a warning 'this.setState is not a function'
console.log(mechanicsList); //Prints data as expected
mechanicsTemp.forEach((mechanic) => {
console.log( mechanic._name); //Prints data as expected
});
});
}
render() {
//The Card element is empty - nothing shows.
console.log(this.state.mechanicsList) //This return Array [] which indicates it is empty
return (
<SafeAreaView style={styles.container}>
<ScrollView horizontal={true}>
<TouchableOpacity>
<Card style={styles.card}>
{
this.state.mechanicsList.map((u, i) => {
return (
<View key={i}>
<Text>{u._key}</Text>
<Text>{u._name}</Text>
</View>
);
})
}
</Card>
</TouchableOpacity>
</ScrollView>
</SafeAreaView>
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#FFF'
},
paragraph: {
margin: 24,
fontSize: 18,
textAlign: 'center',
},
card: {
flex: 1,
width: '80%',
},
});
Console
Finished building JavaScript bundle in 384ms.
Running application on Android SDK built for x86.
Array []
1st thing, you have mechanics object in state so you need to access it like
console.log(this.state.mechanics)
2nd thing is that you are not updating state variable when you are having data, it should be like following
let mechanicsTemp = [];
snapshot.forEach(function (childSnapshot) {
// key will be the auth ID for each user
var key = childSnapshot.key;
var mechanicName = snapshot.child(key + '/name').val();
mechanicsTemp.push({_name: mechanicName, _key: key});
});
this.setState({ mechanics:mechanicsTemp })
I dunno if you still need help with this or not but I just used your code and I solved this.setState problem with binding. You can either use arrow function or bind your function:
.then(function (snapshot) {
// ..
}.bind(this));
I'm developing an app with React Native and I'm testing with my OnePlus 6 and it has a notch. The SafeAreaView is a solution for the iPhone X but for Android, it seems there is no solution.
How to solve this kind of issue?
Do something like
import { StyleSheet, Platform, StatusBar } from "react-native";
export default StyleSheet.create({
AndroidSafeArea: {
flex: 1,
backgroundColor: "white",
paddingTop: Platform.OS === "android" ? StatusBar.currentHeight : 0
}
});
And then In your App.js
import SafeViewAndroid from "./components/SafeViewAndroid";
<SafeAreaView style={SafeViewAndroid.AndroidSafeArea}>
<Layout screenProps={{ navigation: this.props.navigation }} /> //OR whatever you want to render
</SafeAreaView>
This should work good as get height will take care of the knotch in android device by calculating the statusBar height and it will arrange accordingly.
A work around I had to use recently:
GlobalStyles.js:
import { StyleSheet, Platform } from 'react-native';
export default StyleSheet.create({
droidSafeArea: {
flex: 1,
backgroundColor: npLBlue,
paddingTop: Platform.OS === 'android' ? 25 : 0
},
});
It is applied like so:
App.js
import GlobalStyles from './GlobalStyles';
import { SafeAreaView } from "react-native";
render() {
return (
<SafeAreaView style={GlobalStyles.droidSafeArea}>
//More controls and such
</SafeAreaView>
);
}
}
You'll probably need to adjust it a bit to fit whatever screen you're working on, but this got my header just below the icon strip at the top.
Late 2020 answer: For anyone stumbling across this issue themselves, they have added support for this.
Follow this documentation page
You could also create helper component with this style applied right away like this
import React from 'react';
import { StyleSheet, Platform, StatusBar, SafeAreaView } from 'react-native';
export default props => (
<SafeAreaView style={styles.AndroidSafeArea} {...props} >
{props.children}
</SafeAreaView>
);
const styles = StyleSheet.create({
AndroidSafeArea: {
paddingTop: Platform.OS === 'android' ? StatusBar.currentHeight : 0
}
});
Make note that I also deleted unnecessary styles which breaks natural behavior of SafeAreaView which in my case broke styling.
As for use you simply use it like normal SafeAreaView:
import React from 'react';
import SafeAreaView from "src/Components/SafeAreaView";
render() {
return (
<SafeAreaView>
// Rest of your app
</SafeAreaView>
);
}
}
for more consistency import:
import { Platform, StatusBar } from "react-native";
and then use it like so:
paddingTop: Platform.OS === 'android' ? StatusBar.currentHeight : 0
if you're seeing this in 2020 and you also need the web support with the Android and iOS, type this in your terminal.
expo install react-native-safe-area-context
this will install the updated safe area context.
Then import the following stuffs into your app.js
import { SafeAreaView, SafeAreaProvider} from "react-native-safe-area-context";
add <SafeAreaProvider> before all the tags in your main function in app.js, also remember to close it at the end.
and finally, instead of view, add SafeAreaView.
Read more at the official expo website : SafeAreaContext
Although the docs says it is relevant only for iOS, when I used React's SafeAreaView it acted differently on different screens on Android.
I managed to fix the problem by implementing my version of SafeAreaView:
import React from "react";
import { Platform, View, StatusBar } from "react-native";
import { GeneralStyle } from "../styles";
export function SaferAreaView({ children }) {
if (Platform.OS == "ios") {
return <SaferAreaView style={{ flex: 1 }}>{children}</SaferAreaView>;
}
if (Platform.OS == "android") {
return <View style={{flex: 1, paddingTop: StatusBar.currentHeight}}>{children}</View>;
}
}
This was tested on an old device (with hardware navigation) and new notch devices (with software navigation) - different screen sizes.
This is currently the best or easiest way to implement SafeAreaView on Android and ios for both vanilla RN and Expo.
import { SafeAreaView } from 'react-native-safe-area-context';
function SomeComponent() {
return (
<SafeAreaView>
<View />
</SafeAreaView>
);
}
1 - expo install expo-constants
2- and do like this for example
import React from "react";
import Constants from "expo-constants";
import { Text, StyleSheet, SafeAreaView, View } from "react-native";
export default function HeaderTabs({ style }) {
return (
<SafeAreaView style={[styles.screen, style]}>
<View style={[styles.view, style]}>
<Text>Hello this is status bar</Text>
</View>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
screen: {
paddingTop: Constants.statusBarHeight,
flex: 1,
},
view: {
flex: 1,
},
});
Instead of using Platform API, you can use expo constants.
npm i expo-constants
then import it in your component as
import Constants from "expo-constants"
and then in the styles you can use it like this
const styles = StyleSheet.create({
container: {
paddingTop: Constants.statusBarHeight
} });
To see all the properties of Constants console log it you will find some more useful things.
Well, I had the same problem. I solved this using this lib React Native Status Bar Height, and I recommend because it´s a piece of cake to use.
And if you are using style-components you can add the getStatusBarHeight() on your styles.js like I did on the example below:
import styled from 'styled-components/native';
import { getStatusBarHeight} from 'react-native-status-bar-height';
export const Background = styled.View`
flex:1;
background:#131313;
margin-top: ${getStatusBarHeight()};
`
In the SafeAreaView Docs was told:
It is currently only applicable to iOS devices with iOS version 11 or later.
So now I definitely use it in my project but I use Platform to recognize device platform and for Android, I make a manual safe area for the status bar.
you can use react-native-device-info for device info and apply styling also with a notch
I used StatusBar from react-native instead of expo-status-bar and this worked for me on my OnePlus as well as other Android devices.
import { StatusBar } from 'react-native';
Expo solution(docs - android only):
import { setStatusBarTranslucent } from 'expo-status-bar';
Then in the component you can use useEffect hook:
useEffect(() => {
setStatusBarTranslucent(false)
},[])
for iOS you can use the <SafeAreaView> component from react-native.
ENRICO SECCO was right (i cant comment due to my stackoverflow reputation lol)! any safeareaview thingy doesn't work for me as well, so i get around with
import { getStatusBarHeight} from 'react-native-status-bar-height';
here how execute it, keep in mind that this is in my app.js, where i put all my stack.navigator + bottomtab.navigator
export default function App() {
//IGNORE ALL OF THIS, JUMP TO THE RETURN() FUNCTION!
const [appIsReady, setAppIsReady] = useState(false);
useEffect(() => {
async function prepare() {
try {
await SplashScreen.preventAutoHideAsync();
await Font.loadAsync(AntDesign.font);
await Font.loadAsync({
'Montserrat-Bold': require('./assets/fonts/Montserrat-Bold.ttf'),
'Montserrat-Regular': require('./assets/fonts/Montserrat-Regular.ttf'),
'Montserrat-Light': require('./assets/fonts/Montserrat-Light.ttf'),
});
await new Promise(resolve => setTimeout(resolve, 2000));
} catch (e) {
console.warn(e);
} finally {
// Tell the application to render
setAppIsReady(true);
}
}
prepare();
}, []);
const onLayoutRootView = useCallback(async () => {
if (appIsReady) {
await SplashScreen.hideAsync();
}
}, [appIsReady]);
if (!appIsReady) {
return null;
}
return (
//HERE!
<NavigationContainer>
<View style = {{
flex: 1, <- TO MAKE IT FULL SCREEN (PLEASE DELETE THIS)
marginTop: getStatusBarHeight(), <- TO PUSH IT DOWN FROM OFF SCREEN, MINE RAN OFF TO THE TOP LMAO (PLEASE DELETE THIS)
}} onLayout={onLayoutRootView}>
<Tabs/>
</View>
</NavigationContainer>
);
}
I'm using react-native-fs to download a file(pdf, word, excel, png etc.) and I need to open it in other application. Is it possible to open downloaded file with Linking or better open a dialog with possible apps like when using Sharing? Linking in the code below tries to open the file but it closes immediately without any notification, but my app is still working fine. Is there some special way to build URLs for deep linking for a specific file type? Any ideas for the best solution?
I see that there is old package react-native-file-opener but it's no longer maintained. This solution would be great.
Simplified code for download component:
import React, { Component } from 'react';
import { Text, View, Linking, TouchableOpacity } from 'react-native';
import { Icon } from 'react-native-elements';
import RNFS from 'react-native-fs';
import { showToast } from '../../services/toasts';
class DownloadFile extends Component {
state = {
isDone: false,
};
handleDeepLinkPress = (url) => {
Linking.openURL(url).catch(() => {
showToast('defaultError');
});
};
handleDownloadFile = () => {
RNFS.downloadFile({
fromUrl: 'https://www.toyota.com/content/ebrochure/2018/avalon_ebrochure.pdf',
toFile: `${RNFS.DocumentDirectoryPath}/car.pdf`,
}).promise.then(() => {
this.setState({ isDone: true });
});
};
render() {
const preview = this.state.isDone
? (<View>
<Icon
raised
name="file-image-o"
type="font-awesome"
color="#f50"
onPress={() => this.handleDeepLinkPress(`file://${RNFS.DocumentDirectoryPath}/car.pdf`)}
/>
<Text>{`file://${RNFS.DocumentDirectoryPath}/car.pdf`}</Text>
</View>)
: null;
return (
<View>
<TouchableOpacity onPress={this.handleDownloadFile}>
<Text>Download File</Text>
</TouchableOpacity>
{preview}
</View>
);
}
}
export default DownloadFile;
After some research, I decided to use react-native-fetch-blob. From version 0.9.0 it's possible to open downloaded file with Intent and use Download Manager. It also has API for iOS for opening documents.
Code now:
...
const dirs = RNFetchBlob.fs.dirs;
const android = RNFetchBlob.android;
...
handleDownload = () => {
RNFetchBlob.config({
addAndroidDownloads: {
title: 'CatHat1.jpg',
useDownloadManager: true,
mediaScannable: true,
notification: true,
description: 'File downloaded by download manager.',
path: `${dirs.DownloadDir}/CatHat1.jpg`,
},
})
.fetch('GET', 'http://www.swapmeetdave.com/Humor/Cats/CatHat1.jpg')
.then((res) => {
this.setState({ path: res.path() });
})
.catch((err) => console.log(err));
};
...
render() {
...
<Icon
raised
name="file-pdf-o"
type="font-awesome"
color="#f50"
onPress={() => android.actionViewIntent(this.state.path, 'image/jpg')}
...
}