POST Multipart Form Data using Retrofit 2.0 includ

2019-01-01 07:19发布

问题:

I am trying to do a HTTP POST to server using Retrofit 2.0

        MediaType MEDIA_TYPE_TEXT = MediaType.parse(\"text/plain\");
        MediaType MEDIA_TYPE_IMAGE = MediaType.parse(\"image/*\");

        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        imageBitmap.compress(Bitmap.CompressFormat.JPEG, 90, byteArrayOutputStream);
        profilePictureByte = byteArrayOutputStream.toByteArray();

    Call<APIResults> call = ServiceAPI.updateProfile(
                RequestBody.create(MEDIA_TYPE_TEXT, emailString),
                RequestBody.create(MEDIA_TYPE_IMAGE, profilePictureByte));

    call.enqueue();

The server returns an error saying the file is not valid.

This is weird because I have tried to upload the same file with the same format on iOS(using other library), but it uploads successfully.

I am wondering what is the proper way to upload an image using Retrofit 2.0?

Should I save it to disk first before uploading?

Thank you!

P.S.: I have used retrofit for other Multipart request that does not include image and they completed successfully. The problem is when I am trying to include a byte to the body.

回答1:

I am highlighting the solution in both 1.9 and 2.0 since it is useful for some

In 1.9, I think the better solution is to save the file to disk and use it as Typed file like:

RetroFit 1.9

(I don\'t know about your server-side implementation) have an API interface method similar to this

@POST(\"/en/Api/Results/UploadFile\")
void UploadFile(@Part(\"file\")TypedFile file,@Part(\"folder\")String folder,Callback<Response> callback);

And use it like

TypedFile file = new TypedFile(\"multipart/form-data\", new File(path));

For RetroFit 2 Use the following method

RetroFit 2.0 ( This was a workaround for an issue in RetroFit 2 which is fixed now, for the correct method refer jimmy0251\'s answer)

API Interface:

public interface ApiInterface {
    @Multipart
    @POST(\"/api/Accounts/editaccount\")
    Call<User> editUser (@Header(\"Authorization\") String authorization, @Part(\"file\\\"; filename=\\\"pp.png\\\" \") RequestBody file , @Part(\"FirstName\") RequestBody fname, @Part(\"Id\") RequestBody id);
}

Use it like:

File file = new File(imageUri.getPath());
RequestBody fbody = RequestBody.create(MediaType.parse(\"image/*\"), file);
RequestBody name = RequestBody.create(MediaType.parse(\"text/plain\"), firstNameField.getText().toString());
RequestBody id = RequestBody.create(MediaType.parse(\"text/plain\"), AZUtils.getUserId(this));
Call<User> call = client.editUser(AZUtils.getToken(this), fbody, name, id);
call.enqueue(new Callback<User>() {
    @Override
    public void onResponse(retrofit.Response<User> response, Retrofit retrofit) {
        AZUtils.printObject(response.body());
    }

    @Override
    public void onFailure(Throwable t) {
        t.printStackTrace();
    }
});


回答2:

There is a correct way of uploading a file with its name with Retrofit 2, without any hack:

Define API interface:

@Multipart
@POST(\"uploadAttachment\")
Call<MyResponse> uploadAttachment(@Part MultipartBody.Part filePart); 
                                   // You can add other parameters too

Upload file like this:

File file = // initialize file here

MultipartBody.Part filePart = MultipartBody.Part.createFormData(\"file\", file.getName(), RequestBody.create(MediaType.parse(\"image/*\"), file));

Call<MyResponse> call = api.uploadAttachment(filePart);

This demonstrates only file uploading, you can also add other parameters in the same method with @Part annotation.



回答3:

I used Retrofit 2.0 for my register users, send multipart/form File image and text from register account

In my RegisterActivity, use an AsyncTask

//AsyncTask
private class Register extends AsyncTask<String, Void, String> {

    @Override
    protected void onPreExecute() {..}

    @Override
    protected String doInBackground(String... params) {
        new com.tequilasoft.mesasderegalos.dbo.Register().register(txtNombres, selectedImagePath, txtEmail, txtPassword);
        responseMensaje = StaticValues.mensaje ;
        mensajeCodigo = StaticValues.mensajeCodigo;
        return String.valueOf(StaticValues.code);
    }

    @Override
    protected void onPostExecute(String codeResult) {..}

And in my Register.java class is where use Retrofit with synchronous call

import android.util.Log;
import com.tequilasoft.mesasderegalos.interfaces.RegisterService;
import com.tequilasoft.mesasderegalos.utils.StaticValues;
import com.tequilasoft.mesasderegalos.utils.Utilities;
import java.io.File;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.RequestBody;
import okhttp3.ResponseBody;
import retrofit2.Call; 
import retrofit2.Response;
/**Created by sam on 2/09/16.*/
public class Register {

public void register(String nombres, String selectedImagePath, String email, String password){

    try {
        // create upload service client
        RegisterService service = ServiceGenerator.createUser(RegisterService.class);

        // add another part within the multipart request
        RequestBody requestEmail =
                RequestBody.create(
                        MediaType.parse(\"multipart/form-data\"), email);
        // add another part within the multipart request
        RequestBody requestPassword =
                RequestBody.create(
                        MediaType.parse(\"multipart/form-data\"), password);
        // add another part within the multipart request
        RequestBody requestNombres =
                RequestBody.create(
                        MediaType.parse(\"multipart/form-data\"), nombres);

        MultipartBody.Part imagenPerfil = null;
        if(selectedImagePath!=null){
            File file = new File(selectedImagePath);
            Log.i(\"Register\",\"Nombre del archivo \"+file.getName());
            // create RequestBody instance from file
            RequestBody requestFile =
                    RequestBody.create(MediaType.parse(\"multipart/form-data\"), file);
            // MultipartBody.Part is used to send also the actual file name
            imagenPerfil = MultipartBody.Part.createFormData(\"imagenPerfil\", file.getName(), requestFile);
        }

        // finally, execute the request
        Call<ResponseBody> call = service.registerUser(imagenPerfil, requestEmail,requestPassword,requestNombres);
        Response<ResponseBody> bodyResponse = call.execute();
        StaticValues.code  = bodyResponse.code();
        StaticValues.mensaje  = bodyResponse.message();
        ResponseBody errorBody = bodyResponse.errorBody();
        StaticValues.mensajeCodigo  = errorBody==null
                ?null
                :Utilities.mensajeCodigoDeLaRespuestaJSON(bodyResponse.errorBody().byteStream());
        Log.i(\"Register\",\"Code \"+StaticValues.code);
        Log.i(\"Register\",\"mensaje \"+StaticValues.mensaje);
        Log.i(\"Register\",\"mensajeCodigo \"+StaticValues.mensaje);
    }
    catch (Exception e){
        e.printStackTrace();
    }
}
}

In the interface of RegisterService

public interface RegisterService {
@Multipart
@POST(StaticValues.REGISTER)
Call<ResponseBody> registerUser(@Part MultipartBody.Part image,
                                @Part(\"email\") RequestBody email,
                                @Part(\"password\") RequestBody password,
                                @Part(\"nombre\") RequestBody nombre
);
}

For the Utilities parse ofr InputStream response

public class Utilities {
public static String mensajeCodigoDeLaRespuestaJSON(InputStream inputStream){
    String mensajeCodigo = null;
    try {
        BufferedReader reader = new BufferedReader(
                new InputStreamReader(
                    inputStream, \"iso-8859-1\"), 8);
        StringBuilder sb = new StringBuilder();
        String line;
        while ((line = reader.readLine()) != null) {
            sb.append(line).append(\"\\n\");
        }
        inputStream.close();
        mensajeCodigo = sb.toString();
    } catch (Exception e) {
        Log.e(\"Buffer Error\", \"Error converting result \" + e.toString());
    }
    return mensajeCodigo;
}
}


回答4:

Update Code for image file uploading in Retrofit2.0

public interface ApiInterface {
    @Multipart
    @POST(\"user/signup\")
    Call<UserModelResponse> updateProfilePhotoProcess(@Part(\"email\") RequestBody email, @Part(\"password\") RequestBody password, @Part(\"profile_pic\\\"; filename=\\\"pp.png\\\" \") RequestBody file);
}

Change MediaType.parse(\"image/*\") to MediaType.parse(\"image/jpeg\")

RequestBody reqFile = RequestBody.create(MediaType.parse(\"image/jpeg\"), file);
RequestBody email = RequestBody.create(MediaType.parse(\"text/plain\"), \"upload_test4@gmail.com\");
RequestBody password = RequestBody.create(MediaType.parse(\"text/plain\"), \"123456789\");

Call<UserModelResponse> call = apiService.updateProfilePhotoProcess(email,password,reqFile);
call.enqueue(new Callback<UserModelResponse>() {
    @Override
    public void onResponse(Call<UserModelResponse> call, Response<UserModelResponse> response) {
        String TAG = response.body().toString();

        UserModelResponse userModelResponse = response.body();
        UserModel userModel = userModelResponse.getUserModel();

       Log.d(\"MainActivity\",\"user image = \"+userModel.getProfilePic());

    }

    @Override
    public void onFailure(Call<UserModelResponse> call, Throwable t) {
        Toast.makeText(MainActivity.this,\"\"+TAG,Toast.LENGTH_LONG).show();

    }
});


回答5:

Adding to the answer given by @insomniac. You can create a Map to put the parameter for RequestBody including image.

Code for Interface

public interface ApiInterface {
@Multipart
@POST(\"/api/Accounts/editaccount\")
Call<User> editUser (@Header(\"Authorization\") String authorization, @PartMap Map<String, RequestBody> map);
}

Code for Java class

File file = new File(imageUri.getPath());
RequestBody fbody = RequestBody.create(MediaType.parse(\"image/*\"), file);
RequestBody name = RequestBody.create(MediaType.parse(\"text/plain\"), firstNameField.getText().toString());
RequestBody id = RequestBody.create(MediaType.parse(\"text/plain\"), AZUtils.getUserId(this));

Map<String, RequestBody> map = new HashMap<>();
map.put(\"file\\\"; filename=\\\"pp.png\\\" \", fbody);
map.put(\"FirstName\", name);
map.put(\"Id\", id);
Call<User> call = client.editUser(AZUtils.getToken(this), map);
call.enqueue(new Callback<User>() {
@Override
public void onResponse(retrofit.Response<User> response, Retrofit retrofit) 
{
    AZUtils.printObject(response.body());
}

@Override
public void onFailure(Throwable t) {
    t.printStackTrace();
 }
});


回答6:

So its very simple way to achieve your task. You need to follow below step :-

1. First step

public interface APIService {  
    @Multipart
    @POST(\"upload\")
    Call<ResponseBody> upload(
        @Part(\"item\") RequestBody description,
        @Part(\"imageNumber\") RequestBody description,
        @Part MultipartBody.Part imageFile
    );
}

You need to make the entire call as @Multipart request. item and image number is just string body which is wrapped in RequestBody. We use the MultipartBody.Part class that allows us to send the actual file name besides the binary file data with the request

2. Second step

  File file = (File) params[0];
  RequestBody requestFile = RequestBody.create(MediaType.parse(\"multipart/form-data\"), file);

  MultipartBody.Part body =MultipartBody.Part.createFormData(\"Image\", file.getName(), requestBody);

  RequestBody ItemId = RequestBody.create(okhttp3.MultipartBody.FORM, \"22\");
  RequestBody ImageNumber = RequestBody.create(okhttp3.MultipartBody.FORM,\"1\");
  final Call<UploadImageResponse> request = apiService.uploadItemImage(body, ItemId,ImageNumber);

Now you have image path and you need to convert into file.Now convert file into RequestBody using method RequestBody.create(MediaType.parse(\"multipart/form-data\"), file). Now you need to convert your RequestBody requestFile into MultipartBody.Part using method MultipartBody.Part.createFormData(\"Image\", file.getName(), requestBody); .

ImageNumber and ItemId is my another data which I need to send to server so I am also make both thing into RequestBody.

For more info



回答7:

Uploading Files using Retrofit is Quite Simple You need to build your api interface as

public interface Api {

    String BASE_URL = \"http://192.168.43.124/ImageUploadApi/\";


    @Multipart
    @POST(\"yourapipath\")
    Call<MyResponse> uploadImage(@Part(\"image\\\"; filename=\\\"myfile.jpg\\\" \") RequestBody file, @Part(\"desc\") RequestBody desc);

}

in the above code image is the key name so if you are using php you will write $_FILES[\'image\'][\'tmp_name\'] to get this. And filename=\"myfile.jpg\" is the name of your file that is being sent with the request.

Now to upload the file you need a method that will give you the absolute path from the Uri.

private String getRealPathFromURI(Uri contentUri) {
    String[] proj = {MediaStore.Images.Media.DATA};
    CursorLoader loader = new CursorLoader(this, contentUri, proj, null, null, null);
    Cursor cursor = loader.loadInBackground();
    int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
    cursor.moveToFirst();
    String result = cursor.getString(column_index);
    cursor.close();
    return result;
}

Now you can use the below code to upload your file.

 private void uploadFile(Uri fileUri, String desc) {

        //creating a file
        File file = new File(getRealPathFromURI(fileUri));

        //creating request body for file
        RequestBody requestFile = RequestBody.create(MediaType.parse(getContentResolver().getType(fileUri)), file);
        RequestBody descBody = RequestBody.create(MediaType.parse(\"text/plain\"), desc);

        //The gson builder
        Gson gson = new GsonBuilder()
                .setLenient()
                .create();


        //creating retrofit object
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(Api.BASE_URL)
                .addConverterFactory(GsonConverterFactory.create(gson))
                .build();

        //creating our api 
        Api api = retrofit.create(Api.class);

        //creating a call and calling the upload image method 
        Call<MyResponse> call = api.uploadImage(requestFile, descBody);

        //finally performing the call 
        call.enqueue(new Callback<MyResponse>() {
            @Override
            public void onResponse(Call<MyResponse> call, Response<MyResponse> response) {
                if (!response.body().error) {
                    Toast.makeText(getApplicationContext(), \"File Uploaded Successfully...\", Toast.LENGTH_LONG).show();
                } else {
                    Toast.makeText(getApplicationContext(), \"Some error occurred...\", Toast.LENGTH_LONG).show();
                }
            }

            @Override
            public void onFailure(Call<MyResponse> call, Throwable t) {
                Toast.makeText(getApplicationContext(), t.getMessage(), Toast.LENGTH_LONG).show();
            }
        });
    }

For more detailed explanation you can visit this Retrofit Upload File Tutorial.