diff --git a/.gitignore b/.gitignore index d07b789..48bf865 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ xcuserdata *.xccheckout *.moved-aside *.xcuserstate +*.vscode \ No newline at end of file diff --git a/README.md b/README.md index 15b1653..6824cb4 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,16 @@ # react-native-uploader A React Native module for uploading files and camera roll assets. Supports progress notification. +# Do you absolutely need this? +You can use file upload with `fetch` and if you want progress bar, you can use xhr. Read my post [How to upload photo/file in react-native](https://github.com/g6ling/React-Native-Tips/tree/master/How_to_upload_photo%2Cfile_in%20react-native). Even after read my post, but you are not enough, read the following. + ## Install +### Use rnpm +1. `npm install react-native-uploader --save` +2. `rnpm link react-native-uploader` + +If you don't want use rnpm, do this ### iOS 1. `npm install react-native-uploader --save` 2. In XCode, in the project navigator, right click `Libraries` ➜ `Add Files to [your project's name]` @@ -10,6 +18,33 @@ A React Native module for uploading files and camera roll assets. Supports progr 4. In XCode, in the project navigator, select your project. Add `libRNUploader.a` to your project's `Build Phases` ➜ `Link Binary With Libraries` 5. Run your project (`Cmd+R`) +### Android +1. Add to your settings.gradle: +``` +include ':RNFileTransfer', ':app' +project(':RNFileTransfer').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-file-transfer-android/android') +``` + +2. Add to your android/build.gradle: +``` +dependencies { + ... + compile project(':RNFileTransfer') +} +``` + +3. Add to MainActivity.java +``` +import com.burlap.filetransfer.FileTransferPackage; +... +mReactInstanceManager = ReactInstanceManager.builder() + .setApplication(getApplication()) + .setBundleAssetName("index.android.bundle") + .setJSMainModuleName("index.android") + .addPackage(new MainReactPackage()) + .addPackage(new FileTransferPackage()) +``` + ## Example See ./examples/UploadFromCameraRoll @@ -18,10 +53,10 @@ See ./examples/UploadFromCameraRoll ## Usage ```javascript -var RNUploader = require('NativeModules').RNUploader; +var RNUploader = require('react-native-uploader'); var { - StyleSheet, + StyleSheet, Component, View, DeviceEventEmitter, @@ -35,7 +70,7 @@ componentDidMount(){ let bytesWritten = data.totalBytesWritten; let bytesTotal = data.totalBytesExpectedToWrite; let progress = data.progress; - + console.log( "upload progress: " + progress + "%"); }); } @@ -53,17 +88,17 @@ doUpload(){ { name: 'file[]', filename: 'image2.gif', - filepath: "data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7", + filepath: "data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7", // base64 only support ios filetype: 'image/gif', }, ]; let opts = { url: 'http://my.server/api/upload', - files: files, - method: 'POST', // optional: POST or PUT - headers: { 'Accept': 'application/json' }, // optional - params: { 'user_id': 1 }, // optional + files: files, + method: 'POST', // optional: POST or PUT, only support ios, android always have POST + headers: { 'Accept': 'application/json' }, // optional, only support ios, android always have { 'Accept': 'application/json' } + params: { 'user_id': 1 }, // optional, Android support this only string. If you want this in Android use params: { 'user_id': '1' } }; RNUploader.upload( opts, (err, response) => { @@ -71,12 +106,14 @@ doUpload(){ console.log(err); return; } - + let status = response.status; let responseString = response.data; let json = JSON.parse( responseString ); console.log('upload complete with status ' + status); + + // android's response is response.body.string. }); } @@ -90,9 +127,10 @@ doUpload(){ ||type|required|description|example| |---|---|---|---|---| |`url`|string|required|URL to upload to|`http://my.server/api/upload`| -|`method`|string|optional|HTTP method, values: [PUT,POST], default: POST|`POST`| -|`headers`|object|optional|HTTP headers|`{ 'Accept': 'application/json' }`| -|`params`|object|optional|Query parameters|`{ 'user_id': 1 }`| +|`method(only iOS)`|string|optional|HTTP method, values: [PUT,POST], default: POST|`POST`| +|`headers(only iOS)`|object|optional|HTTP headers|`{ 'Accept': 'application/json' }`| +|`params(iOS)`|object|optional|Query parameters|`{ 'user_id': 1 }`| +|`params(Android)`|object|optional|Query parameters|`{ 'user_id': '1' }`
only support string value. You can't use int, boolean, etc..| |`files`|array|required|Array of file objects to upload. See below.| `[{ name: 'file', filename: 'image1.png', filepath: 'assets-library://...', filetype: 'image/png' } ]` | `callback` is a method with two parameters: @@ -100,7 +138,8 @@ doUpload(){ ||type|description|example| |---|---|---|---| |error|string|String detailing the error|`A server with the specified hostname could not be found.`| -|response|object{status:Number, data:String}|Object returned with a status code and data.|`{ status: 200, data: '{ success: true }' }`| +|response(iOS)|object{status:Number, data:String}|Object returned with a status code and data.|`{ status: 200, data: '{ success: true }' }`| +|response(Android)|String|String returned with responseBody.|`success: true`| #### `files` @@ -110,10 +149,10 @@ doUpload(){ |---|---|---|---|---| |name|string|optional|Form parameter key for the specified file. If missing, will use `filename`.|`file[]`| |filename|string|required|filename|`image1.png`| -|filepath|string|required|File URI
Supports `assets-library:`, `data:` and `file:` URIs and file paths.|`assets-library://...`
`data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOV...`
`file:/tmp/image1.png`
`/tmp/image1.png`| +|filepath|string|required|File URI
Supports `assets-library:`, `data:` and `file:` URIs and file paths.|`assets-library://...(iOS)`
`content://...(Android)`
`data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOV...(only iOS)`
`file:/tmp/image1.png`
`/tmp/image1.png`| |filetype|string|optional|MIME type of file. If missing, will infer based on the extension in `filepath`.|`image/png`| -### Progress +### Progress (only support iOS) To monitor upload progress simply subscribe to the `RNUploaderProgress` event using DeviceEventEmitter. ``` @@ -121,12 +160,12 @@ DeviceEventEmitter.addListener('RNUploaderProgress', (data)=>{ let bytesWritten = data.totalBytesWritten; let bytesTotal = data.totalBytesExpectedToWrite; let progress = data.progress; - + console.log( "upload progress: " + progress + "%"); }); ``` -### Cancel +### Cancel (only support iOS) To cancel an upload in progress: ``` RNUploader.cancel() @@ -143,8 +182,9 @@ Inspired by similiar projects: * progress reporting * packaged as a static library * support for multiple files at a time -* support for files from the assets library, base64 `data:` or `file:` paths +* support for files from the assets library, base64 `data:` or `file:` paths * no external dependencies (ie: AFNetworking) +* support Android ## License diff --git a/RNUploader/RNUploader.ios.js b/RNUploader/RNUploader.ios.js deleted file mode 100644 index 50c8be0..0000000 --- a/RNUploader/RNUploader.ios.js +++ /dev/null @@ -1,19 +0,0 @@ -/** - * @providesModule RNUploader - * @flow - */ -'use strict'; - -var NativeRNUploader = require('NativeModules').RNUploader; - -/** - * High-level docs for the RNUploader iOS API can be written here. - */ - -var RNUploader = { - test: function() { - NativeRNUploader.test(); - } -}; - -module.exports = RNUploader; \ No newline at end of file diff --git a/android/build.gradle b/android/build.gradle new file mode 100644 index 0000000..76f22b0 --- /dev/null +++ b/android/build.gradle @@ -0,0 +1,36 @@ +buildscript { + repositories { + jcenter() + maven { url "https://jitpack.io" } + } + + dependencies { + classpath 'com.android.tools.build:gradle:1.1.3' + } +} + +apply plugin: 'com.android.library' + +android { + compileSdkVersion 23 + buildToolsVersion "23.0.1" + + defaultConfig { + minSdkVersion 16 + targetSdkVersion 23 + versionCode 1 + versionName "1.0" + } +} + +repositories { + maven { url "https://jitpack.io" } +} + +dependencies { + compile 'com.android.support:appcompat-v7:23.+' + compile 'com.facebook.react:react-native:0.16.+' + compile 'com.squareup.okhttp3:okhttp:3.3.1' + compile 'com.zhy:okhttputils:2.6.2' + compile fileTree(dir: 'libs', include: ['*.jar']) +} diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml new file mode 100644 index 0000000..4add1ea --- /dev/null +++ b/android/src/main/AndroidManifest.xml @@ -0,0 +1,5 @@ + + + diff --git a/android/src/main/java/com/burlap/filetransfer/FileTransferModule.java b/android/src/main/java/com/burlap/filetransfer/FileTransferModule.java new file mode 100644 index 0000000..e0eb5d4 --- /dev/null +++ b/android/src/main/java/com/burlap/filetransfer/FileTransferModule.java @@ -0,0 +1,171 @@ +package com.burlap.filetransfer; + +import android.app.DownloadManager; +import android.content.Context; +import android.database.Cursor; +import android.provider.MediaStore; +import android.util.Log; +import android.net.Uri; + +import com.facebook.react.bridge.NativeModule; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; +import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.bridge.Callback; +import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.ReadableMapKeySetIterator; +import com.facebook.react.bridge.ReadableType; +import com.facebook.react.bridge.WritableMap; +import com.facebook.react.bridge.Arguments; +import com.facebook.react.modules.core.DeviceEventManagerModule; + +import org.json.*; + +import java.util.Map; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.FileNotFoundException; +import java.util.HashMap; + +import okhttp3.MediaType; +import okhttp3.MultipartBody; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + +import com.zhy.http.okhttp.request.CountingRequestBody; + +public class FileTransferModule extends ReactContextBaseJavaModule { + + private final OkHttpClient client = new OkHttpClient(); + private String TAG = "ImageUploadAndroid"; + ReactApplicationContext reactContext = null; + + public FileTransferModule(ReactApplicationContext reactContext) { + super(reactContext); + this.reactContext = reactContext; + } + + @Override + public String getName() { + // match up with the IOS name + return "FileTransfer"; + } + + @ReactMethod + public void upload(ReadableMap options, Callback complete) { + + final Callback completeCallback = complete; + + try { + MultipartBody.Builder mRequestBody = new MultipartBody.Builder() + .setType(MultipartBody.FORM); + + ReadableArray files = options.getArray("files"); + String url = options.getString("url"); + + if(options.hasKey("params")){ + ReadableMap data = options.getMap("params"); + ReadableMapKeySetIterator iterator = data.keySetIterator(); + + while(iterator.hasNextKey()){ + String key = iterator.nextKey(); + if(ReadableType.String.equals(data.getType(key))) { + mRequestBody.addFormDataPart(key, data.getString(key)); + } + } + } + + + + + if(files.size() != 0){ + for(int fileIndex=0 ; fileIndex createNativeModules(ReactApplicationContext reactContext) { + return Arrays.asList( + new FileTransferModule(reactContext) + ); + } + + + @Override + public List> createJSModules() { + return Collections.emptyList(); + } + + @Override + public List createViewManagers(ReactApplicationContext reactContext) { + return Arrays.asList(); + } +} diff --git a/index.android.js b/index.android.js new file mode 100644 index 0000000..a279f82 --- /dev/null +++ b/index.android.js @@ -0,0 +1,15 @@ +'use strict'; + +var { NativeModules } = require('react-native'); +var FileTransfer = NativeModules.FileTransfer + +class RNUploader { + constructor() { + } + + static upload(opts, callback) { + FileTransfer.upload(opts, callback); + } +} + +module.exports = RNUploader; \ No newline at end of file diff --git a/index.ios.js b/index.ios.js new file mode 100644 index 0000000..1a33044 --- /dev/null +++ b/index.ios.js @@ -0,0 +1,28 @@ +/** + * @providesModule RNUploader + * @flow + */ +'use strict'; +var { NativeModules } = require('react-native'); +var NativeRNUploader = NativeModules.RNUploader; + +/** + * High-level docs for the RNUploader iOS API can be written here. + */ + +class RNUploader { + constructor() { + } + + static upload(opts, callback) { + NativeRNUploader.upload(opts, callback); + } + static cancel(){ + NativeRNUploader.cancel() + } + static test() { + NativeRNUploader.test() + } +} + +module.exports = RNUploader; \ No newline at end of file diff --git a/RNUploader/RNUploader.m b/ios/RNUploader.m similarity index 100% rename from RNUploader/RNUploader.m rename to ios/RNUploader.m diff --git a/RNUploader/RNUploader.xcodeproj/project.pbxproj b/ios/RNUploader.xcodeproj/project.pbxproj similarity index 100% rename from RNUploader/RNUploader.xcodeproj/project.pbxproj rename to ios/RNUploader.xcodeproj/project.pbxproj diff --git a/RNUploader/RNUploader.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/ios/RNUploader.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 100% rename from RNUploader/RNUploader.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to ios/RNUploader.xcodeproj/project.xcworkspace/contents.xcworkspacedata diff --git a/package.json b/package.json index 7bf5627..b654ff7 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "type": "git", "url": "git+https://github.com/aroth/react-native-uploader.git" }, - "version": "0.0.9", + "version": "0.0.10", "description": "A React Native module to upload files and camera roll assets. Supports progress notification.", "author": { "name": "Adam Roth", @@ -31,5 +31,6 @@ "name": "aroth", "email": "adamjroth@gmail.com" } - ] + ], + "main": "index" }