PUBLICAR datos de formulario multiparte utilizando Retrofit 2.0, incluida la imagen

148

Estoy tratando de hacer una POST HTTP al servidor usando 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();

El servidor devuelve un error diciendo que el archivo no es válido.

Esto es extraño porque he intentado cargar el mismo archivo con el mismo formato en iOS (usando otra biblioteca), pero se carga con éxito.

Me pregunto cuál es la forma correcta de cargar una imagen usando Retrofit 2.0 .

¿Debo guardarlo en el disco antes de cargarlo?

PD: He usado la actualización para otra solicitud de varias partes que no incluye imagen y se completaron con éxito. El problema es cuando intento incluir un byte en el cuerpo.

JayVDiyk
fuente

Respuestas:

180

Estoy destacando la solución en 1.9 y 2.0 ya que es útil para algunos

En 1.9, creo que la mejor solución es guardar el archivo en el disco y usarlo como archivo escrito como:

RetroFit 1.9

(No sé acerca de su implementación del lado del servidor) tiene un método de interfaz API similar a este

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

Y úsalo como

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

Para RetroFit 2, use el siguiente método

RetroFit 2.0 (Esta fue una solución para un problema en RetroFit 2 que se solucionó ahora; para el método correcto, consulte la respuesta de jimmy0251 )

Interfaz API:

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);
}

Úselo como:

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();
    }
});
insomne
fuente
55
Sí, bueno, creo que es un problema ( github.com/square/retrofit/issues/1063 ) con la actualización 2.0, es posible que desee quedarse con 1.9
insomniac
2
ver mi edición, no he tryed sin embargo, que será recibido en
insomne
1
He subido con éxito una imagen usando el ejemplo de Retrofit 2.0.
jerogaren
3
@Bhargav Puede cambiar la interfaz a @Multipart @POST("/api/Accounts/editaccount") Call<User> editUser(@PartMap Map<String, RequestBody> params);Y cuando tenga el archivo: Map<String, RequestBody> map = new HashMap<>(); RequestBody fileBody = RequestBody.create(MediaType.parse("image/jpg"), file); map.put("file\"; filename=\"" + file.getName(), fileBody);
insomniac
2
@insomniac Sí, acabo de enterarme de eso, también puedo usarMultiPartBody.Part
Bhargav
177

Hay una forma correcta de cargar un archivo con su nombre con Retrofit 2 , sin ningún hack :

Definir interfaz API:

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

Sube un archivo como este:

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);

Esto demuestra solo la carga de archivos, también puede agregar otros parámetros en el mismo método con @Partanotaciones.

jimmy0251
fuente
2
¿Cómo podemos enviar múltiples archivos usando MultipartBody.Part?
Praveen Sharma
Puede usar múltiples MultipartBody.Partargumentos en la misma API.
jimmy0251
Necesito enviar colecciones de imágenes con "imagen []" como clave. Lo intenté @Part("images[]") List<MultipartBody.Part> imagespero da error que@Part parameters using the MultipartBody.Part must not include a part name
Praveen Sharma
Debe usar @Body MultipartBody multipartBodyy MultipartBody.Builderpara enviar una colección de imágenes.
jimmy0251
2
¿Cómo puedo agregar clave a mutipart?
andro
23

Utilicé Retrofit 2.0 para mis usuarios registrados, envié una imagen de archivo multipart / form y texto desde la cuenta de registro

En mi RegisterActivity, use una 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) {..}

Y en mi clase Register.java es donde usar Retrofit con llamada sincrónica

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();
    }
}
}

En la interfaz de 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
);
}

Para el análisis de utilidades de la respuesta InputStream

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;
}
}
Samuel Ivan
fuente
16

Código de actualización para cargar archivos de imagen en 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);
}

Cambiar MediaType.parse("image/*")aMediaType.parse("image/jpeg")

RequestBody reqFile = RequestBody.create(MediaType.parse("image/jpeg"),
                                         file);
RequestBody email = RequestBody.create(MediaType.parse("text/plain"),
                                       "[email protected]");
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();

    }
});
Muhammad Waleed
fuente
Intenté muchas formas de hacerlo, pero no pude obtener los resultados. Acabo de cambiar esto ("Cambiar MediaType.parse (" image / * ") a MediaType.parse (" image / jpeg ")") como dijiste y funciona ahora, muchas gracias.
Gunnar
Ojalá pudiera darte más de un voto, gracias.
Rohit Maurya
si su API tiene @Multipartuna @Partanotación, debe proporcionar un nombre o usar el tipo de parámetro MultipartBody.Part.
Rohit
¡Buena solución! Y hay una cita más en @Part ("profile_pic \"; filename = \ "pp.png \" ", debe ser@Part("profile_pic\"; filename=\"pp.png "
Ninja
15

Agregando a la respuesta dada por @insomniac . Puede crear un Mappara poner el parámetro para RequestBodyincluir la imagen.

Código para la interfaz

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

Código para la clase Java

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();
 }
});
Anjana Sharma
fuente
¿Cómo puedo subir varios archivos con 2 cadenas?
Jay Dangar
¿Es posible que usted responda stackoverflow.com/questions/60428238/…
Ranjit
14

Entonces, es una forma muy simple de lograr su tarea. Debe seguir el siguiente paso: -

1. Primer paso

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

Necesita hacer toda la llamada como @Multipart request. itemy image numberes solo un cuerpo de cuerda que está envuelto RequestBody. Usamos el MultipartBody.Part classque nos permite enviar el nombre real del archivo además de los datos del archivo binario con la solicitud

2. Segundo paso

  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);

Ahora tiene image pathy necesita convertir en file. Ahora convierta fileen RequestBodyusar el método RequestBody.create(MediaType.parse("multipart/form-data"), file). Ahora lo que necesita para convertir su RequestBody requestFileen MultipartBody.Partel uso de método MultipartBody.Part.createFormData("Image", file.getName(), requestBody);.

ImageNumbery ItemIdes mi otra información que necesito enviar al servidor, así que también hago ambas cosas enRequestBody .

Para más información

duggu
fuente
3

Cargar archivos usando Retrofit es bastante simple. Necesita construir su interfaz api como

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);

}

en la imagen de código anterior está el nombre de la clave, por lo que si está utilizando php, escribirá $ _FILES ['image'] ['tmp_name'] para obtener esto. Y filename = "myfile.jpg" es el nombre del archivo que se envía con la solicitud.

Ahora para cargar el archivo necesita un método que le dará la ruta absoluta desde el 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;
}

Ahora puede usar el siguiente código para cargar su archivo.

 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();
            }
        });
    }

Para obtener una explicación más detallada, puede visitar este Tutorial de actualización de archivos de carga .

Belal Khan
fuente
Este es un truco, se ha solucionado en la actualización 2.0 por un tiempo. Ver jimmy0251 respuesta a continuación.
Matt Wolfe
1

Versión de Kotlin con actualización para depricación de RequestBody.create :

Interfaz de actualización

@Multipart
@POST("uploadPhoto")
fun uploadFile(@Part file: MultipartBody.Part): Call<FileResponse>

y subir

fun uploadFile(fileUrl: String){
    val file = File(fileUrl)
    val fileUploadService = RetrofitClientInstance.retrofitInstance.create(FileUploadService::class.java)
    val requestBody = file.asRequestBody(file.extension.toMediaTypeOrNull())
    val filePart = MultipartBody.Part.createFormData(
        "blob",file.name,requestBody
    )
    val call = fileUploadService.uploadFile(filePart)

    call.enqueue(object: Callback<FileResponse>{
        override fun onFailure(call: Call<FileResponse>, t: Throwable) {
            Log.d(TAG,"Fckd")
        }

        override fun onResponse(call: Call<FileResponse>, response: Response<FileResponse>) {
            Log.d(TAG,"success"+response.toString()+" "+response.body().toString()+"  "+response.body()?.status)
        }

    })
}

Gracias a @ jimmy0251

voz silenciada
fuente
0

No use múltiples parámetros en el nombre de la función, solo vaya con una simple convención de args que aumentará la legibilidad de los códigos , para esto puede hacer lo siguiente:

// MultipartBody.Part.createFormData("partName", data)
Call<SomReponse> methodName(@Part MultiPartBody.Part part);
// RequestBody.create(MediaType.get("text/plain"), data)
Call<SomReponse> methodName(@Part(value = "partName") RequestBody part); 
/* for single use or you can use by Part name with Request body */

// add multiple list of part as abstraction |ease of readability|
Call<SomReponse> methodName(@Part List<MultiPartBody.Part> parts); 
Call<SomReponse> methodName(@PartMap Map<String, RequestBody> parts);
// this way you will save the abstraction of multiple parts.

Puede haber múltiples excepciones que puede encontrar al usar Retrofit, todas las excepciones documentadas como código , tienen un tutorialretrofit2/RequestFactory.java . puede realizar dos funciones parseParameterAnnotationy, parseMethodAnnotationsi puede lanzar una excepción, continúe con esto, le ahorrará mucho tiempo que buscar en Google / stackoverflow

Nawab Khan
fuente
0

en kotlin es bastante fácil, usando métodos de extensiones de toMediaType , asRequestBody y toRequestBody aquí hay un ejemplo:

aquí estoy publicando un par de campos normales junto con un archivo pdf y un archivo de imagen usando multiparte

Esta es la declaración de API que utiliza la modificación:

    @Multipart
    @POST("api/Lesson/AddNewLesson")
    fun createLesson(
        @Part("userId") userId: RequestBody,
        @Part("LessonTitle") lessonTitle: RequestBody,
        @Part pdf: MultipartBody.Part,
        @Part imageFile: MultipartBody.Part
    ): Maybe<BaseResponse<String>>

y aquí está cómo llamarlo realmente:

api.createLesson(
            userId.toRequestBody("text/plain".toMediaType()),
            lessonTitle.toRequestBody("text/plain".toMediaType()),
            startFromRegister.toString().toRequestBody("text/plain".toMediaType()),
            MultipartBody.Part.createFormData(
                "jpeg",
                imageFile.name,
                imageFile.asRequestBody("image/*".toMediaType())
            ),
            MultipartBody.Part.createFormData(
                "pdf",
                pdfFile.name,
                pdfFile.asRequestBody("application/pdf".toMediaType())
            )
Amin Keshavarzian
fuente