问题
Update: I started Bounty for this question, because accepted solution is working till Oreo version, but my project supports from API level 21 to 30.
Please check end of the question, to see latest updates.
I tried to upload a PDF file [Not Image] in PHP API from Android, and I'm getting 400 with this exception:
{"head":{"StatusValue":400,"StatusText":"Failed"},"body":{"Error":"Missing required property fileId"}}
I'm getting 200 request code, when I'm doing in Postman:
Now, Android code:
@Multipart
@POST("eligibity/auth/attachment/upload/add")
Call<ResponseBody> uploadFile(@Part MultipartBody.Part file, @Part("fileName") RequestBody name, @Part("body") JSONObject body);
private void uploadPDF(String path) {
String pdfname = String.valueOf(Calendar.getInstance().getTimeInMillis());
File file = new File(path);
RequestBody requestBody = RequestBody.create(MediaType.parse("multipart/form-data"), file);
MultipartBody.Part fileToUpload = MultipartBody.Part.createFormData("fileName", file.getName(), requestBody);
RequestBody filename = RequestBody.create(MediaType.parse("text/plain"), pdfname);
try {
JSONObject sJsonAttachment = new JSONObject();
JSONObject body = new JSONObject();
sJsonAttachment.put("appointmentId", AdapterCollections.clsClaimDiscussionList.get(position).appointment_id);
sJsonAttachment.put("createdBy", Integer.parseInt(preferenceManager.getUserId()));
sJsonAttachment.put("customerId", Integer.parseInt(preferenceManager.getCustomerId()));
sJsonAttachment.put("encounterId", AdapterCollections.clsClaimDiscussionList.get(position).encounter_id);
sJsonAttachment.put("expiryDate", Utility.getCurrentDate("MM-DD-YY"));
sJsonAttachment.put("fbType", 4);
sJsonAttachment.put("fileId", 0);
sJsonAttachment.put("fileName", "name");
sJsonAttachment.put("insTpaPatId", 0);
sJsonAttachment.put("isActive", 1);
sJsonAttachment.put("messageId", AdapterCollections.clsClaimDiscussionList.get(position).log_messages.get(position).log_id);
sJsonAttachment.put("patientId", AdapterCollections.clsClaimDiscussionList.get(position).patient_id);
sJsonAttachment.put("recType", "");
sJsonAttachment.put("reportDate", Utility.getCurrentDate("DD-MM-YYYY"));
sJsonAttachment.put("siteId", Integer.parseInt(preferenceManager.getSiteId()));
sJsonAttachment.put("type", 4);
sJsonAttachment.put("uploadDate", Utility.getCurrentDate("MMDDYY"));
// body.put("body", sJsonAttachment);
ApiCall apiCall = RetrofitService.createService(SCMS_BASE_URL, ApiCall.class);
assert apiCall != null;
apiCall.uploadFile(fileToUpload, filename, sJsonAttachment).enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(@NotNull Call<ResponseBody> call, @NotNull Response<ResponseBody> response) {
try {
showLog("STATUS: " + response.code());
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void onFailure(@NotNull Call<ResponseBody> call, @NotNull Throwable t) {
showLog("FAILED: "+ t.getMessage());
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
I know that I'm doing somewhere small mistakes, but not able to spot that.
If I used part then this error: Missing required property fileId, and if I use query then this error: must be object.
---------------------------------UPDATE------------------------------------
Regarding Path: provider_paths.xml
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="/storage/emulated/0" path="."/>
</paths>
Intent to open PDF chooser:
private void fileDialog() {
Intent intent = new Intent().setType("application/pdf").setAction(Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
startActivityForResult(Intent.createChooser(intent, "Select PDF file"), 123);
}
I followed this https://stackoverflow.com/a/62830720/12630878 answer to getPath, but, I'm NOT getting path properly.
Higher version means: 28 to 30
Higher Version URI: URI: content://com.android.providers.media.documents/document/document%3A31
FROM RECENT, If I'll choose PDF, then cursor is returning null:
if (cursor.moveToFirst()) {
return cursor.getString(column_index);
}
FROM DOWNLOADS, if I'll choose PDF, then NumberFormatException coming on this line: URI: content://com.android.providers.downloads.documents/document/msf%3A27
Exception: java.lang.NumberFormatException: For input string: "msf:27"
LINE NO: Uri contentUri = ContentUris.withAppendedId(Uri.parse(contentUriPrefix), Long.valueOf(id));
FileUtils class: https://drive.google.com/file/d/1S5-Sdp_CCSXXsOsZxwuhv0F5_CtH2EQO/view?usp=sharing
Search this method where I'm getting Path:
public static File getFile(Context context, Uri uri) {
if (uri != null) {
String path = getPathForAllVersion(context, uri);
// String path = getFilePathFromURI(context, uri);
showLog("isPATH: " + path);
if (checkNull(path) && isLocal(path)) {
return new File(path);
}
}
return null;
}
回答1:
You api accept only two parameters but you passed three parameters that's why you getting error.
so API method should be
@Multipart
@POST("eligibity/auth/attachment/upload/add")
Call<ResponseBody> uploadFile(
@Part("body") RequestBody description,
@Part MultipartBody.Part file
);
And update your uploadPDF()
like below
private void uploadPDF(String path) {
//json data
JSONObject sJsonAttachment = new JSONObject();
JSONObject body = new JSONObject();
sJsonAttachment.put("appointmentId", AdapterCollections.clsClaimDiscussionList.get(position).appointment_id);
sJsonAttachment.put("createdBy", Integer.parseInt(preferenceManager.getUserId()));
sJsonAttachment.put("customerId", Integer.parseInt(preferenceManager.getCustomerId()));
sJsonAttachment.put("encounterId", AdapterCollections.clsClaimDiscussionList.get(position).encounter_id);
sJsonAttachment.put("expiryDate", Utility.getCurrentDate("MM-DD-YY"));
sJsonAttachment.put("fbType", 4);
sJsonAttachment.put("fileId", 0);
sJsonAttachment.put("fileName", "name");
sJsonAttachment.put("insTpaPatId", 0);
sJsonAttachment.put("isActive", 1);
sJsonAttachment.put("messageId", AdapterCollections.clsClaimDiscussionList.get(position).log_messages.get(position).log_id);
sJsonAttachment.put("patientId", AdapterCollections.clsClaimDiscussionList.get(position).patient_id);
sJsonAttachment.put("recType", "");
sJsonAttachment.put("reportDate", Utility.getCurrentDate("DD-MM-YYYY"));
sJsonAttachment.put("siteId", Integer.parseInt(preferenceManager.getSiteId()));
sJsonAttachment.put("type", 4);
sJsonAttachment.put("uploadDate", Utility.getCurrentDate("MMDDYY"));
// create RequestBody instance from file
File file=new File(path);
RequestBody requestFile =
RequestBody.create(
MediaType.parse(Files.probeContentType(file.toPath()))),
file
);
// MultipartBody.Part is used to send also the actual file name
MultipartBody.Part fileBody =
MultipartBody.Part.createFormData("fileName", file.getName(), requestFile);
// add another part within the multipart request
RequestBody bodyJsonAttachment =
RequestBody.create(
okhttp3.MultipartBody.FORM, sJsonAttachment.toString());
ApiCall apiCall = RetrofitService.createService(SCMS_BASE_URL, ApiCall.class);
assert apiCall != null;
apiCall.uploadFile(fileBody, bodyJsonAttachment).enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(@NotNull Call<ResponseBody> call, @NotNull Response<ResponseBody> response) {
try {
showLog("STATUS: " + response.code());
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void onFailure(@NotNull Call<ResponseBody> call, @NotNull Throwable t) {
showLog("FAILED: "+ t.getMessage());
}
});
}
Note : if you getting warning error on mime type, update it
回答2:
If you are using multipart then pass all the field in Multipart just like this.
Declare method in Api interface just like this. Only one MultipartBody field.
@Multipart
@POST("eligibity/auth/attachment/upload/add")
Call<ResponseBody> uploadFile(@Body MultipartBody body);
Create MultipartBody.Builder
add your and your field in it.
MultipartBody.Builder requestBodyBuilde = MultipartBody.Builder().setType(MultipartBody.FORM);
//adding image in multipart
File file = File(selected_file_path);
RequestBody fileBody = ProgressRequestBody(file, this);
requestBodyBuilde.addFormDataPart("fileName", file.name, fileBody);
//add your other field just like this
requestBodyBuilde.addFormDataPart("body", sJsonAttachment );
ApiCall apiCall = RetrofitService.createService(SCMS_BASE_URL, ApiCall.class);
assert apiCall != null;
//Note here we will pass only one parameter that is multipartBody
apiCall.uploadFile(requestBodyBuilde.build()).enqueue(new
Callback<ResponseBody>() {
@Override
public void onResponse(@NotNull Call<ResponseBody> call, @NotNull Response<ResponseBody> response) {
try {
showLog("STATUS: " + response.code());
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void onFailure(@NotNull Call<ResponseBody> call, @NotNull Throwable t) {
showLog("FAILED: "+ t.getMessage());
}
});
} catch (Exception e) {
e.printStackTrace();
}
回答3:
Please use this method to get file path from your device, suppose you are facing with file path problem
public static String getPath(Context context, Uri uri) throws URISyntaxException {
final boolean needToCheckUri = Build.VERSION.SDK_INT >= 19;
String selection = null;
String[] selectionArgs = null;
// Uri is different in versions after KITKAT (Android 4.4), we need to
// deal with different Uris.
if (needToCheckUri && DocumentsContract.isDocumentUri(context.getApplicationContext(), uri)) {
if (isExternalStorageDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
return Environment.getExternalStorageDirectory() + "/" + split[1];
} else if (isDownloadsDocument(uri)) {
final String id = DocumentsContract.getDocumentId(uri);
if (id != null && id.startsWith("raw:")) {
return id.substring(4);
}
String[] contentUriPrefixesToTry = new String[]{
"content://downloads/public_downloads",
"content://downloads/my_downloads",
"content://downloads/all_downloads"
};
for (String contentUriPrefix : contentUriPrefixesToTry) {
Uri contentUri = ContentUris.withAppendedId(Uri.parse(contentUriPrefix), Long.valueOf(id));
try {
String path = getDataColumn(context, contentUri, null, null);
if (path != null) {
return path;
}
} catch (Exception e) {}
}
// path could not be retrieved using ContentResolver, therefore copy file to accessible cache using streams
String fileName = getFileName(context, uri);
File cacheDir = getDocumentCacheDir(context);
File file = generateFileName(fileName, cacheDir);
String destinationPath = null;
if (file != null) {
destinationPath = file.getAbsolutePath();
saveFileFromUri(context, uri, destinationPath);
}
return destinationPath;
} else if (isMediaDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
if ("image".equals(type)) {
uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
} else if ("video".equals(type)) {
uri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
} else if ("audio".equals(type)) {
uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
}
selection = "_id=?";
selectionArgs = new String[]{ split[1] };
}else if (isGoogleDrive(uri)) { // Google Drive
String TAG = "isGoogleDrive";
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(";");
final String acc = split[0];
final String doc = split[1];
try {
return saveFileIntoExternalStorageByUri(context, uri);
} catch (Exception e) {
e.printStackTrace();
}
}
}
if ("content".equalsIgnoreCase(uri.getScheme())) {
if (uri.toString().startsWith("content://com.google.android.apps.photos.content")){
if (isGooglePhotosUri(uri)) {
return uri.getLastPathSegment();
}else{
try {
return saveFileIntoExternalStorageByUri(context, uri);
} catch (Exception e) {
e.printStackTrace();
}
}
}
String[] projection = { MediaStore.Images.Media.DATA };
Cursor cursor = null;
try {
cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null);
int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
if (cursor.moveToFirst()) {
return cursor.getString(column_index);
}
}catch (SecurityException e) {
//if file open with third party application
if (uri.toString().contains("/storage/emulated/0")) {
return "/storage/emulated/0" + uri.toString().split("/storage/emulated/0")[1];
}
} catch (Exception e) {
e.printStackTrace();
}
} else if ("file".equalsIgnoreCase(uri.getScheme())) {
return uri.getPath();
}
return null;
}
And the methods
public static boolean isExternalStorageDocument(Uri uri) {
return "com.android.externalstorage.documents".equals(uri.getAuthority());
}
public static boolean isDownloadsDocument(Uri uri) {
return "com.android.providers.downloads.documents".equals(uri.getAuthority());
}
public static boolean isMediaDocument(Uri uri) {
return "com.android.providers.media.documents".equals(uri.getAuthority());
}
public static boolean isGooglePhotosUri(Uri uri) {
return "com.google.android.apps.photos.content".equals(uri.getAuthority());
}
public static boolean isGoogleDrive(Uri uri) {
return uri.getAuthority().equalsIgnoreCase("com.google.android.apps.docs.storage");
}
public static String saveFileIntoExternalStorageByUri(Context context, Uri uri) throws Exception {
InputStream inputStream = context.getContentResolver().openInputStream(uri);
int originalSize = inputStream.available();
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
String fileName = getFileName(context, uri);
File file = makeEmptyFileIntoExternalStorageWithTitle(fileName);
bis = new BufferedInputStream(inputStream);
bos = new BufferedOutputStream(new FileOutputStream(
file, false));
byte[] buf = new byte[originalSize];
bis.read(buf);
do {
bos.write(buf);
} while (bis.read(buf) != -1);
bos.flush();
bos.close();
bis.close();
return file.getAbsolutePath();
}
public static File makeEmptyFileIntoExternalStorageWithTitle(String title) {
String root = Environment.getExternalStorageDirectory().getAbsolutePath();
return new File(root, title);
}
public static String getFileName(Context context,Uri uri) {
String result = null;
if (uri.getScheme().equals("content")) {
Cursor cursor = null;
try {
cursor = context.getContentResolver().query(uri, null, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
result = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
}
}catch (SecurityException e) {
//if file open with third party application
if (uri.toString().contains("/storage/emulated/0")) {
result = "/storage/emulated/0" + uri.toString().split("/storage/emulated/0")[1];
}
}catch (Exception e){
e.printStackTrace();
return null;
} finally{
if(cursor!=null)
cursor.close();
}
} if (uri.getScheme().equals("file")) {
return uri.getPath();
}
if (result == null) {
result = uri.getPath();
int cut = result.lastIndexOf('/');
if (cut != -1) {
result = result.substring(cut + 1);
}
}
return result;
}
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);
}
} finally {
if (cursor != null)
cursor.close();
}
return null;
}
public static File getDocumentCacheDir(@NonNull Context context) {
File dir = new File(context.getCacheDir(), DOCUMENTS_DIR);
if (!dir.exists()) {
dir.mkdirs();
}
return dir;
}
@Nullable
public static File generateFileName(@Nullable String name, File directory) {
if (name == null) {
return null;
}
File file = new File(directory, name);
if (file.exists()) {
String fileName = name;
String extension = "";
int dotIndex = name.lastIndexOf('.');
if (dotIndex > 0) {
fileName = name.substring(0, dotIndex);
extension = name.substring(dotIndex);
}
int index = 0;
while (file.exists()) {
index++;
name = fileName + '(' + index + ')' + extension;
file = new File(directory, name);
}
}
try {
if (!file.createNewFile()) {
return null;
}
} catch (IOException e) {
return null;
}
return file;
}
private static void saveFileFromUri(Context context, Uri uri, String destinationPath) {
InputStream is = null;
BufferedOutputStream bos = null;
try {
is = context.getContentResolver().openInputStream(uri);
bos = new BufferedOutputStream(new FileOutputStream(destinationPath, false));
byte[] buf = new byte[1024];
is.read(buf);
do {
bos.write(buf);
} while (is.read(buf) != -1);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (is != null) is.close();
if (bos != null) bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
来源:https://stackoverflow.com/questions/62783444/why-does-pdf-is-not-able-to-upload-in-php-api-for-android-pie-q-and-r-using-ret