react-native local filesystem storage for images - react-native

As a learning exercise, I'm writing a react-native-cli based photo application which should work in a offline mode. What I mean is, application provides a way to either take a picture using camera or select a photo from a built-in gallery and stores them in local filesystem whose directory path is stored in a realm database. Following is my stack,
System: Ubuntu Linux,
react-native-cli: 2.0.1
react-native: 0.61.4,
realm#3.4.2,
react-native-image-picker#1.1.0
With react-native-image-picker, I can either pick a photo or take a photo whose details are stored in the response object from image-picker as,
response.data (image data) and response.uri
ImagePicker.showImagePicker(options, (response) => {
if (response.didCancel) {
alert('User cancelled image picker');
} else if (response.error) {
alert('ImagePicker Error: ', response.error);
} else {
const source = { uri: response.uri };
const sourceData = response.data;
this.setState({
imageSourceData: source,
});
}
});
In Main.js I've a simple view,
import React, { Component } from 'react';
function Item({ item }) {
return (
<View style={{flexDirection: 'row'}}>
<Text>{item.picureName}</Text>
</View>
<Image source={uri: ....????} /> <------------- How this would work?
)
}
export default class Main extends Component {
state = {
size: 0,
pictureName: null,
picureLocation: null,
picureDate: new Date(),
imageSourceData: '',
picures: []
}
componentDidMount() {
Realm.open(databaseOptions)
.then(realm => {
const res = realm.objects(PICTURE_SCHEMA);
this.setState({
pictures: res
})
});
}
render() {
return(
<View>
<Image source={uri: 'data:image/jpeg;base64,' + this.state.imageSourceData}
style:{{width: 50, height:50}}/>
<FlatList
data={this.state.pictures}
renderItem={({ item }) => <Item item={item} />}
keyExtractor={item => item.pictureID}
>
</View>
)
}
}
I need to do following, once I get the image from the ImagePicker,
1) store this data in a file on the device and get a file location.
2) Store the location with other meta data in a realm object
saveButton() {
// Store the imageSourceData into a file on a device,
// Get the fileLocation
// update state.picureLocation property
this.addOnePicture()
}
addOnePicture() {
var obj = new Object();
obj = {
PicureID: this.state.size + 1;
PictureName: this.state.pictureName,
PictureDate: this.state.pictureDate,
PicureLocation: this.state.picureLocation
};
Realm.open(databaseOptions)
.then(realm => {
realm.write(() => {
realm.create(PICTURE_SCHEMA, obj);
this.setState({ size: realm.objects(PICTURE_SCHEMA).length });
});
})
}
3) List of realm objects can be read to display the data in a flatlist in "componentDidMount() hook"
It's a snippet of a code but I hope it's clear. I would really appreciate any help/suggestions with possible code block to do following,
1) How do you store the data (imageSourceData) to a local file, basically fill in saveButton() (I was thinking of using react-native-fs package. Is this a good idea?)
2) How do I display this image in a view? Do I need to read the contents as it renders in a FlatList? What does Image syntax looks like (check Item component code).

react-native-fs worked perfectly.

Related

React Native add data to Async Storage

I'm developing application about travelling, so I have a map with places to travel and I want to implement adding to favourites functionality. When the user clicks on the marker on the map I show him modal window with all the information about the place and there I have icon-button "add-to-favorites". When user clicked on this "add-to-favourites" icon-button I want to save the place to the AsyncStorage, rerender icon to "remove-from-favourites" then if the place in favouries and user clicks on "remove-from-favorites" then remove place from AsyncStorage and rerender icon the add-to-favorites. I don't really understand how to do it in a right way in functional component. Give me an example please.
What I tried:
My ModalWindow code:
const [favourite, setFavourite] = useState(false);
const onHeartPress = async () => {
setFavourite(prev => !prev);
await storage.save({
key: 'markers',
data: {
marker: marker
},
expires: null
})
<TouchableOpacity onPress={() => onHeartPress()}>
<View style={{flexDirection: "row", alignItems: "center"}}>
{favourite ? <Ionicons size={height/20} name={'heart-dislike'} /> : <Ionicons size={height/20} name={'heart'} />}
</View>
</TouchableOpacity>
It's not difficult to understand how to add to the storage and then load from the storage but how to render the icon correctly like heart/dislike heart because now with this code when I press on button icon in one place in the other places it also changes, because favourite becomes true so I want to understand how in the correct way change icon only for place where user clicked on icon.
You can follow this code to achieve this functionality:
const STORAGE_KEY = '#icon_name'
// Store data
const saveData = async () => {
try {
await AsyncStorage.setItem(STORAGE_KEY, age)
alert('Data successfully saved')
} catch (e) {
alert('Failed to save the data to the storage')
}
}
// fetch data
const readData = async () => {
try {
const userAge = await AsyncStorage.getItem(STORAGE_KEY)
if (userAge !== null) {
setAge(userAge)
}
} catch (e) {
alert('Failed to fetch the data from storage')
}
}
//remove data
try {
await AsyncStorage.removeItem(STORAGE_KEY);
console.log('Data removed')
}
catch(exception) {
console.log(exception)
}

App stop working when Image Picker is opened in React Native

I am developing a React Native application using React Native. I am using react native image picker library, https://www.npmjs.com/package/react-native-imagepicker to pick up the images from the Gallery. But when I opened the image picker, my app stopped working and exited.
This is my code
import React from "react";
import { CameraRoll, View, Text, Button, Alert, Image } from "react-native";
import ImagePicker from "react-native-image-picker";
// More info on all the options is below in the API Reference... just some common use cases shown here
const options = {
title: "Select Avatar",
customButtons: [{ name: "fb", title: "Choose Photo from Facebook" }],
storageOptions: {
skipBackup: true,
path: "images"
}
};
class Gallery extends React.Component {
constructor(props) {
super(props);
this.state = {
url:"https://www.designevo.com/res/templates/thumb_small/terrible-black-bat-icon.png",
avatarSource: null
};
}
saveToCameraRoll = () => {
let { url } = this.state;
};
_handlePickImageButton = () => {
ImagePicker.showImagePicker(options, response => {
console.log("Response = ", response);
if (response.didCancel) {
Alert.alert("User cancelled image picker")
} else if (response.error) {
//console.log("ImagePicker Error: ", response.error);
Alert.alert("ImagePicker Error:");
} else if (response.customButton) {
//console.log("User tapped custom button: ", response.customButton);
Alert.alert("Custom button");
} else {
const source = { uri: response.uri };
// You can also display the image using data:
// const source = { uri: 'data:image/jpeg;base64,' + response.data };
this.setState({
avatarSource: source
});
}
});
};
render() {
return (
<View>
<Button
onPress={() => {
this._handlePickImageButton();
}}
title="Pick a image"
>
Pick image
</Button>
<Image source={this.state.avatarSource} />
</View>
);
}
}
export default Gallery;
What is wrong with my code? Also, I did not get any error info in the console as in the screenshot attached below.
I tried, opening in this way too
ImagePicker.launchImageLibrary(options, (response) => {
//nothing implemented yet
});
It just stopped working.
I added the following permission in the plist as well:
I tried this too
const options = {
noData: true
};
ImagePicker.launchImageLibrary(options, (response) => {
});
I found the issue. The problem was in the plist. When I was adding the permissions, I just copy-pasted from a post. Might be something was wrong with it. When I typed in the permissions in the XCode, I saw the suggestion box, so I just clicked on the suggestion box and added the description for each permission as below.
As you can see in the above screenshot, the String value in the Type column is grayed out and cannot be changed. In the screenshot attached in the question, those values can be changed. That is the difference.

Is it possible to encrypt the images of android device using react native?

I am developing an app where I take photos using react-native-image-picker and upload those to the AWS server. Once after uploading those I want to encrypt those taken images in the Android device i.e in saved folder. Is it possible to do? If yes, How can I do that using react-native?
My code is,
I am able to capture images and uploading to the AWS s3.
import React, { Component } from "react";
import {
Platform,
StyleSheet,
Alert,
Text,
TouchableOpacity,
View,
Picker,
Animated,
Easing,
Image
} from "react-native";
import ImagePicker from "react-native-image-picker";
import { RNS3 } from "react-native-aws3";
export default class SecondScreen extends Component<Props> {
constructor(props) {
super(props);
this.state = {
file: "",
saveImages: []
};
}
takePic() {
ImagePicker.launchCamera({}, responce => {
const file = {
uri: responce.uri,
name: responce.fileName,
method: "POST",
path: responce.path,
type: responce.type,
notification: {
enabled: true
}
};
this.state.saveImages.push(file);
});
}
_upload = saveImages => {
const config = {
keyPrefix: "uploads/",
bucket: "s3merahkee",
region: "us-east-2",
accessKey: "***",
secretKey: "***",
successActionStatus: 201
};
this.state.saveImages.map(image => {
RNS3.put(image, config).then(responce => {
console.log(saveImages);
});
});
};
render() {
return (
<View style={styles.container}>
<View style={styles.Camera}>
<TouchableOpacity onPress={this.takePic.bind(this)}>
<Text>Take Picture</Text>
</TouchableOpacity>
</View>
<View style={styles.Send}>
<TouchableOpacity onPress={() => this._upload()}>
<Text>Send</Text>
</TouchableOpacity>
</View>
</View>
);
}
}
I am using two methods here, one the for capturing images and once the user clicks on send it to upload the file to AWS s3.
I hope I can encrypt the image If possible tell me how can I implement it. Or if not possible suggest me the other way that I can do it. (EX: deleting, etc..)
I have done this using converting image to base64. Using react-native-fs library, I am able to achieve this. Here, Once I captured the images I convert them to a base64 string and delete the real image from the folder.
My code is,
takePic = () => {
// create a path you want to write to
var pictureFolder = RNFetchBlob.fs.dirs.SDCardDir+'/Schoolapp';
ImagePicker.launchCamera(options,(responce)=>{
const file ={
uri : responce.uri,
name : responce.fileName,
method: 'POST',
path : responce.path,
type : responce.type,
notification: {
enabled: true
}
}
//convert image into base64
const base64 = RNFS.writeFile(responce.uri, responce.data);
return base64;
//delete the original image
RNFS.unlink(responce.path)
.then(() => {
console.log('deleted');
RNFS.scanFile(responce.path)
.then(() => {
console.log('scanned');
})
.catch(err => {
console.log(err);
});
})
.catch((err) => {
console.log(err);
})
});
}

React-Native Camera error - No suitable URL request handler found for assets-library

I am creating react-native app which allows me to take a picture using camera and upload to AWS S3.
I am able to click the picture and save image to my iPhone camera roll. But, when I try to upload the image, I get error No suitable URL request handler found for assets-library://asset as shown below:
Here is the code snippet:
import Camera from 'react-native-camera';
import {RNS3} from "react-native-aws3";
class NCamera extends React.Component {
takePicture() {
this.camera.capture()
.then((data) => {
const file = { uri: data.path, name: 'image.png', type: 'image/png',}
const options = {
keyPrefix: "images/",
bucket: "my-bucket-name",
region: "us-east-1",
accessKey: "key",
secretKey: "secret-key",
successActionStatus: 201
}
RNS3.put(file, options)
.then(response => {
if (response.status != 201 )
console.log('Error uploading file to S3');
else
console.log(response.body);
})
.catch (error => console.log(`Error uploading: ${error}`));
})
.catch(err => console.log(err));
}
render() {
return (
<Camera
ref={(cam) => {
this.camera = cam;
}}
style={styles.preview}
aspect={Camera.constants.Aspect.fill}>
<Text style={styles.capture} onPress={this.takePicture.bind(this)}>[CAPTURE]</Text>
</Camera>
);
}
}
Solution
I added libRCTCameraRoll.a which resolved the issue.
Here are the steps:
1. Open RCTCameraRoll.xcodeproj in xcode. The file can be found under node_modules/react-native/Libraries/CameraRoll
2. Under Build Phases, add libRCTCameraRoll.a(screenshot below).
If this is on IOS, I think you need to link libRCTCamera.a in XCode so that the file url can be resolved properly. See this medium article for more details on that.

Expo.FileSystem.downloadAsync do not show download notification

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.