I am using expo FileSystem to download the pdf file. The API response lands into success function. However, I am not able to show the downloaded file to the user.
The expected behaviour should be like we usually see notification icon on the status bar and on click on icon its opens your file.
FileSystem.downloadAsync(
'https://bitcoin.org/bitcoin.pdf',
FileSystem.documentDirectory + 'Stay_Overview.xlsx'
).then(({ uri }) => {
console.log('Finished downloading to ', uri);
})
.catch(error => {
console.error(error);
});
This one had one or two tricks, but here is a solution to this using Expo that works on both iOS and Android.
In a new Expo project, amend the following two files:
App.js
import React, { Component } from 'react';
import { View, ScrollView, StyleSheet, Button, Alert, Platform, Text, TouchableWithoutFeedback } from 'react-native';
import { FileSystem, Constants, Notifications, Permissions } from 'expo';
import Toast, {DURATION} from 'react-native-easy-toast';
async function getiOSNotificationPermission() {
const { status } = await Permissions.getAsync(
Permissions.NOTIFICATIONS
);
if (status !== 'granted') {
await Permissions.askAsync(Permissions.NOTIFICATIONS);
}
}
export default class App extends Component {
constructor(props) {
super(props);
// this.toast = null;
this.listenForNotifications = this.listenForNotifications.bind(this);
// this.openFile = this.openFile.bind(this);
this.state = {
filePreviewText: ''
}
}
_handleButtonPress = () => {
let fileName = 'document.txt';
let fileUri = FileSystem.documentDirectory + fileName;
FileSystem.downloadAsync(
"https://raw.githubusercontent.com/expo/expo/master/README.md",
fileUri
).then(({ uri }) => {
console.log('Finished downloading to ', uri);
const localnotification = {
title: 'Download has finished',
body: fileName + " has been downloaded. Tap to open file.",
android: {
sound: true,
},
ios: {
sound: true,
},
data: {
fileUri: uri
},
};
localnotification.data.title = localnotification.title;
localnotification.data.body = localnotification.body;
let sendAfterFiveSeconds = Date.now();
sendAfterFiveSeconds += 3000;
const schedulingOptions = { time: sendAfterFiveSeconds };
Notifications.scheduleLocalNotificationAsync(
localnotification,
schedulingOptions
);
})
.catch(error => {
console.error(error);
Alert.alert(error);
});
};
listenForNotifications = () => {
const _this = this;
Notifications.addListener(notification => {
if (notification.origin === 'received') {
// We could also make our own design for the toast
// _this.refs.toast.show(<View><Text>hello world!</Text></View>);
const toastDOM =
<TouchableWithoutFeedback
onPress={() => {this.openFile(notification.data.fileUri)}}
style={{padding: '10', backgroundColor: 'green'}}>
<Text style={styles.toastText}>{notification.data.body}</Text>
</TouchableWithoutFeedback>;
_this.toast.show(toastDOM, DURATION.FOREVER);
} else if (notification.origin === 'selected') {
this.openFile(notification.data.fileUri);
}
// Expo.Notifications.setBadgeNumberAsync(number);
// Notifications.setBadgeNumberAsync(10);
// Notifications.presentLocalNotificationAsync(notification);
// Alert.alert(notification.title, notification.body);
});
};
componentWillMount() {
getiOSNotificationPermission();
this.listenForNotifications();
}
componentDidMount() {
// let asset = Asset.fromModule(md);
// Toast.show('Hello World');
}
openFile = (fileUri) => {
this.toast.close(40);
console.log('Opening file ' + fileUri);
FileSystem.readAsStringAsync(fileUri)
.then((fileContents) => {
// Get file contents in binary and convert to text
// let fileTextContent = parseInt(fileContents, 2);
this.setState({filePreviewText: fileContents});
});
}
render() {
return (
<View style={styles.container}>
<View style={styles.buttonsContainer}>
<Button style={styles.button}
title={"Download text file"}
onPress={this._handleButtonPress}
/>
<Button style={styles.button}
title={"Clear File Preview"}
onPress={() => {this.setState({filePreviewText: ""})}}
/>
</View>
<ScrollView style={styles.filePreview}>
<Text>{this.state.filePreviewText}</Text>
</ScrollView>
<Toast ref={ (ref) => this.toast=ref }/>
</View>
);
// <Toast
// ref={ (ref) => this.toast=ref }
// style={{backgroundColor:'green'}}
// textStyle={{color:'white'}}
// position={'bottom'}
// positionValue={100}
// opacity={0.8}
// />
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
},
buttonsContainer: {
flexDirection: 'row',
},
button: {
flex: 1
},
filePreview: {
flex: 1,
padding: 10,
},
toastText: {
color: 'white',
padding: 5,
justifyContent: 'flex-start',
},
});
package.json: Add the following dependency (fork of react-native-easy-toast)
"react-native-easy-toast": "git+https://github.com/SiavasFiroozbakht/react-native-easy-toast.git"
There are a couple of important notes about this solution:
Uses Expo API the most, for external local notifications and writing to / reading from files, which limits the current solution to being unable to write to other locations than Expo's own directory.
Once the file is downloaded, either a customisable toast is shown to the user if the app is active (Expo currently does not support foreground notifications), or sends a local Push Notification to let the user know the download has finished. Clicking on any of these two will show the contents of the file in a View, using the <Text> component.
The crazycodeboy/react-native-easy-toast repo has not been used directly due to a limitation of the toast, which is that the touch events are currently disregarded. The forked repo makes this functionality available before the merge request is implemented in the original. I recommend switching back to the original one once it gets patched as I will likely not maintain mine.
Although this project is also available in Snack, it will not run due to the need of using a git repository in package.json as mentioned above, and other apparent inconsistencies in variable scoping. This would be fixed by either the merge request or the new feature in Snack.
Other file types may be supported, either by Expo itself or via external packages, such as this PDF viewer. However, the code will have to be further adapted.
The toast (internal notification) is created with a TouchableWithoutFeedback component, although there are other similar ones in React Native with various differences. This component can be customised in the code (search for toastDOM), but might even be replaceable in the future by internal notifications available in Expo.
Lastly, an intentional three-second delay is applied to the notification once the file is downloaded – this allows us to test the notification when the app is in background. Feel free to remove the delay and trigger the notification immediately.
And that's it! I think this gives a good starting point for file downloading and previewing with Expo.
Codebase also available on GitHub.
DownloadManager
I believe that you are looking to use the DownloadManager for handling your downloads on Android (be aware there is no DownloadManager for iOS so you would have to handle this differently) The DownloadManager either savse the file to a shared system cache or it would save it to external storage.
However, at this time I do not believe that Expo allows you to use the DownloadManager, instead handling all the downloads itself. The reason could be that Expo doesn't allow you access to external storage, as it states in the documentation:
Each app only has read and write access to locations under the following directories:
Expo.FileSystem.documentDirectory
Expo.FileSystem.cacheDirectory
https://docs.expo.io/versions/latest/sdk/filesystem
So there would be no way to access the file in Expo once it was downloaded.
Possible Solution
A possible solution would be to use React-Native Fetch Blob. It does allow you to use the DownloadManager.
https://github.com/joltup/rn-fetch-blob#android-media-scanner-and-download-manager-support
Using the DownloadManager can be achieved in rn-fetch-blob by setting the addAndroidDownloads with the useDownloadManager key set to true.
However, that would mean ejecting your application from Expo.
Related
I am having a hard time understanding the process of downloading a file pdf or excel from let's say a nodejs server in ReactNative using Expo.
So far I have gone through a bunch of blog posts/resources on the web but unfortunately I didn't find any solution for this. Most of the blogs are outdated or they don't have any clear implementation for it.
Here are a few approach that I came across.
using rn-fetch-blob but this does not work with expo as per my findings and each time I tried to integrate it with Expo, I ended up with error.
The second approach to use expo's FileSharing approach, which kind of works but only if you have a predefined image or pdf file.
import { StatusBar } from "expo-status-bar";
import { useEffect, useState } from "react";
import { StyleSheet, Text, View, Button, Alert } from "react-native";
import * as FileSystem from "expo-file-system";
import * as Sharing from "expo-sharing";
export default function App() {
const [downloadProgress, setDownloadProgress] = useState(0);
const [document, setDocument] = useState(null);
async function openShareDialogAsync() {
if (!(await Sharing.isAvailableAsync())) {
alert(`Uh oh, sharing isn't available on your platform`);
return;
}
Sharing.shareAsync(document);
}
async function handleDownload() {
const callback = (downloadProgress) => {
const progress =
downloadProgress.totalBytesWritten /
downloadProgress.totalBytesExpectedToWrite;
setDownloadProgress(progress * 100);
};
let url =
"https://www.adobe.com/support/products/enterprise/knowledgecenter/media/c4611_sample_explain.pdf";
const downloadResumable = FileSystem.createDownloadResumable(
url,
FileSystem.documentDirectory + "a.pdf",
{},
callback
);
try {
const { uri } = await downloadResumable.downloadAsync();
console.log("Finished downloading to ", uri);
setDocument(uri);
} catch (e) {
console.error(e);
}
}
useEffect(() => {
handleDownload();
}, []);
return (
<View style={styles.container}>
<Text>Open up App.js to start working on your app!</Text>
<Text>Welcome Saroj!</Text>
<Button title="Share" onPress={openShareDialogAsync} />
<StatusBar style="auto" />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
alignItems: "center",
justifyContent: "center",
},
});
This implementation kind of works. I am able to share the pdf but the issue is the pdf link is not generated on the fly, it is something already being generated previosuly. I expect something like we can call an endpoint to a server and the server would either send base64 or binary data that we can use to make the pdf download to the local storage of the device.
I am open to suggestions and would really appreciate your inputs.
Use Blob and FileReader to download any binary data. Something like this:
const response = await fetch('<link>');
const data = await response.blob()
const reader = new FileReader();
reader.onload = async () => {
const fileUri = FileSystem.documentDirectory + "file";
await FileSystem.writeAsStringAsync(
fileUri, reader.result.split(',')[1],
{ encoding: FileSystem.EncodingType.Base64 }
);
};
reader.readAsDataURL(data);
I'm new to web development and I'm trying to build an image recognition app using expo for testing. My code for the camera is below. On screen load, I get a black screen (not the camera) with my "capture" button. When I click on capture, I get the error:
Unhandled promise rejection: Error: Camera is not ready yet. Wait for 'onCameraReady' callback.
My code is below
import { Dimensions, Alert, StyleSheet, ActivityIndicator } from 'react-native';
// import { RNCamera } from 'react-native-camera';
import CaptureButton from './CaptureButton.js'
import { Camera } from 'expo-camera';
export default class AppCamera extends React.Component {
constructor(props){
super(props);
this.state = {
identifiedAs: '',
loading: false
}
}
takePicture = async function(){
if (this.camera) {
// Pause the camera's preview
this.camera.pausePreview();
// Set the activity indicator
this.setState((previousState, props) => ({
loading: true
}));
// Set options
const options = {
base64: true
};
// Get the base64 version of the image
const data = await this.camera.takePictureAsync(options)
// Get the identified image
this.identifyImage(data.base64);
}
}
identifyImage(imageData){
// Initialise Clarifai api
const Clarifai = require('clarifai');
const app = new Clarifai.App({
apiKey: '8d5ecc284af54894a38ba9bd7e95681b'
});
// Identify the image
app.models.predict(Clarifai.GENERAL_MODEL, {base64: imageData})
.then((response) => this.displayAnswer(response.outputs[0].data.concepts[0].name)
.catch((err) => alert(err))
);
}
displayAnswer(identifiedImage){
// Dismiss the acitivty indicator
this.setState((prevState, props) => ({
identifiedAs:identifiedImage,
loading:false
}));
// Show an alert with the answer on
Alert.alert(
this.state.identifiedAs,
'',
{ cancelable: false }
)
// Resume the preview
this.camera.resumePreview();
}
render () {
const styles = StyleSheet.create({
preview: {
flex: 1,
justifyContent: 'flex-end',
alignItems: 'center',
height: Dimensions.get('window').height,
width: Dimensions.get('window').width,
},
loadingIndicator: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
}
});
return (
<Camera ref={ref => {this.camera = ref;}}style={styles.preview}>
<ActivityIndicator size="large" style={styles.loadingIndicator} color="#fff" animating={this.state.loading}/>
<CaptureButton buttonDisabled={this.state.loading} onClick={this.takePicture.bind(this)}/>
</Camera>
)
}
}```
Could someone kindly point me in the right direction to fix this error?
https://docs.expo.dev/versions/latest/sdk/camera/#takepictureasyncoptions
Note: Make sure to wait for the onCameraReady callback before calling this method.
So, you might resolve if you add onCameraReady props to Camera component like this document.
I'm facing issue like this, and it is not resolved now... I hope my advice works well.
I'm trying to create a basic app with a user login feature using Redux to manage the user details. I've linked a GIF of my screen below and, as you can see, there is a delay between loading the component and the user details rendering. My code for the component profile is also noted.
Name of user delay when loading
import React, {Component} from 'react';
import {View, StyleSheet, Text, TouchableOpacity} from 'react-native';
import {connect} from 'react-redux';
import {fetchProfile} from '../../actions/ProfileActions';
import {logoutUser} from '../../actions/AuthActions';
class Profile extends Component {
state = {
firstName: '',
lastName: '',
email: '',
goals: '',
};
componentDidMount() {
this.props.fetchProfile();
}
componentDidUpdate(prevProps) {
if (prevProps !== this.props) {
this.setState({
firstName: this.props.profile.firstName,
lastName: this.props.profile.lastName,
email: this.props.profile.email,
goals: this.props.profile.goals,
});
}
}
onPressLogout = () => {
this.props.logoutUser();
};
render() {
return (
<View style={styles.container}>
<View style={styles.headerContainer}>
<Text style={styles.header}>
Profile of {this.state.firstName} {this.state.lastName}
</Text>
</View>
<View style={styles.textContainer}>
<TouchableOpacity
style={styles.buttonContainer}
onPress={this.onPressLogout.bind(this)}>
<Text style={styles.buttonText}>Logout</Text>
</TouchableOpacity>
</View>
</View>
);
}
}
const mapStateToProps = (state) => ({
profile: state.profile.profile,
});
export default connect(mapStateToProps, {fetchProfile, logoutUser})(Profile);
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F9F9F9',
},
headerContainer: {
marginTop: 75,
marginLeft: 20,
},
header: {
fontSize: 34,
fontWeight: '700',
color: '#000000',
},
textContainer: {
flex: 1,
justifyContent: 'flex-end',
alignItems: 'center',
marginBottom: 30,
},
buttonContainer: {
backgroundColor: '#34495E',
alignItems: 'center',
padding: 12,
width: 350,
borderRadius: 15,
},
buttonText: {
color: 'white',
fontWeight: 'bold',
fontSize: 19,
},
});
EDIT: I forgot to explain what fetchProfile() does. It connects to the firebase database to retrieve the user's details. Code below:
import {PROFILE_FETCH} from './types';
import firebase from 'firebase';
export const fetchProfile = () => {
const {currentUser} = firebase.auth();
return (dispatch) => {
firebase
.database()
.ref(`/users/${currentUser.uid}/profile`)
.on('value', (snapshot) => {
dispatch({
type: PROFILE_FETCH,
payload: snapshot.val(),
});
});
};
};
Furthermore, I have 3 different screens in the app, all of which will probably make use of the user's details. I'm sure there must be a more efficient way of only having to fetchProfile() once and then passing the details to each component, somehow...
How can I have it so when the user logs in, their details are already loaded in the component, so there is no delay? Thanks in advance!
One way I've gotten around this is by conditionally rendering a skeleton if it is still loading and then actually rendering the details once finished.
I'm not sure if this is exactly the solution you're looking for (and you may already know it's an option), but maybe this helps?
Using firebase you must create a listener.
do something like this:
Reducer Action:
// Action Creators
export function onAuthChanged(fb) {
return async (dispatch) => {
fb.auth().onAuthStateChanged((res) => {
dispatch({
type: Types.SET_ATTR,
payload: {
attr: 'user',
value: res,
},
});
});
};
}
call this function from a FirebaseProvider componentWillMount function
then
put the FirebaseProvider on your App class;
const App = () => {
return (
<>
<Provider store={store}>
<TranslatorProvider />
<FirebaseProvider />
<ThemeProvider>
<TrackingProvider>
<Routes />
</TrackingProvider>
</ThemeProvider>
</Provider>
</>
);
};
the listener will save on your reducer the user when it login and logout
According to what you have provided, definitely there will be a delay. I'll explain what is happening here.
You are requesting data from the firebase after you have rendered the details on Profile. This happens because you are requesting data in componentDidMount method. This method gets called first time Render method is completely finished rendering your components. So I'll suggest you two methods to get rid of that.
As Coding Duck suggested, you can show a skeleton loader until you fetch data from the firebase.
You can request these data from your login. That means, if user authentication is success, you can request these data using fetchProfile action and once you fetch these data completely, you can use Navigation.navigate('Profile') to navigate to your Profile screen rather than directly navigate to it once the authentication is success. In that time since you have fetched data already, there will be no issue.
Also you can use firebase persist option to locally store these data. So even if there were no internet connection, still firebase will provide your profile information rapidly.
EDIT
More specific answer with some random class and function names. This is just an example.
Let's say onLogin function handles all your login requirements in your authentication class.
onLogin = () => {
/** Do the validation and let's assume validation is success */
/** Now you are not directly navigating to your Profile page like you did in the GIF. I assume that's what you did because you have not added more code samples to fully understand what you have done.*/
/** So now you are calling the fetchProfile action through props and retrieve your details. */
this.props.fetchProfile(this.props.navigation);
};
Now let's modify your fetchDetails action.
export const fetchProfile = (navigation) => {
const {currentUser} = firebase.auth();
return (dispatch) => {
firebase
.database()
.ref(`/users/${currentUser.uid}/profile`)
.on('value', (snapshot) => {
dispatch({
type: PROFILE_FETCH,
payload: snapshot.val(),
});
navigation.navigate('Profile')
});
};
};
Note : This is not the best method of handling navigations but use a global navigation service to access directly top level navigator. You can learn more about that in React Navigation Documentation. But let's use that for now in this example.
So as you can see, when user login is successful, now you are not requesting data after rendering the Profile page but request data even before navigating to the page. So this ensures that profile page is only getting loaded with relevant data and there will be no lag like in your GIF.
I have an Expo app, and I'm trying to handle push notifications sent while the app is in the foreground. It works fine in Android, but iOS it's crashing the app as it's received.
I have a push notification being sent from a Rails server:
params = ({
to: token.expo_push_token,
title: user.first_name,
sound: "default",
body: msg.body,
})
puts params
puts params.class
x = Net::HTTP.post_form(URI.parse('https://exp.host/--/api/v2/push/send'), params)
puts x.body
I can see in the server it sends:
Hash
app[worker.1]: {"data":{"id":"9058abf3-7352-4181-a69d-0b5fc8a8525c","status":"ok"}}
4 TID-godk4ew98 ExpoPushWorker JID-51b823f8feeaf42c313e392e INFO: done: 2.005 sec
And if the app is closed, the push notification appears on the lock screen. If the app is open in the foreground, nothing happens.
I want to listen for notifications when the app is open, and I have this in App.js:
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import MessagesScreenRouter from './app/components/Router/MessagesScreenRouter';
import Sentry from 'sentry-expo';
import reducers from './app/reducers';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import { Notifications } from 'expo';
export default class App extends React.Component {
constructor(props) {
super(props)
this.state = {
notification: {},
}
this._handleNotification = this._handleNotification.bind(this)
}
_handleNotification = (notification) => {
console.log(notification)
this.setState({notification: notification});
};
componentWillUnmount(){
this._notificationSubscription && this._notificationSubscription.remove()
}
componentDidMount() {
this.notificationSubscription = Notifications.addListener(
(notification) => this._handleNotification(notification),
);
}
render() {
return (
<View style={{flex:1}}>
<StatusBar hidden={true} />
<View style={{height: 50, justifyContent: 'center', alignItems: 'center'}}>
<Text>Origin: {this.state.notification.origin}</Text>
<Text>Data: {JSON.stringify(this.state.notification)}</Text>
</View>
<Provider store={createStore(reducers)}>
<MessagesScreenRouter/>
</Provider>
</View>
);
}
}
I've tried many suggestions from tutorials all day, but I can not get this to work. What am I missing here?
You can't test it on simulator as expo docs states
Note: iOS and Android simulators cannot receive push notifications. To test them out you will need to use a real-life device. Additionally, when calling Permissions.askAsync on the simulator, it will resolve immediately with "undetermined" as the status, regardless of whether you choose to allow or not.
Just run exp publish and test it on expo client. Also you have to call for permission using Permissions.askAsync in the first place.
Doc's sample work like a charm, check it out: https://docs.expo.io/versions/v28.0.0/guides/push-notifications#__next
Expo has likely been updated in that aspect since then, and now it might even be using the command you have mentioned in the comment (exp start -m tunnel). As foreground notifications are still not available on iOS so far (which might have even caused your issue in the first place), this answer is rather for people looking to implement push notifications than fixing the issue above.
I have created a file downloader and previewer that shows both internal and external notifications on both OSes without running into such issues. The code is available on GitHub and an elaboration is given in this SO answer.
The most relevant code for this post is in regard to the use of local notifications from Expo while the app is in background, and showing them in foreground using toasts from the react-native-toast package. This functionality may be replaceable by Expo Notifications once this feature gets implemented.
For completeness, here is the code for the project:
App.js
import React, { Component } from 'react';
import { View, ScrollView, StyleSheet, Button, Alert, Platform, Text, TouchableWithoutFeedback } from 'react-native';
import { FileSystem, Constants, Notifications, Permissions } from 'expo';
import Toast, {DURATION} from 'react-native-easy-toast';
async function getiOSNotificationPermission() {
const { status } = await Permissions.getAsync(
Permissions.NOTIFICATIONS
);
if (status !== 'granted') {
await Permissions.askAsync(Permissions.NOTIFICATIONS);
}
}
export default class App extends Component {
constructor(props) {
super(props);
// this.toast = null;
this.listenForNotifications = this.listenForNotifications.bind(this);
// this.openFile = this.openFile.bind(this);
this.state = {
filePreviewText: ''
}
}
_handleButtonPress = () => {
let fileName = 'document.txt';
let fileUri = FileSystem.documentDirectory + fileName;
FileSystem.downloadAsync(
"https://raw.githubusercontent.com/expo/expo/master/README.md",
fileUri
).then(({ uri }) => {
console.log('Finished downloading to ', uri);
const localnotification = {
title: 'Download has finished',
body: fileName + " has been downloaded. Tap to open file.",
android: {
sound: true,
},
ios: {
sound: true,
},
data: {
fileUri: uri
},
};
localnotification.data.title = localnotification.title;
localnotification.data.body = localnotification.body;
let sendAfterFiveSeconds = Date.now();
sendAfterFiveSeconds += 3000;
const schedulingOptions = { time: sendAfterFiveSeconds };
Notifications.scheduleLocalNotificationAsync(
localnotification,
schedulingOptions
);
})
.catch(error => {
console.error(error);
Alert.alert(error);
});
};
listenForNotifications = () => {
const _this = this;
Notifications.addListener(notification => {
if (notification.origin === 'received') {
// We could also make our own design for the toast
// _this.refs.toast.show(<View><Text>hello world!</Text></View>);
const toastDOM =
<TouchableWithoutFeedback
onPress={() => {this.openFile(notification.data.fileUri)}}
style={{padding: '10', backgroundColor: 'green'}}>
<Text style={styles.toastText}>{notification.data.body}</Text>
</TouchableWithoutFeedback>;
_this.toast.show(toastDOM, DURATION.FOREVER);
} else if (notification.origin === 'selected') {
this.openFile(notification.data.fileUri);
}
// Expo.Notifications.setBadgeNumberAsync(number);
// Notifications.setBadgeNumberAsync(10);
// Notifications.presentLocalNotificationAsync(notification);
// Alert.alert(notification.title, notification.body);
});
};
componentWillMount() {
getiOSNotificationPermission();
this.listenForNotifications();
}
componentDidMount() {
// let asset = Asset.fromModule(md);
// Toast.show('Hello World');
}
openFile = (fileUri) => {
this.toast.close(40);
console.log('Opening file ' + fileUri);
FileSystem.readAsStringAsync(fileUri)
.then((fileContents) => {
// Get file contents in binary and convert to text
// let fileTextContent = parseInt(fileContents, 2);
this.setState({filePreviewText: fileContents});
});
}
render() {
return (
<View style={styles.container}>
<View style={styles.buttonsContainer}>
<Button style={styles.button}
title={"Download text file"}
onPress={this._handleButtonPress}
/>
<Button style={styles.button}
title={"Clear File Preview"}
onPress={() => {this.setState({filePreviewText: ""})}}
/>
</View>
<ScrollView style={styles.filePreview}>
<Text>{this.state.filePreviewText}</Text>
</ScrollView>
<Toast ref={ (ref) => this.toast=ref }/>
</View>
);
// <Toast
// ref={ (ref) => this.toast=ref }
// style={{backgroundColor:'green'}}
// textStyle={{color:'white'}}
// position={'bottom'}
// positionValue={100}
// opacity={0.8}
// />
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
},
buttonsContainer: {
flexDirection: 'row',
},
button: {
flex: 1
},
filePreview: {
flex: 1,
padding: 10,
},
toastText: {
color: 'white',
padding: 5,
justifyContent: 'flex-start',
},
});
package.json: Currently using forked repo, switch back when feature becomes available.
{
"name": "local-notification-with-ios",
"version": "0.0.0",
"description": "No description",
"author": null,
"private": true,
"main": "node_modules/expo/AppEntry.js",
"dependencies": {
"expo": "^32.0.0",
"react": "16.5.0",
"react-native": "https://github.com/expo/react-native/archive/sdk-32.0.0.tar.gz",
"prop-types": "^15.5.7",
"react-native-easy-toast": "git+https://github.com/SiavasFiroozbakht/react-native-easy-toast.git#6eed52f4d64c796cb49bdafcd7b3986cf5975d62"
}
}
I'm getting the following error:
Possible unhandled promise rejection (id:0: Network request failed)
Here's the promise code, I don't see what's wrong here, any ideas?
return fetch(url)
.then(function(response){
return response.json();
})
.then(function(json){
return {
city: json.name,
temperature: kelvinToF(json.main.temp),
description: _.capitalize(json.weather[0].description)
}
})
.catch(function(error) {
console.log('There has been a problem with your fetch operation: ' + error.message);
});
}
Edit:
I added a catch function and got a better error:
You passed an undefined or null state object; instead, use forceUpdate(). index.ios.js:64 undefined
Here's the index.ios.js code. The url is fine and giving me the correct json data. I can see with console log that both region.latitude and region.longitude are available in Api(region.latitude, region.longitude). But data is undefined.
I'm still not sure what's going on, why there's a problem with data and why it's undefined.
// var React = require('react-native'); --deprecated
// updated
import React from 'react';
// updated
import {
AppRegistry,
MapView,
View,
Text,
StyleSheet,
} from 'react-native';
/*
var {
AppRegistry,
MapView,
View,
Text,
StyleSheet
} = React;
*/ // -- depreciated
var Api = require('./src/api');
var Weather = React.createClass({
getInitialState: function() {
return {
pin: {
latitude: 0,
longitude: 0
},
city: '',
temperature: '',
description: ''
};
},
render: function() {
return <View style={styles.container}>
<MapView
annotations={[this.state.pin]}
onRegionChangeComplete={this.onRegionChangeComplete}
style={styles.map}>
</MapView>
<View style={styles.textWrapper}>
<Text style={styles.text}>{this.state.city}</Text>
<Text style={styles.text}>{this.state.temperature}</Text>
<Text style={styles.text}>{this.state.description}</Text>
</View>
</View>
},
onRegionChangeComplete: function(region) {
this.setState({
pin: {
longitude: region.longitude,
latitude: region.latitude
}
});
Api(region.latitude, region.longitude)
.then((data) => {
console.log(data);
this.setState(data);
});
}
});
var styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'stretch',
backgroundColor: '#F5FCFF'
},
map: {
flex: 2,
marginTop: 30
},
textWrapper: {
flex: 1,
alignItems: 'center'
},
text: {
fontSize: 30
}
});
AppRegistry.registerComponent('weather', () => Weather);
catch function in your api should either return some data which could be handled by Api call in React class or throw new error which should be caught using a catch function in your React class code. Latter approach should be something like:
return fetch(url)
.then(function(response){
return response.json();
})
.then(function(json){
return {
city: json.name,
temperature: kelvinToF(json.main.temp),
description: _.capitalize(json.weather[0].description)
}
})
.catch(function(error) {
console.log('There has been a problem with your fetch operation: ' + error.message);
// ADD THIS THROW error
throw error;
});
Then in your React Class:
Api(region.latitude, region.longitude)
.then((data) => {
console.log(data);
this.setState(data);
}).catch((error)=>{
console.log("Api call error");
alert(error.message);
});
You should add the catch() to the end of the Api call. When your code hits the catch() it doesn't return anything, so data is undefined when you try to use setState() on it. The error message actually tells you this too :)
According to this post, you should enable it in XCode.
Click on your project in the Project Navigator
Open the Info tab
Click on the down arrow left to the "App Transport Security Settings"
Right click on "App Transport Security Settings" and select Add Row
For created row set the key “Allow Arbitrary Loads“, type to boolean and value to YES.
Adding here my experience that hopefully might help somebody.
I was experiencing the same issue on Android emulator in Linux with hot reload. The code was correct as per accepted answer and the emulator could reach the internet (I needed a domain name).
Refreshing manually the app made it work. So maybe it has something to do with the hot reloading.
In My case, I am running a local Django backend in IP 127.0.0.1:8000
with Expo start.
Just make sure you have the server in public domain not hosted locally on your machine.
If it is hosted locally find the local IP address like 192.168.0.105 or something and use that
delete build folder projectfile\android\app\build and run project