Skip to content

Instantly share code, notes, and snippets.

@kukiron
Created January 21, 2020 10:58
Show Gist options
  • Save kukiron/21af4a26134e50a967131efd89242be3 to your computer and use it in GitHub Desktop.
Save kukiron/21af4a26134e50a967131efd89242be3 to your computer and use it in GitHub Desktop.
Update of `FilePickerModule` from callback to promise implementation in `react-native-file-picker`
package com.filepicker;
import android.Manifest;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.ActivityNotFoundException;
import android.content.ClipData;
import android.content.ContentUris;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
import androidx.core.app.ActivityCompat;
import android.util.Log;
import android.widget.ArrayAdapter;
import com.facebook.react.bridge.ActivityEventListener;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableMapKeySetIterator;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.modules.core.PermissionAwareActivity;
import com.facebook.react.modules.core.PermissionListener;
import org.apache.commons.io.FileUtils;
import java.util.ArrayList;
import java.util.List;
import java.io.InputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.BufferedOutputStream;
import java.io.OutputStream;
public class FilePickerModule extends ReactContextBaseJavaModule implements ActivityEventListener {
static int REQUEST_LAUNCH_FILE_CHOOSER = 2;
private final ReactApplicationContext mReactContext;
private static final String E_ACTIVITY_DOES_NOT_EXIST = "ACTIVITY_DOES_NOT_EXIST";
private static final String E_PERMISSION_DENIED = "PERMISSION_DENIED";
private static final String E_UNABLE_TO_OPEN_FILE_TYPE = "UNABLE_TO_OPEN_FILE_TYPE";
private static final String E_UNKNOWN_ACTIVITY_RESULT = "UNKNOWN_ACTIVITY_RESULT";
private static final String E_INVALID_DATA_RETURNED = "INVALID_DATA_RETURNED";
private static final String E_UNEXPECTED_EXCEPTION = "UNEXPECTED_EXCEPTION";
private Promise mPromise;
public FilePickerModule(ReactApplicationContext reactContext) {
super(reactContext);
reactContext.addActivityEventListener(this);
mReactContext = reactContext;
}
@Override
public String getName() {
return "FilePickerManager";
}
@ReactMethod
public void showFilePicker(final ReadableMap options, final Promise promise) {
final Activity currentActivity = getCurrentActivity();
if (currentActivity == null) {
promise.reject(E_ACTIVITY_DOES_NOT_EXIST, "Current activity does not exist");
return;
}
int readPermission = ActivityCompat.checkSelfPermission(currentActivity, Manifest.permission.READ_EXTERNAL_STORAGE);
int writePermission = ActivityCompat.checkSelfPermission(currentActivity, Manifest.permission.WRITE_EXTERNAL_STORAGE);
if (writePermission != PackageManager.PERMISSION_GRANTED
|| readPermission != PackageManager.PERMISSION_GRANTED) {
String[] PERMISSIONS = {
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
};
((PermissionAwareActivity) currentActivity).requestPermissions(PERMISSIONS, 1, new PermissionListener() {
@Override
public boolean onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
if (requestCode == 1) {
int readPermission = ActivityCompat.checkSelfPermission(currentActivity, Manifest.permission.READ_EXTERNAL_STORAGE);
int writePermission = ActivityCompat.checkSelfPermission(currentActivity, Manifest.permission.WRITE_EXTERNAL_STORAGE);
if (writePermission != PackageManager.PERMISSION_GRANTED
|| readPermission != PackageManager.PERMISSION_GRANTED) {
// user rejected permission request
promise.reject(E_PERMISSION_DENIED, "User rejected permission request");
return true;
}
// permissions available
launchFileChooser(options, promise);
return true;
}
return true;
}
});
} else {
launchFileChooser(options, promise);
}
}
// NOTE: Currently not reentrant / doesn't support concurrent requests
@ReactMethod
public void launchFileChooser(final ReadableMap options, final Promise promise) {
int requestCode;
Intent libraryIntent;
Activity currentActivity = getCurrentActivity();
String type = "*/*";
boolean multiple = false;
if (options.hasKey("multiple")) {
multiple = options.getBoolean("multiple");
}
if (options.hasKey("type")) {
String userRequestedType = options.getString("type");
type = userRequestedType + "/*";
}
if (currentActivity == null) {
promise.reject(E_ACTIVITY_DOES_NOT_EXIST, "Current activity does not exist");
return;
}
if (multiple) {
REQUEST_LAUNCH_FILE_CHOOSER = 41;
requestCode = REQUEST_LAUNCH_FILE_CHOOSER;
} else {
requestCode = REQUEST_LAUNCH_FILE_CHOOSER;
}
libraryIntent = new Intent(Intent.ACTION_GET_CONTENT);
// libraryIntent.setType("audio/*");
if (multiple) {
libraryIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, multiple);
}
libraryIntent.setType(type);
libraryIntent.addCategory(Intent.CATEGORY_OPENABLE);
if (libraryIntent.resolveActivity(mReactContext.getPackageManager()) == null) {
promise.reject(E_UNABLE_TO_OPEN_FILE_TYPE, "Cannot launch file library");
return;
}
mPromise = promise;
try {
Intent intent = null;
if (multiple) {
intent = Intent.createChooser(libraryIntent, "Select a file");
} else {
intent = Intent.createChooser(libraryIntent, "Select multiple files");
}
currentActivity.startActivityForResult(intent, requestCode);
} catch (ActivityNotFoundException e) {
e.printStackTrace();
}
}
// R.N > 33
public void onActivityResult(final Activity activity, final int requestCode,
final int resultCode, final Intent data) {
onActivityResult(requestCode, resultCode, data);
}
public void onActivityResult(final int requestCode, final int resultCode,
final Intent data) {
//robustness code
if (mPromise == null || requestCode != REQUEST_LAUNCH_FILE_CHOOSER) {
return;
}
// user cancel
if (resultCode != Activity.RESULT_OK) {
mPromise.reject(E_UNKNOWN_ACTIVITY_RESULT, "Unknown activity result: " + resultCode);
return;
}
Uri uri = null;
ClipData clipData = null;
if (data != null) {
uri = data.getData();
clipData = data.getClipData();
}
try {
WritableArray results = Arguments.createArray();
if (uri != null) {
WritableMap doc = getDataFromURI(uri);
mPromise.resolve(doc);
} else if (clipData != null && clipData.getItemCount() > 0) {
final int length = clipData.getItemCount();
for (int i = 0; i < length; ++i) {
ClipData.Item item = clipData.getItemAt(i);
WritableMap doc = getDataFromURI(item.getUri());
results.pushMap(doc);
}
mPromise.resolve(results);
} else {
mPromise.reject(E_INVALID_DATA_RETURNED, "Invalid data returned by intent");
return;
}
} catch (Exception e) {
mPromise.reject(E_UNEXPECTED_EXCEPTION, e.getLocalizedMessage(), e);
return;
}
}
private WritableMap getDataFromURI(Uri uri) {
WritableMap map = Arguments.createMap();
Activity currentActivity = getCurrentActivity();
map.putString("uri", uri.toString());
String path = null;
String readableSize = null;
Long size = null;
// final Uri uri = uri.toString();
path = getPath(currentActivity, uri);
if (path != null) {
map.putString("path", path);
readableSize = getFileReadableSize(path);
size = getFileSize(path);
map.putString("readableSize", readableSize);
map.putInt("size", size.intValue());
} else {
path = getFileFromUri(currentActivity, uri);
if (!path.equals("error")) {
readableSize = getFileReadableSize(path);
size = getFileSize(path);
map.putString("path", path);
map.putString("readableSize", readableSize);
map.putInt("size", size.intValue());
}
}
map.putString("type", currentActivity.getContentResolver().getType(uri));
map.putString("fileName", getFileNameFromUri(currentActivity, uri));
return map;
}
@TargetApi(Build.VERSION_CODES.KITKAT)
public static String getPath(final Context context, final Uri uri) {
final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
// DocumentProvider
if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
// ExternalStorageProvider
if (isExternalStorageDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
if ("primary".equalsIgnoreCase(type)) {
return Environment.getExternalStorageDirectory() + "/" + split[1];
}
// TODO handle non-primary volumes
}
// DownloadsProvider
else if (isDownloadsDocument(uri)) {
final String id = DocumentsContract.getDocumentId(uri);
final String[] split = id.split(":");
final String type = split[0];
if ("raw".equalsIgnoreCase(type)) {
return split[1];
} else {
String prefix = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? "file:///" : "content://";
final Uri contentUri = ContentUris.withAppendedId(
Uri.parse(prefix + "downloads/public_downloads"), Long.valueOf(id));
return getDataColumn(context, contentUri, null, null);
}
}
// MediaProvider
else if (isMediaDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
Uri contentUri = null;
if ("image".equals(type)) {
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
} else if ("video".equals(type)) {
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
} else if ("audio".equals(type)) {
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
}
final String selection = "_id=?";
final String[] selectionArgs = new String[]{
split[1]
};
return getDataColumn(context, contentUri, selection, selectionArgs);
}
}
// MediaStore (and general)
else if ("content".equalsIgnoreCase(uri.getScheme())) {
// Return the remote address
if (isGooglePhotosUri(uri))
return uri.getLastPathSegment();
return getDataColumn(context, uri, null, null);
}
// File
else if ("file".equalsIgnoreCase(uri.getScheme())) {
return uri.getPath();
}
return null;
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is ExternalStorageProvider.
*/
public static boolean isExternalStorageDocument(Uri uri) {
return "com.android.externalstorage.documents".equals(uri.getAuthority());
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is DownloadsProvider.
*/
public static boolean isDownloadsDocument(Uri uri) {
return "com.android.providers.downloads.documents".equals(uri.getAuthority());
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is MediaProvider.
*/
public static boolean isMediaDocument(Uri uri) {
return "com.android.providers.media.documents".equals(uri.getAuthority());
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is Google Photos.
*/
public static boolean isGooglePhotosUri(Uri uri) {
return "com.google.android.apps.photos.content".equals(uri.getAuthority());
}
public static String getDataColumn(Context context, Uri uri, String selection,
String[] selectionArgs) {
Cursor cursor = null;
final String column = "_data";
final String[] projection = {
column
};
try {
cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
null);
if (cursor != null && cursor.moveToFirst()) {
final int column_index = cursor.getColumnIndexOrThrow(column);
return cursor.getString(column_index);
}
} catch (Exception e) {
Log.e("FilePickerModule", "Failed to get cursor, so return null for path", e);
} finally {
if (cursor != null)
cursor.close();
}
return null;
}
private String getFileReadableSize(String path) {
File file = new File(path);
long size = FileUtils.sizeOf(file);
return FileUtils.byteCountToDisplaySize(size);
}
private Long getFileSize (String path) {
File file = new File(path);
return file.length();
}
private String getFileFromUri(Activity activity, Uri uri) {
//If it can't get path of file, file is saved in cache, and obtain path from there
try {
String filePath = activity.getCacheDir().toString();
String fileName = getFileNameFromUri(activity, uri);
String path = filePath + "/" + fileName;
if (!fileName.equals("error") && saveFileOnCache(path, activity, uri)) {
return path;
} else {
return "error";
}
} catch (Exception e) {
//Log.d("FilePickerModule", "Error getFileFromStream");
return "error";
}
}
private String getFileNameFromUri(Activity activity, Uri uri) {
Cursor cursor = activity.getContentResolver().query(uri, null, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
final int column_index = cursor.getColumnIndexOrThrow("_display_name");
return cursor.getString(column_index);
} else {
return "error";
}
}
private boolean saveFileOnCache(String path, Activity activity, Uri uri) {
//Log.d("FilePickerModule", "saveFileOnCache path: "+path);
try {
InputStream is = activity.getContentResolver().openInputStream(uri);
OutputStream stream = new BufferedOutputStream(new FileOutputStream(path));
int bufferSize = 1024;
byte[] buffer = new byte[bufferSize];
int len = 0;
while ((len = is.read(buffer)) != -1) {
stream.write(buffer, 0, len);
}
if (stream != null)
stream.close();
//Log.d("FilePickerModule", "saveFileOnCache done!");
return true;
} catch (Exception e) {
//Log.d("FilePickerModule", "saveFileOnCache error");
return false;
}
}
// Required for RN 0.30+ modules than implement ActivityEventListener
public void onNewIntent(Intent intent) {
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment