Skip to content

Commit e8e65e9

Browse files
authored
Merge pull request #311 from Elyx0/@vonovak/allow-copying-file
@vonovak/allow copying file
2 parents 7d53b49 + 2cee0e3 commit e8e65e9

File tree

9 files changed

+135
-97
lines changed

9 files changed

+135
-97
lines changed

README.md

Lines changed: 72 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ A React Native wrapper for:
1717
npm i --save react-native-document-picker
1818
```
1919

20-
2120
You need to enable iCloud Documents to access iCloud
2221

2322
<img src="https://camo.githubusercontent.com/ac300ca7e3bbab573a76c151469a89efd8b31e72/68747470733a2f2f33313365353938373731386233343661616638332d66356538323532373066323961383466373838313432333431303338343334322e73736c2e6366312e7261636b63646e2e636f6d2f313431313932303637342d656e61626c652d69636c6f75642d64726976652e706e67" width="600">
@@ -38,39 +37,52 @@ Use `pick` or `pickMultiple` to open a document picker for the user to select fi
3837

3938
### Options
4039

40+
All of the options are optional
41+
4142
##### `type`:`string|Array<string>`:
4243

4344
The type or types of documents to allow selection of. May be an array of types as single type string.
44-
- On Android these are MIME types such as `text/plain` or partial MIME types such as `image/*`. See [common MIME types](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types).
45-
- On iOS these must be Apple "[Uniform Type Identifiers](https://developer.apple.com/library/content/documentation/Miscellaneous/Reference/UTIRef/Articles/System-DeclaredUniformTypeIdentifiers.html)"
46-
- If `type` is omitted it will be treated as `*/*` or `public.content`.
47-
- Multiple type strings are not supported on Android before KitKat (API level 19), Jellybean will fall back to `*/*` if you provide an array with more than one value.
4845

49-
##### `readContent`:
46+
- On Android these are MIME types such as `text/plain` or partial MIME types such as `image/*`. See [common MIME types](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types).
47+
- On iOS these must be Apple "[Uniform Type Identifiers](https://developer.apple.com/library/content/documentation/Miscellaneous/Reference/UTIRef/Articles/System-DeclaredUniformTypeIdentifiers.html)"
48+
- If `type` is omitted it will be treated as `*/*` or `public.content`.
49+
- Multiple type strings are not supported on Android before KitKat (API level 19), Jellybean will fall back to `*/*` if you provide an array with more than one value.
50+
51+
##### [iOS only] `copyTo`:`"cachesDirectory" | "documentDirectory"`:
52+
53+
If specified, the picked file is copied to `NSCachesDirectory` / `NSDocumentDirectory` directory. The uri of the copy will be available in result's `fileCopyUri`. If copying the file fails (eg. due to lack of space), `fileCopyUri` will be the same as `uri`, and more details about the error will be available in `copyError` field in the result.
54+
55+
This should help if you need to work with the file(s) later on, because by default, [the picked documents are temporary files. They remain available only until your application terminates](https://developer.apple.com/documentation/uikit/uidocumentpickerdelegate/2902364-documentpicker). This may impact performance for large files, so keep this in mind if you expect users to pick particularly large files and your app does not need immediate read access.
56+
57+
##### [UWP only] `readContent`:`boolean`
5058

51-
Boolean which defaults to `false`. If `readContent` is set to true the content of the picked file/files will be read and supplied in the result object.
59+
Defaults to `false`. If `readContent` is set to true the content of the picked file/files will be read and supplied in the result object.
5260

53-
- Be aware that this can introduce a huge performance hit in case of big files. (The files are read completely and into the memory and encoded to base64 afterwards to add them to the result object)
54-
- However reading the file directly from within the Thread which managed the picker can be necessary on Windows: Windows Apps can only read the Downloads folder and their own app folder by default and If a file is outside of these locations it cannot be acessed directly. However if the user picks the file through a file picker permissions to that file are granted implicitly.
61+
- Be aware that this can introduce a huge performance hit in case of big files. (The files are read completely and into the memory and encoded to base64 afterwards to add them to the result object)
62+
- However reading the file directly from within the Thread which managed the picker can be necessary on Windows: Windows Apps can only read the Downloads folder and their own app folder by default and If a file is outside of these locations it cannot be acessed directly. However if the user picks the file through a file picker permissions to that file are granted implicitly.
5563

56-
```
57-
In addition to the default locations, an app can access additional files and folders by declaring capabilities in the app manifest (see App capability declarations), or by calling a file picker to let the user pick files and folders for the app to access (see Open files and folders with a picker).
58-
```
64+
```
65+
In addition to the default locations, an app can access additional files and folders by declaring capabilities in the app manifest (see App capability declarations), or by calling a file picker to let the user pick files and folders for the app to access (see Open files and folders with a picker).
66+
```
5967

60-
https://docs.microsoft.com/en-us/windows/uwp/files/file-access-permissions
68+
https://docs.microsoft.com/en-us/windows/uwp/files/file-access-permissions
6169

62-
Unfortunately that permission is not granted to the whole app, but only the Thread which handled the filepicker. Therefore it can be useful to read the file directly.
70+
Unfortunately that permission is not granted to the whole app, but only the Thread which handled the filepicker. Therefore it can be useful to read the file directly.
6371

64-
- You can use `react-native-fs` on Android and IOS to read the picked file.
72+
- You can use `react-native-fs` on Android and IOS to read the picked file.
6573

6674
### Result
6775

6876
The object a `pick` Promise resolves to or the objects in the array a `pickMultiple` Promise resolves to will contain the following keys.
6977

70-
##### `uri`:
78+
##### `uri`:
7179

7280
The URI representing the document picked by the user. _On iOS this will be a `file://` URI for a temporary file in your app's container. On Android this will be a `content://` URI for a document provided by a DocumentProvider that must be accessed with a ContentResolver._
7381

82+
##### `fileCopyUri`:
83+
84+
Same as `uri`, but has special meaning on iOS, if `copyTo` option is specified.
85+
7486
##### `type`:
7587

7688
The MIME type of the file. _On Android some DocumentProviders may not provide MIME types for their documents. On iOS this MIME type is based on the best MIME type for the file extension according to Apple's internal "Uniform Type Identifiers" database._
@@ -153,7 +165,6 @@ try {
153165

154166
<img src="http://i.stack.imgur.com/dv0iQ.png" height="400">
155167

156-
157168
## How to send it back ?
158169

159170
I recommend using [https://github.com/johanneslumpe/react-native-fs](https://github.com/johanneslumpe/react-native-fs)
@@ -170,7 +181,7 @@ if ([fileData length] == 0) {
170181
```
171182

172183
```javascript
173-
let url = "file://whatever/com.bla.bla/file.ext"; //The url you received from the DocumentPicker
184+
let url = 'file://whatever/com.bla.bla/file.ext'; //The url you received from the DocumentPicker
174185

175186
// I STRONGLY RECOMMEND ADDING A SMALL SETTIMEOUT before uploading the url you just got.
176187
const split = url.split('/');
@@ -184,50 +195,55 @@ const uploadBegin = (response) => {
184195
};
185196

186197
const uploadProgress = (response) => {
187-
const percentage = Math.floor((response.totalBytesSent/response.totalBytesExpectedToSend) * 100);
198+
const percentage = Math.floor(
199+
(response.totalBytesSent / response.totalBytesExpectedToSend) * 100
200+
);
188201
console.log('UPLOAD IS ' + percentage + '% DONE!');
189202
};
190203

191204
RNFS.uploadFiles({
192-
toUrl: uploadUrl,
193-
files: [{
205+
toUrl: uploadUrl,
206+
files: [
207+
{
194208
name,
195-
filename:name,
209+
filename: name,
196210
filepath: realPath,
197-
}],
198-
method: 'POST',
199-
headers: {
200-
'Accept': 'application/json',
201-
},
202-
begin: uploadBegin,
203-
beginCallback: uploadBegin, // Don't ask me, only way I made it work as of 1.5.1
204-
progressCallback: uploadProgress,
205-
progress: uploadProgress
206-
})
207-
.then((response) => {
208-
console.log(response,"<<< Response");
209-
if (response.statusCode == 200) { //You might not be getting a statusCode at all. Check
210-
console.log('FILES UPLOADED!');
211-
} else {
212-
console.log('SERVER ERROR');
213-
}
214-
})
215-
.catch((err) => {
216-
if (err.description) {
217-
switch (err.description) {
218-
case "cancelled":
219-
console.log("Upload cancelled");
220-
break;
221-
case "empty":
222-
console.log("Empty file");
223-
default:
224-
//Unknown
225-
}
226-
} else {
227-
//Weird
228-
}
229-
console.log(err);
230-
});
211+
},
212+
],
213+
method: 'POST',
214+
headers: {
215+
Accept: 'application/json',
216+
},
217+
begin: uploadBegin,
218+
beginCallback: uploadBegin, // Don't ask me, only way I made it work as of 1.5.1
219+
progressCallback: uploadProgress,
220+
progress: uploadProgress,
221+
})
222+
.then((response) => {
223+
console.log(response, '<<< Response');
224+
if (response.statusCode == 200) {
225+
//You might not be getting a statusCode at all. Check
226+
console.log('FILES UPLOADED!');
227+
} else {
228+
console.log('SERVER ERROR');
229+
}
230+
})
231+
.catch((err) => {
232+
if (err.description) {
233+
switch (err.description) {
234+
case 'cancelled':
235+
console.log('Upload cancelled');
236+
break;
237+
case 'empty':
238+
console.log('Empty file');
239+
default:
240+
//Unknown
241+
}
242+
} else {
243+
//Weird
244+
}
245+
console.log(err);
246+
});
231247
```
232248

233249
## Help wanted: Improvements

android/src/main/java/io/github/elyx0/reactnativedocumentpicker/DocumentPickerModule.java

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ public class DocumentPickerModule extends ReactContextBaseJavaModule {
4444
private static final String OPTION_MULIPLE = "multiple";
4545

4646
private static final String FIELD_URI = "uri";
47+
private static final String FIELD_FILE_COPY_URI = "fileCopyUri";
4748
private static final String FIELD_NAME = "name";
4849
private static final String FIELD_TYPE = "type";
4950
private static final String FIELD_SIZE = "size";
@@ -60,15 +61,6 @@ public void onActivityResult(Activity activity, int requestCode, int resultCode,
6061
}
6162
};
6263

63-
private String[] readableArrayToStringArray(ReadableArray readableArray) {
64-
int l = readableArray.size();
65-
String[] array = new String[l];
66-
for (int i = 0; i < l; ++i) {
67-
array[i] = readableArray.getString(i);
68-
}
69-
return array;
70-
}
71-
7264
private Promise promise;
7365

7466
public DocumentPickerModule(ReactApplicationContext reactContext) {
@@ -105,10 +97,9 @@ public void pick(ReadableMap args, Promise promise) {
10597
intent.setType("*/*");
10698
if (!args.isNull(OPTION_TYPE)) {
10799
ReadableArray types = args.getArray(OPTION_TYPE);
108-
if (types.size() > 1) {
100+
if (types != null && types.size() > 1) {
109101
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
110-
String[] mimeTypes = readableArrayToStringArray(types);
111-
intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes);
102+
intent.putExtra(Intent.EXTRA_MIME_TYPES, Arguments.toList(types));
112103
} else {
113104
Log.e(NAME, "Multiple type values not supported below API level 19");
114105
}
@@ -178,14 +169,14 @@ private WritableMap getMetadata(Uri uri) {
178169
WritableMap map = Arguments.createMap();
179170

180171
map.putString(FIELD_URI, uri.toString());
172+
// TODO vonovak - FIELD_FILE_COPY_URI is implemented on iOS only (copyTo) settings
173+
map.putString(FIELD_FILE_COPY_URI, uri.toString());
181174

182175
ContentResolver contentResolver = getReactApplicationContext().getContentResolver();
183176

184177
map.putString(FIELD_TYPE, contentResolver.getType(uri));
185178

186-
Cursor cursor = contentResolver.query(uri, null, null, null, null, null);
187-
188-
try {
179+
try (Cursor cursor = contentResolver.query(uri, null, null, null, null, null)) {
189180
if (cursor != null && cursor.moveToFirst()) {
190181
int displayNameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
191182
if (!cursor.isNull(displayNameIndex)) {
@@ -204,10 +195,6 @@ private WritableMap getMetadata(Uri uri) {
204195
map.putInt(FIELD_SIZE, cursor.getInt(sizeIndex));
205196
}
206197
}
207-
} finally {
208-
if (cursor != null) {
209-
cursor.close();
210-
}
211198
}
212199

213200
return map;

android/src/main/java/io/github/elyx0/reactnativedocumentpicker/DocumentPickerPackage.java

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package io.github.elyx0.reactnativedocumentpicker;
22

33
import com.facebook.react.ReactPackage;
4-
import com.facebook.react.bridge.JavaScriptModule;
54
import com.facebook.react.bridge.NativeModule;
65
import com.facebook.react.bridge.ReactApplicationContext;
76
import com.facebook.react.uimanager.ViewManager;
@@ -12,11 +11,6 @@
1211

1312
public class DocumentPickerPackage implements ReactPackage {
1413

15-
// Deprecated RN 0.47
16-
public List<Class<? extends JavaScriptModule>> createJSModules() {
17-
return Collections.emptyList();
18-
}
19-
2014
@Override
2115
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
2216
return Collections.emptyList();

index.d.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,13 @@ declare module 'react-native-document-picker' {
4848
windows: Types['extensions']
4949
};
5050
interface DocumentPickerOptions<OS extends keyof PlatformTypes> {
51-
type: Array<PlatformTypes[OS][keyof PlatformTypes[OS]]> | DocumentType[OS]
51+
type: Array<PlatformTypes[OS][keyof PlatformTypes[OS]]> | DocumentType[OS];
52+
copyTo?: 'cachesDirectory' | 'documentDirectory';
5253
}
5354
interface DocumentPickerResponse {
5455
uri: string;
56+
fileCopyUri: string;
57+
copyError?: string;
5558
type: string;
5659
name: string;
5760
size: string;

index.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ function pick(opts) {
7070
);
7171
}
7272

73+
if ('copyTo' in opts && !['cachesDirectory', 'documentDirectory'].includes(opts.copyTo)) {
74+
throw new TypeError('Invalid copyTo option: ' + opts.copyTo);
75+
}
76+
7377
return RNDocumentPicker.pick(opts);
7478
}
7579

ios/RNDocumentPicker/RNDocumentPicker.h

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
1-
#if __has_include(<React/RCTBridgeModule.h>)
21
#import <React/RCTBridgeModule.h>
3-
#else // back compatibility for RN version < 0.40
4-
#import "RCTBridgeModule.h"
5-
#endif
62

73
@import UIKit;
84

0 commit comments

Comments
 (0)