Add with-formdata-image-upload

This commit is contained in:
Brent Vatne
2017-09-09 11:49:49 -07:00
parent f30661e160
commit 5e8e0d5c2b
12 changed files with 4295 additions and 0 deletions

View File

@@ -0,0 +1,18 @@
# Image Upload Example
Try it at https://expo.io/@community/image-upload-example
## How to use
### Running the app
- `cd` into the `app` directory and run `yarn` or `npm install`
- Open `app` with `exp` or XDE, try it out.
### Running the server
- By default, the app will use a server that is already deployed in order to upload the image to S3. If you want to deploy your own, follow the steps in the [backend directory](https://github.com/expo/examples/tree/master/with-formdata-image-upload/backend).
## The idea behind the example
A common requirement for apps is to be able to upload an image to a server. This example shows how you can use `ImagePicker` to snap a photo or grab it from your camera roll, then use `FormData` with `fetch` to upload it to a server. The `/backend` demsontrates a simple Node app that uploads an image to S3. The `/app` directory contains the Expo app that sends the image to that backend.

View File

@@ -0,0 +1,8 @@
{
"presets": ["babel-preset-expo"],
"env": {
"development": {
"plugins": ["transform-react-jsx-source"]
}
}
}

View File

@@ -0,0 +1,3 @@
node_modules/**/*
.expo/*
npm-debug.*

View File

@@ -0,0 +1,192 @@
import React from 'react';
import {
ActivityIndicator,
Button,
Clipboard,
Image,
Share,
StatusBar,
StyleSheet,
Text,
TouchableOpacity,
View,
} from 'react-native';
import Exponent, { Constants, ImagePicker, registerRootComponent } from 'expo';
export default class App extends React.Component {
state = {
image: null,
uploading: false,
};
render() {
let { image } = this.state;
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text
style={{
fontSize: 20,
marginBottom: 20,
textAlign: 'center',
marginHorizontal: 15,
}}>
Example: Upload ImagePicker result
</Text>
<Button
onPress={this._pickImage}
title="Pick an image from camera roll"
/>
<Button onPress={this._takePhoto} title="Take a photo" />
{this._maybeRenderImage()}
{this._maybeRenderUploadingOverlay()}
<StatusBar barStyle="default" />
</View>
);
}
_maybeRenderUploadingOverlay = () => {
if (this.state.uploading) {
return (
<View
style={[
StyleSheet.absoluteFill,
{
backgroundColor: 'rgba(0,0,0,0.4)',
alignItems: 'center',
justifyContent: 'center',
},
]}>
<ActivityIndicator color="#fff" animating size="large" />
</View>
);
}
};
_maybeRenderImage = () => {
let { image } = this.state;
if (!image) {
return;
}
return (
<View
style={{
marginTop: 30,
width: 250,
borderRadius: 3,
elevation: 2,
shadowColor: 'rgba(0,0,0,1)',
shadowOpacity: 0.2,
shadowOffset: { width: 4, height: 4 },
shadowRadius: 5,
}}>
<View
style={{
borderTopRightRadius: 3,
borderTopLeftRadius: 3,
overflow: 'hidden',
}}>
<Image source={{ uri: image }} style={{ width: 250, height: 250 }} />
</View>
<Text
onPress={this._copyToClipboard}
onLongPress={this._share}
style={{ paddingVertical: 10, paddingHorizontal: 10 }}>
{image}
</Text>
</View>
);
};
_share = () => {
Share.share({
message: this.state.image,
title: 'Check out this photo',
url: this.state.image,
});
};
_copyToClipboard = () => {
Clipboard.setString(this.state.image);
alert('Copied image URL to clipboard');
};
_takePhoto = async () => {
let pickerResult = await ImagePicker.launchCameraAsync({
allowsEditing: true,
aspect: [4, 3],
});
this._handleImagePicked(pickerResult);
};
_pickImage = async () => {
let pickerResult = await ImagePicker.launchImageLibraryAsync({
allowsEditing: true,
aspect: [4, 3],
});
this._handleImagePicked(pickerResult);
};
_handleImagePicked = async pickerResult => {
let uploadResponse, uploadResult;
try {
this.setState({ uploading: true });
if (!pickerResult.cancelled) {
uploadResponse = await uploadImageAsync(pickerResult.uri);
uploadResult = await uploadResponse.json();
this.setState({ image: uploadResult.location });
}
} catch (e) {
console.log({ uploadResponse });
console.log({ uploadResult });
console.log({ e });
alert('Upload failed, sorry :(');
} finally {
this.setState({ uploading: false });
}
};
}
async function uploadImageAsync(uri) {
let apiUrl = 'https://file-upload-example-backend-dkhqoilqqn.now.sh/upload';
// Note:
// Uncomment this if you want to experiment with local server
//
// if (Constants.isDevice) {
// apiUrl = `https://your-ngrok-subdomain.ngrok.io/upload`;
// } else {
// apiUrl = `http://localhost:3000/upload`
// }
let uriParts = uri.split('.');
let fileType = uri[uri.length - 1];
let formData = new FormData();
formData.append('photo', {
uri,
name: `photo.${fileType}`,
type: `image/${fileType}`,
});
let options = {
method: 'POST',
body: formData,
headers: {
Accept: 'application/json',
'Content-Type': 'multipart/form-data',
},
};
return fetch(apiUrl, options);
}

View File

@@ -0,0 +1,17 @@
{
"expo": {
"name": "image-upload-example",
"description": "Demonstration of how to upload images from the ImagePicker, using a Node backend to upload to S3. https://github.com/expo/examples/tree/master/with-formdata-image-upload",
"slug": "image-upload-example",
"sdkVersion": "20.0.0",
"version": "1.0.0",
"orientation": "portrait",
"primaryColor": "#cccccc",
"packagerOpts": {
"assetExts": ["ttf", "mp4"]
},
"ios": {
"supportsTablet": true
}
}
}

View File

@@ -0,0 +1,13 @@
{
"name": "image-upload-example-frontend",
"version": "0.0.0",
"description": "Hello Exponent!",
"author": "exponent.team@gmail.com",
"private": true,
"main": "node_modules/expo/AppEntry.js",
"dependencies": {
"expo": "^20.0.0",
"react": "16.0.0-alpha.12",
"react-native": "https://github.com/expo/react-native/archive/sdk-20.0.0.tar.gz"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,3 @@
AWS_ACCESS_KEY_ID=<YOUR_ACCESS_KEY_ID>
AWS_SECRET_ACCESS_KEY=<YOUR_SECRET_ACCESS_KEY>
AWS_BUCKET=<YOUR_BUCKET_NAME>

View File

@@ -0,0 +1,2 @@
node_modules/**/*
.env

View File

@@ -0,0 +1,31 @@
## Run this locally
1. Rename `.env.example` to `.env`
2. Fill in your AWS credentials in `.env`
3. `npm start`
4. Update the `apiUrl` in `frontend` to point to the server.
## Deploy this with [now.sh](https://zeit.co/now/):
1. Add your AWS credentials to now
```
now secret add aws-access-key-id your-access-key-id-here
now secret add aws-secret-access-key your-secret-access-key
now secret add aws-bucket your-bucket-name-here
```
2. Deploy
```
now . \
-e AWS_ACCESS_KEY_ID=@aws-access-key-id \
-e AWS_SECRET_ACCESS_KEY=@aws-secret-access-key \
-e AWS_BUCKET=@aws-bucket \
-e NODE_ENV=production
```
3. Update the `apiUrl` in `frontend` to point to your deploy.

View File

@@ -0,0 +1,42 @@
// Use local .env file for env vars when not deployed
if (process.env.NODE_ENV !== 'production') {
require('dotenv').config();
}
const aws = require('aws-sdk')
const multer = require('multer')
const multerS3 = require('multer-s3')
const s3 = new aws.S3({
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
region: "us-east-1",
});
// Initialize multers3 with our s3 config and other options
const upload = multer({
storage: multerS3({
s3,
bucket: process.env.AWS_BUCKET,
acl: 'public-read',
metadata(req, file, cb) {
cb(null, {fieldName: file.fieldname});
},
key(req, file, cb) {
cb(null, Date.now().toString() + '.png');
}
})
})
// Expose the /upload endpoint
const app = require('express')();
const http = require('http').Server(app);
app.post('/upload', upload.single('photo'), (req, res, next) => {
res.json(req.file)
})
let port = process.env.PORT || 3000;
http.listen(port, () => {
console.log(`Listening on port ${port}`);
});

View File

@@ -0,0 +1,18 @@
{
"name": "file-upload-example-backend",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "node index.js"
},
"author": "",
"license": "MIT",
"dependencies": {
"aws-sdk": "^2.7.13",
"dotenv": "^2.0.0",
"express": "^4.14.0",
"multer": "^1.2.0",
"multer-s3": "^2.5.0"
}
}