Dados de formulário multipartes do POST usando Retrofit 2.0, incluindo imagem

148

Estou tentando fazer um HTTP POST para o servidor usando o 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();

O servidor retorna um erro dizendo que o arquivo não é válido.

Isso é estranho, porque tentei carregar o mesmo arquivo com o mesmo formato no iOS (usando outra biblioteca), mas ele é carregado com êxito.

Pergunto-me qual é a maneira correta de carregar uma imagem usando o Retrofit 2.0 ?

Devo salvá-lo no disco antes de fazer o upload?

PS: Eu usei o retrofit para outra solicitação Multipart que não inclui imagem e eles foram concluídos com êxito. O problema é quando estou tentando incluir um byte no corpo.

JayVDiyk
fonte

Respostas:

180

Estou destacando a solução em 1.9 e 2.0, pois é útil para alguns

Em 1.9, acho que a melhor solução é salvar o arquivo em disco e usá-lo como arquivo digitado, como:

RetroFit 1.9

(Eu não sei sobre a implementação do servidor) tem um método de interface API semelhante a este

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

E use-o como

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

Para RetroFit 2 Use o seguinte método

RetroFit 2.0 (esta foi uma solução alternativa para um problema no RetroFit 2 que foi corrigido agora, para o método correto, consulte a resposta de jimmy0251 )

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

Use-o 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();
    }
});
insone
fonte
5
Sim, bem, eu acho que é um problema ( github.com/square/retrofit/issues/1063 ) com retrofit 2.0, você pode querer ficar com 1,9
insone
2
ver a minha edição, eu não tentei ainda, você são recebidos
insone
1
Fiz upload com sucesso de uma imagem usando o exemplo do Retrofit 2.0.
Jerogaren
3
@Bhargav Você pode mudar a interface para @Multipart @POST("/api/Accounts/editaccount") Call<User> editUser(@PartMap Map<String, RequestBody> params);e quando você tem o arquivo: Map<String, RequestBody> map = new HashMap<>(); RequestBody fileBody = RequestBody.create(MediaType.parse("image/jpg"), file); map.put("file\"; filename=\"" + file.getName(), fileBody);
insone
2
@insomniac Sim, eu só descobri sobre isso, também pode usarMultiPartBody.Part
Bhargav
177

Existe uma maneira correta de fazer upload de um arquivo com seu nome no Retrofit 2 , sem qualquer invasão :

Defina a interface da API:

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

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

Isso demonstra apenas o upload de arquivos. Você também pode adicionar outros parâmetros no mesmo método com @Partanotação.

jimmy0251
fonte
2
como podemos enviar vários arquivos usando o MultipartBody.Part?
Praveen Sharma
Você pode usar vários MultipartBody.Partargumentos na mesma API.
precisa saber é o seguinte
Preciso enviar coleções de imagens com "image []" como chave. Eu tentei, @Part("images[]") List<MultipartBody.Part> imagesmas dá erro que #@Part parameters using the MultipartBody.Part must not include a part name
Praveen Sharma
Você deve usar @Body MultipartBody multipartBodye MultipartBody.Builderpara enviar a coleção de imagens.
precisa saber é o seguinte
2
Como posso adicionado chave para mutipart
andro
23

Usei o Retrofit 2.0 para meus usuários cadastrados, enviei imagens / texto de arquivo com várias partes / formulário da conta registrada

Em meu RegisterActivity, use um 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) {..}

E na minha classe Register.java é onde usar Retrofit com chamada síncrona

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

Na interface do 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 a análise Utilities 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;
}
}
Samuel Ivan
fonte
16

Código de atualização para upload de arquivo de imagem no 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);
}

Mude MediaType.parse("image/*")paraMediaType.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
fonte
Eu estava tentando várias maneiras de fazer isso, mas não consegui os resultados. Acabei de alterar isso ("Alterar MediaType.parse (" image / * ") para MediaType.parse (" image / jpeg ")") como você disse e funciona agora, muito obrigado.
Gunnar
gostaria de poder lhe dar mais de um voto, obrigado.
Rohit Maurya
se o seu api tem @Multipartentão @Parta anotação deve fornecer um nome ou usar MultipartBody.Part tipo de parâmetro.
Rohit
Boa solução! E há mais uma citação em @Part ("profile_pic \"; filename = \ "pp.png \" ", ela deve ser@Part("profile_pic\"; filename=\"pp.png "
Ninja
15

Adicionando à resposta dada por @insomniac . Você pode criar um Mappara colocar o parâmetro para RequestBodyincluir a imagem.

Código para Interface

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

Classe Code for 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
fonte
como posso carregar vários arquivos com 2 strings?
Jay Dangar
É possível que você responda stackoverflow.com/questions/60428238/…
Ranjit
14

Portanto, é uma maneira muito simples de realizar sua tarefa. Você precisa seguir a etapa abaixo: -

1. Primeiro passo

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

Você precisa fazer a ligação inteira como @Multipart request. iteme image numberé apenas o corpo da corda que está envolvido RequestBody. Usamos o MultipartBody.Part classque nos permite enviar o nome do arquivo real além dos dados do arquivo binário com a solicitação

2. Segundo passo

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

Agora você tem image pathe você precisa converter em file.Agora convertido fileem RequestBodyutilizando o método RequestBody.create(MediaType.parse("multipart/form-data"), file). Agora você precisa converter seu RequestBody requestFilepara MultipartBody.Partusando o método MultipartBody.Part.createFormData("Image", file.getName(), requestBody);.

ImageNumbere ItemIdsão meus outros dados que preciso enviar para o servidor, para que eu também faça as duas coisas RequestBody.

Para mais informações

duggu
fonte
3

Carregar arquivos usando o Retrofit é bastante simples Você precisa criar sua interface 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);

}

no código acima, a imagem é o nome da chave; se você estiver usando o php, escreverá $ _FILES ['image'] ['tmp_name'] para obter isso. E filename = "myfile.jpg" é o nome do seu arquivo que está sendo enviado com a solicitação.

Agora, para fazer o upload do arquivo, você precisa de um método que lhe forneça o caminho absoluto do 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;
}

Agora você pode usar o código abaixo para fazer upload de seu arquivo.

 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 obter explicações mais detalhadas, você pode visitar este Tutorial de atualização de arquivo de upload .

Belal Khan
fonte
Este é um truque, foi corrigido no retrofit 2.0 por algum tempo. Veja a resposta jimmy0251 abaixo.
Matt Wolfe
1

Versão Kotlin com atualização para privação de RequestBody.create:

Interface de modernização

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

e fazer upload

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

    })
}

Obrigado a @ jimmy0251

hushed_voice
fonte
0

Não use vários parâmetros no nome da função, basta seguir uma convenção simples de alguns argumentos que aumentará a legibilidade dos códigos , pois você pode fazer o seguinte:

// 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.

Pode haver várias exceções que você pode encontrar ao usar o Retrofit, todas as exceções documentadas como código , com uma explicação passo a passoretrofit2/RequestFactory.java . você pode executar duas funções parseParameterAnnotatione parseMethodAnnotationonde você pode executar exceções, por favor, faça isso, economizará muito tempo do que pesquisar no google / stackoverflow

Nawab Khan
fonte
0

no kotlin é bastante fácil, usando métodos de extensões toMediaType , asRequestBody e toRequestBody, aqui está um exemplo:

aqui estou postando alguns campos normais, juntamente com um arquivo pdf e um arquivo de imagem usando multipartes

esta é a declaração da API usando retrofit:

    @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>>

e aqui está como realmente chamá-lo:

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
fonte