POST Multipart Form Data à l'aide de Retrofit 2.0, y compris l'image

149

J'essaie de faire un HTTP POST sur le serveur en utilisant 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();

Le serveur renvoie une erreur indiquant que le fichier n'est pas valide.

C'est bizarre parce que j'ai essayé de télécharger le même fichier avec le même format sur iOS (en utilisant une autre bibliothèque), mais il est téléchargé avec succès.

Je me demande quelle est la bonne façon de télécharger une image à l'aide de Retrofit 2.0 ?

Dois-je l'enregistrer sur le disque avant de le télécharger?

PS: J'ai utilisé la modernisation pour d'autres demandes Multipart qui n'incluent pas d'image et elles se sont terminées avec succès. Le problème, c'est lorsque j'essaie d'inclure un octet dans le corps.

JayVDiyk
la source

Réponses:

181

Je mets en évidence la solution dans les versions 1.9 et 2.0 car elle est utile pour certains

En 1.9, je pense que la meilleure solution consiste à enregistrer le fichier sur le disque et à l'utiliser comme fichier typé comme:

RetroFit 1.9

(Je ne connais pas votre implémentation côté serveur) ont une méthode d'interface API similaire à celle-ci

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

Et utilisez-le comme

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

Pour RetroFit 2 Utilisez la méthode suivante

RetroFit 2.0 (C'était une solution de contournement pour un problème dans RetroFit 2 qui est maintenant corrigé, pour la méthode correcte, reportez- vous à la réponse de jimmy0251 )

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

Utilisez-le comme:

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();
    }
});
insomniaque
la source
5
Oui, eh bien, je pense que c'est un problème ( github.com/square/retrofit/issues/1063 ) avec la modernisation 2.0, vous voudrez peut-être vous en tenir à 1.9
insomniac
2
voir mon montage, je ne l'ai pas encore essayé, vous êtes le bienvenu
insomniac
1
J'ai téléchargé avec succès une image en utilisant l'exemple de Retrofit 2.0.
jerogaren
3
@Bhargav Vous pouvez changer l'interface en @Multipart @POST("/api/Accounts/editaccount") Call<User> editUser(@PartMap Map<String, RequestBody> params);And When you have the file: 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 Oui, je viens de le découvrir, je peux aussi utiliserMultiPartBody.Part
Bhargav
177

Il existe une manière correcte de télécharger un fichier avec son nom avec Retrofit 2 , sans aucun hack :

Définissez l'interface API:

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

Téléchargez un fichier comme celui-ci:

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

Cela ne montre que le téléchargement de fichiers, vous pouvez également ajouter d'autres paramètres dans la même méthode avec @Partannotation.

jimmy0251
la source
2
comment pouvons-nous envoyer plusieurs fichiers en utilisant MultipartBody.Part?
Praveen Sharma
Vous pouvez utiliser plusieurs MultipartBody.Partarguments dans la même API.
jimmy0251
J'ai besoin d'envoyer des collections d'images avec "image []" comme clé. J'ai essayé @Part("images[]") List<MultipartBody.Part> imagesmais cela donne une erreur@Part parameters using the MultipartBody.Part must not include a part name
Praveen Sharma
Vous devez utiliser @Body MultipartBody multipartBodyet MultipartBody.Builderpour envoyer une collection d'images.
jimmy0251
2
comment puis-je ajouter la clé à mutipart
andro
23

J'ai utilisé Retrofit 2.0 pour mes utilisateurs de registre, envoyer une image de fichier en plusieurs parties / formulaire et du texte à partir du compte de registre

Dans mon RegisterActivity, utilisez une 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) {..}

Et dans ma classe Register.java, c'est où utiliser Retrofit avec appel synchrone

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

Dans l'interface 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
);
}

Pour l'analyse des utilitaires de la réponse 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
la source
16

Code de mise à jour pour le téléchargement du fichier image dans 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);
}

Changer MediaType.parse("image/*")pourMediaType.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
la source
J'essayais de nombreuses façons de le faire mais je n'ai pas pu obtenir les résultats. Je viens de changer cela ("Changer MediaType.parse (" image / * ") en MediaType.parse (" image / jpeg ")") comme vous l'avez dit et cela fonctionne maintenant, merci beaucoup.
Gunnar
J'aimerais pouvoir vous donner plus d'un vote, merci.
Rohit Maurya
si votre API a, @Multipartalors l' @Partannotation doit fournir un nom ou utiliser le type de paramètre MultipartBody.Part.
Rohit
Bonne solution! Et il y a une autre citation dans @Part ("profile_pic \"; filename = \ "pp.png \" ", ça doit être@Part("profile_pic\"; filename=\"pp.png "
Ninja
15

Ajout à la réponse donnée par @insomniac . Vous pouvez créer un Mappour mettre le paramètre pour RequestBodyinclure l'image.

Code pour l'interface

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

Code pour la classe 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
la source
comment puis-je télécharger plusieurs fichiers avec 2 chaînes?
Jay Dangar
Est-il possible pour vous de répondre à stackoverflow.com/questions/60428238/…
Ranjit le
14

C'est donc une manière très simple d'accomplir votre tâche. Vous devez suivre l'étape ci-dessous: -

1. Première étape

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

Vous devez passer l'appel entier en tant que @Multipart request. itemet image numberest juste un corps de chaîne qui est enveloppé RequestBody. Nous utilisons le MultipartBody.Part classqui nous permet d'envoyer le nom de fichier réel en plus des données du fichier binaire avec la demande

2. Deuxième étape

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

Maintenant vous avez image pathet vous devez convertir en file.Maintenant convertir fileen RequestBodyméthode using RequestBody.create(MediaType.parse("multipart/form-data"), file). Vous devez maintenant convertir votre méthode RequestBody requestFileen MultipartBody.Partutilisant MultipartBody.Part.createFormData("Image", file.getName(), requestBody);.

ImageNumberet ItemIdsont mes autres données que je dois envoyer au serveur afin que je fasse également les deux choses RequestBody.

Pour plus d'informations

duggu
la source
3

Le téléchargement de fichiers à l'aide de la mise à niveau est assez simple Vous devez créer votre interface API comme

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

}

dans l' image de code ci-dessus est le nom de la clé donc si vous utilisez php, vous écrirez $ _FILES ['image'] ['tmp_name'] pour l'obtenir. Et filename = "monfichier.jpg" est le nom de votre fichier qui est envoyé avec la demande.

Maintenant, pour télécharger le fichier, vous avez besoin d'une méthode qui vous donnera le chemin absolu de l'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;
}

Vous pouvez maintenant utiliser le code ci-dessous pour télécharger votre fichier.

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

Pour des explications plus détaillées, vous pouvez visiter ce didacticiel de téléchargement de fichiers de mise à niveau .

Belal Khan
la source
C'est un hack, il a été corrigé dans la mise à niveau 2.0 pendant un certain temps. Voir la réponse jimmy0251 ci-dessous.
Matt Wolfe
1

Version Kotlin avec mise à jour pour dépréciation de RequestBody.create:

Interface de modernisation

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

et pour télécharger

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

    })
}

Merci à @ jimmy0251

hushed_voice
la source
0

N'utilisez pas plusieurs paramètres dans le nom de la fonction, utilisez simplement une simple convention d'arguments qui augmentera la lisibilité des codes , pour cela, vous pouvez faire comme -

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

Il peut y avoir plusieurs exceptions que vous pouvez rencontrer lors de l'utilisation de Retrofit, toutes les exceptions documentées sous forme de code , ont une procédure pas à pas pourretrofit2/RequestFactory.java . vous pouvez utiliser deux fonctions parseParameterAnnotationet parseMethodAnnotationoù vous pouvez faire une exception lancée, veuillez passer par ceci, cela vous fera gagner beaucoup de temps que googler / stackoverflow

Nawab Khan
la source
0

dans kotlin c'est assez simple, en utilisant les méthodes d'extensions de toMediaType , asRequestBody et toRequestBody, voici un exemple:

ici, je poste quelques champs normaux avec un fichier pdf et un fichier image en utilisant multipart

il s'agit d'une déclaration d'API utilisant la modernisation:

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

et voici comment l'appeler:

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
la source