32

I'm trying to down/upload a file with retrofit 2 but can't find any tutorials examples on how to do so. My code for downloading is:

@GET("documents/checkout")
public Call<File> checkout(@Query(value = "documentUrl") String documentUrl, @Query(value = "accessToken") String accessToken, @Query(value = "readOnly") boolean readOnly);

and

Call<File> call = RetrofitSingleton.getInstance(serverAddress)
                .checkout(document.getContentUrl(), apiToken, readOnly[i]);
call.enqueue(new Callback<File>() {
        @Override
        public void onResponse(Response<File> response,
                Retrofit retrofit) {
            String fileName = document.getFileName();
            try {
                System.out.println(response.body());
                long fileLength = response.body().length();
                InputStream input = new FileInputStream(response.body());
                File path = Environment.getExternalStorageDirectory();
                File file = new File(path, fileName);
                BufferedOutputStream output = new BufferedOutputStream(
                        new FileOutputStream(file));
                byte data[] = new byte[1024];

                long total = 0;
                int count;
                while ((count = input.read(data)) != -1) {
                    total += count;
                    output.write(data, 0, count);
                }

                output.flush();

                output.close();
            } catch (IOException e) {
                String logTag = "TEMPTAG";
                Log.e(logTag, "Error while writing file!");
                Log.e(logTag, e.toString());
            }
        }
        @Override
        public void onFailure(Throwable t) {
            // TODO: Error handling
            System.out.println(t.toString());
        }
    });

I've tried with Call and Call but nothing seems to work.

The server-side code writes the file's bytes into HttpServletResponse's output stream after setting the headers and mime type correctly.

What am I doing wrong?

Finally, the upload code:

@Multipart
@POST("documents/checkin")
public Call<String> checkin(@Query(value = "documentId") String documentId, @Query(value = "name") String fileName, @Query(value = "accessToken") String accessToken, @Part("file") RequestBody file);

and

RequestBody requestBody = RequestBody.create(MediaType.parse(document.getMimeType()), file);

            Call<String> call = RetrofitSingleton.getInstance(serverAddress).checkin(documentId, document.getFileName(), apiToken, requestBody);
            call.enqueue(new Callback<String>() {
                @Override
                public void onResponse(Response<String> response, Retrofit retrofit) {
                    System.out.println(response.body());
                }

                @Override
                public void onFailure(Throwable t) {
                    System.out.println(t.toString());
                }
            });

Thanks!

Edit:

After the answer, downloading only yields a corrupted file (without the @Streaming), uploading doesn't as well. When I use the above code, the server returns a 400 error. After changing it to

RequestBody requestBody = RequestBody.create(MediaType.parse(document.getMimeType()), file);
            MultipartBuilder multipartBuilder = new MultipartBuilder();
            multipartBuilder.addFormDataPart("file", document.getFileName(), requestBody);

            Call<String> call = RetrofitSingleton.getInstance(serverAddress).checkin(documentId, document.getFileName(), apiToken, multipartBuilder.build());

, the request executes but the backend doesn't seem to receive a file.

Backend code:

@RequestMapping(value = "/documents/checkin", method = RequestMethod.POST)
public void checkInDocument(@RequestParam String documentId,
        @RequestParam String name, @RequestParam MultipartFile file,
        @RequestParam String accessToken, HttpServletResponse response)

What am I doing wrong? I was able to use the backend from plain Java with the Apache HttpClient:

    MultipartEntityBuilder builder = MultipartEntityBuilder.create();
    builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
    builder.addBinaryBody("file", new File("E:\\temp\\test.jpg"));
    HttpEntity httpEntity = builder.build();
    System.out.println("HttpEntity " + EntityUtils.toString(httpEntity.));
    HttpPost httpPost = new HttpPost(uri);
    httpPost.setEntity(httpEntity);

Edit v2

For anyone interested, both up- and downloading work now: These are the solutions:

Service:

@GET("documents/checkout")
public Call<ResponseBody> checkout(@Query(value = "documentUrl") String documentUrl, @Query(value = "accessToken") String accessToken, @Query(value = "readOnly") boolean readOnly);

@Multipart
@POST("documents/checkin")
public Call<String> checkin(@Query(value = "documentId") String documentId, @Query(value = "name") String fileName, @Query(value = "accessToken") String accessToken, @Part("file") RequestBody file);

Download Code:

    Call<ResponseBody> call = RetrofitSingleton.getInstance(serverAddress)
                .checkout(document.getContentUrl(), apiToken, readOnly[i]);
        call.enqueue(new Callback<ResponseBody>() {
            @Override
            public void onResponse(Response<ResponseBody> response,
                    Retrofit retrofit) {
                String fileName = document.getFileName();

                try {
                    File path = Environment.getExternalStorageDirectory();
                    File file = new File(path, fileName);
                    FileOutputStream fileOutputStream = new FileOutputStream(file);
                    IOUtils.write(response.body().bytes(), fileOutputStream);
                } catch (IOException e) {
                    Log.e(logTag, "Error while writing file!");
                    Log.e(logTag, e.toString());
                }
            }

            @Override
            public void onFailure(Throwable t) {
                // TODO: Error handling
                System.out.println(t.toString());
            }
        });

Upload Code:

    Call<String> call = RetrofitSingleton
                    .getInstance(serverAddress).checkin(documentId,
                            document.getFileName(), apiToken,
                            multipartBuilder.build());
            call.enqueue(new Callback<String>() {
                @Override
                public void onResponse(Response<String> response,
                        Retrofit retrofit) {
                    // Handle response here
                }

                @Override
                public void onFailure(Throwable t) {
                    // TODO: Error handling
                    System.out.println("Error");
                    System.out.println(t.toString());
                }
            });
N4zroth
  • 1,334
  • 2
  • 10
  • 25
  • what log messages are you getting. you can set the log level `restAdapter.setLogLevel(LogLevel.FULL);` – Eoin Sep 30 '15 at 03:08
  • After adding logging via Retrofit.client().interceptors().add, the problem seems to be that content-length is always 0 but I have no idea why, the file exists on the file system. – N4zroth Sep 30 '15 at 13:52
  • Could you update with the Full Download Method in Spring Server please? Thank you – Alberto Crespo Oct 14 '16 at 18:37
  • I am glad we have this code/functionality, but are we even supposed to use retrofit to upload and download files? In case of uploads, what I have observed is retrofit reads the whole file into the memory before sending into the server, now if a user chooses a large file, then on Android it is very easy to hit the max heap allowed for the application. Has anyone faced this issue ? Are there other ways to do uploads ? – Nitesh Mudireddy Dec 29 '16 at 21:25

5 Answers5

28

For downloading, you can use ResponseBody as your return type --

@GET("documents/checkout")
@Streaming
public Call<ResponseBody> checkout(@Query("documentUrl") String documentUrl, @Query("accessToken") String accessToken, @Query("readOnly") boolean readOnly);

and you can get the ResponseBody input stream in your call back --

Call<ResponseBody> call = RetrofitSingleton.getInstance(serverAddress)
            .checkout(document.getContentUrl(), apiToken, readOnly[i]);

call.enqueue(new Callback<ResponseBody>() {
        @Override
        public void onResponse(Response<ResponseBody> response,
                Retrofit retrofit) {
            String fileName = document.getFileName();
            try {
                InputStream input = response.body().byteStream();
                //  rest of your code

Your upload looks okay at first glance if you server handles multipart messages correctly. Is it working? If not, can you explain the failure mode? You also might be able to simplify by not making it multipart. Remove the @Multipart annotation and convert @Path to @Body --

@POST("documents/checkin")
public Call<String> checkin(@Query("documentId") String documentId, @Query("name") String fileName, @Query("accessToken") String accessToken, @Body RequestBody file);
Baloomba
  • 169
  • 12
iagreen
  • 31,470
  • 8
  • 76
  • 90
  • Thanks, downloading works now, only uploading doesn't - see edit for problem description :-) – N4zroth Sep 30 '15 at 11:10
  • @iagreen, can you look at this problem? I need help with it. https://stackoverflow.com/questions/53343332/send-image-file-via-retrofit-from-android-to-spring/53343958?noredirect=1#comment93567370_53343958 – Avi Patel Nov 16 '18 at 19:41
5

I am using retrofit 2.0.0-beta2 and I had an issue uploading image by using multipart request. I solved it by using this answer: https://stackoverflow.com/a/32796626/2915075

The key for me was to use standard POST with MultipartRequestBody instead of @Multipart annotated request.

Here is my code:

Retrofit service class

@POST("photo")
Call<JsonElement> uploadPhoto(@Body RequestBody imageFile, @Query("sessionId"));

Usage from activity, fragment:

RequestBody fileBody = RequestBody.create(MediaType.parse("image/jpeg"), imageFile);
MultipartBuilder multipartBuilder = new MultipartBuilder();
multipartBuilder.addFormDataPart("photo", imageFile.getName(), fileBody);
RequestBody fileRequestBody = multipartBuilder.build();

//call
mRestClient.getRetrofitService().uploadProfilePhoto(fileRequestBody, sessionId);
Community
  • 1
  • 1
1

i have the same problems, and i found a solution to upload files, that described here Is it possible to show progress bar when upload image via Retrofit 2

Community
  • 1
  • 1
Yuriy Kolbasinskiy
  • 3,791
  • 3
  • 16
  • 33
1

Also I had this problem, This is how i try to solve my problem (RETROFIT 2 )

 //1. What We Need From Server ( upload.php Script )
    public class FromServer {
        String result;
    }

    //2. Which Interface To Communicate Our upload.php Script?
    public interface ServerAPI {

        @Multipart
        @POST("upload.php")//Our Destination PHP Script
        Call<List<FromServer>> upload(
                @Part("file_name") String file_name,
                @Part("file") RequestBody description);

         Retrofit retrofit =
                new Retrofit.Builder()
                        .baseUrl("http://192.168.43.135/retro/") // REMEMBER TO END with /
                        .addConverterFactory(GsonConverterFactory.create())
                 .build();
    }


    //3. How To Upload
    private void upload(){

            ServerAPI api = ServerAPI.retrofit.create(ServerAPI.class);

            File from_phone = FileUtils.getFile(Environment.getExternalStorageDirectory()+"/aa.jpg"); //org.apache.commons.io.FileUtils
            RequestBody to_server = RequestBody.create(MediaType.parse("multipart/form-data"), from_phone);

            api.upload(from_phone.getName(),to_server).enqueue(new Callback<List<FromServer>>() {
                @Override
                public void onResponse(Call<List<FromServer>> call, Response<List<FromServer>> response) {
                    Toast.makeText(MainActivity.this, response.body().get(0).result, Toast.LENGTH_SHORT).show();
                }
                @Override
                public void onFailure(Call<List<FromServer>> call, Throwable t) { }
            });


         }

//4. upload.php
<?php

    $pic = $_POST['file_name'];

    $pic = str_replace("\"", "", $pic); //REMOVE " from file name
    if(file_exists($pic)){unlink($pic);}

    $f = fopen($pic, "w");
    fwrite($f,$_POST['file']);
    fclose($f);

    $arr[] = array("result"=>"Done");
    print(json_encode($arr));
?>
ugali soft
  • 2,719
  • 28
  • 25
1

You can refer tutorial for Image Download using Retrofit 2.0

For the time being you can refer following functions for image download:

void getRetrofitImage() {

    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl(url)
            .addConverterFactory(GsonConverterFactory.create())
            .build();

    RetrofitImageAPI service = retrofit.create(RetrofitImageAPI.class);

    Call<ResponseBody> call = service.getImageDetails();

    call.enqueue(new Callback<ResponseBody>() {
        @Override
        public void onResponse(Response<ResponseBody> response, Retrofit retrofit) {

            try {

                Log.d("onResponse", "Response came from server");

                boolean FileDownloaded = DownloadImage(response.body());

                Log.d("onResponse", "Image is downloaded and saved ? " + FileDownloaded);

            } catch (Exception e) {
                Log.d("onResponse", "There is an error");
                e.printStackTrace();
            }

        }

        @Override
        public void onFailure(Throwable t) {
            Log.d("onFailure", t.toString());
        }
    });
}

Following is the file handling part image download using Retrofit 2.0

private boolean DownloadImage(ResponseBody body) {

    try {
        Log.d("DownloadImage", "Reading and writing file");
        InputStream in = null;
        FileOutputStream out = null;

        try {
            in = body.byteStream();
            out = new FileOutputStream(getExternalFilesDir(null) + File.separator + "AndroidTutorialPoint.jpg");
            int c;

            while ((c = in.read()) != -1) {
                out.write(c);
            }
        }
        catch (IOException e) {
            Log.d("DownloadImage",e.toString());
            return false;
        }
        finally {
            if (in != null) {
                in.close();
            }
            if (out != null) {
                out.close();
            }
        }

        int width, height;
        ImageView image = (ImageView) findViewById(R.id.imageViewId);
        Bitmap bMap = BitmapFactory.decodeFile(getExternalFilesDir(null) + File.separator + "AndroidTutorialPoint.jpg");
        width = 2*bMap.getWidth();
        height = 6*bMap.getHeight();
        Bitmap bMap2 = Bitmap.createScaledBitmap(bMap, width, height, false);
        image.setImageBitmap(bMap2);

        return true;

    } catch (IOException e) {
        Log.d("DownloadImage",e.toString());
        return false;
    }
}

I hope it will help. All the best. Happy Coding :)

Navneet Goel
  • 116
  • 6