5

How can I download a file(image/video) from my PHP server using Retrofit2 ?

I wasn't able to find any resources or tutorials online on how to proceed; I found this post that treats a certain download error on SO but it's not very clear to me. Could anyone point me to the right direction?

UPDATE:

Here is my code:

FileDownloadService.java

public interface FileDownloadService {
    @GET(Constants.UPLOADS_DIRECTORY + "/{filename}")
    @Streaming
    Call<ResponseBody> downloadRetrofit(@Path("filename") String fileName);
}

MainActivity.java (@Blackbelt's solution)

private void downloadFile(String filename) {
    FileDownloadService service = ServiceGenerator
            .createService(FileDownloadService.class, Constants.SERVER_IP_ADDRESS);
    Call<ResponseBody> call = service.downloadRetrofit("db90408a4bb1ee65d3e09d261494a49f.jpg");

    call.enqueue(new Callback<ResponseBody>() {
        @Override
        public void onResponse(final Response<ResponseBody> response, Retrofit retrofit) {
            try {
                InputStream is = response.body().byteStream();
                FileOutputStream fos = new FileOutputStream(
                        new File(Environment.getExternalStorageDirectory(), "image.jpg")
                );
                int read = 0;
                byte[] buffer = new byte[32768];
                while ((read = is.read(buffer)) > 0) {
                    fos.write(buffer, 0, read);
                }

                fos.close();
                is.close();
            } catch (Exception e) {
                Toast.makeText(MainActivity.this, "Exception: " + e.toString(), Toast.LENGTH_LONG).show();
            }
        }

        @Override
        public void onFailure(Throwable t) {
            Toast.makeText(MainActivity.this, "Failed to download file...", Toast.LENGTH_LONG).show();
        }
    });
}

I get a FileNotFoundException when USB debugging is active, & a NetworkOnMainThreadException when not.

MainActivity.java: (@Emanuel's solution)

private void downloadFile(String filename) {
    FileDownloadService service = ServiceGenerator
            .createService(FileDownloadService.class, Constants.SERVER_IP_ADDRESS);
    Call<ResponseBody> call = service.downloadRetrofit("db90408a4bb1ee65d3e09d261494a49f.jpg");

    call.enqueue(new Callback<ResponseBody>() {
        @Override
        public void onResponse(final Response<ResponseBody> response, Retrofit retrofit) {
            Log.i(TAG, "external storage = " + (Environment.getExternalStorageState() == null));
            Toast.makeText(MainActivity.this, "Downloading file... " + Environment.getExternalStorageDirectory(), Toast.LENGTH_LONG).show();

            File file = new File(Environment.getDataDirectory().toString() + "/aouf/image.jpg");
            try {
                file.createNewFile();
                Files.asByteSink(file).write(response.body().bytes());
            } catch (Exception e) {
                Toast.makeText(MainActivity.this,
                        "Exception: " + e.toString(),
                        Toast.LENGTH_LONG).show();
            }
        }

        @Override
        public void onFailure(Throwable t) {
            Toast.makeText(MainActivity.this, "Failed to download file...", Toast.LENGTH_LONG).show();
        }
    });
}

I get a FileNotFoundException.

Community
  • 1
  • 1
Mohammed Aouf Zouag
  • 17,042
  • 4
  • 41
  • 67
  • Try to find out the line call which produces the `NetworkOnMainThreadException` and call it from another thread. Or call the whole `downloadFile(...)` method from another thread: `new Thread(new Runnable() { public void run() { downloadFile("filename"); } }).start();` – Emanuel Seidinger Dec 09 '15 at 12:53

3 Answers3

6

This is a little example showing how to download the Retrofit JAR file. You can adapt it to your needs.

This is the interface:

import com.squareup.okhttp.ResponseBody;
import retrofit.Call;
import retrofit.http.GET;
import retrofit.http.Path;

interface RetrofitDownload {
    @GET("/maven2/com/squareup/retrofit/retrofit/2.0.0-beta2/{fileName}")
    Call<ResponseBody> downloadRetrofit(@Path("fileName") String fileName);
}

And this is a Java class using the interface:

import com.google.common.io.Files;
import com.squareup.okhttp.ResponseBody;
import retrofit.Call;
import retrofit.Callback;
import retrofit.Response;
import retrofit.Retrofit;

import java.io.File;
import java.io.IOException;

public class Main {

    public static void main(String... args) {
        Retrofit retrofit = new Retrofit.Builder().
                baseUrl("http://repo1.maven.org").
                build();

        RetrofitDownload retrofitDownload = retrofit.create(RetrofitDownload.class);

        Call<ResponseBody> call = retrofitDownload.downloadRetrofit("retrofit-2.0.0-beta2.jar");

        call.enqueue(new Callback<ResponseBody>() {
            @Override
            public void onResponse(Response<ResponseBody> response, Retrofit retrofitParam) {
                File file = new File("retrofit-2.0.0-beta2.jar");
                try {
                    file.createNewFile();
                    Files.asByteSink(file).write(response.body().bytes());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            @Override
            public void onFailure(Throwable t) {
            }
        });
    }
}
Emanuel Seidinger
  • 1,272
  • 1
  • 12
  • 21
  • `Files.asByteSink` is deprecated, what should I use instead ? – Mohammed Aouf Zouag Dec 08 '15 at 15:22
  • `Files.asByteSink` is from the Google Guava library and it is not deprecated. I just checked the API docs. You could use a `FileOutputStream` and pass the target file to the constructor. Then you write the byte array to the output stream via its `write (byte[] buffer, int byteOffset, int byteCount)` method. Use 0 as byteOffset and the array length as byteCount. – Emanuel Seidinger Dec 08 '15 at 15:42
  • My bad, I downloaded an old version (12.0). I'll upgrade to the new one (18.0), thanks. – Mohammed Aouf Zouag Dec 08 '15 at 15:44
  • Same case as with @Blackbelt's solution: a **FileNotFoundException** when USB debugging is active, & a **NetworkOnMainThreadException** when not. – Mohammed Aouf Zouag Dec 08 '15 at 19:53
3

If anybody stumbles upon this response this is how i did it using retrofit in conjunction with Rx. Every downloaded file is cached, and any subsequent requests with the same url will return the already downloaded file.

In order to use this just subscribe to this observable and pass your url. This will save your file in downloads directory so make sure to ask for permissions if your app targets API 23 or greater.

  public Observable<File> getFile(final String filepath) {
    URL url = null;
    try {
        url = new URL(filepath);
    } catch (MalformedURLException e) {
        e.printStackTrace();
    }
    final String name = url.getPath().substring(url.getPath().lastIndexOf("/") + 1);
    final File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), name);
    if (file.exists()) {
        return Observable.just(file);
    } else {
        return mRemoteService.getFile(filepath).flatMap(new Func1<Response<ResponseBody>, Observable<File>>() {
            @Override
            public Observable<File> call(final Response<ResponseBody> responseBodyResponse) {
                return Observable.create(new Observable.OnSubscribe<File>() {
                    @Override
                    public void call(Subscriber<? super File> subscriber) {
                        try {

                            final File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsoluteFile(), name);

                            BufferedSink sink = Okio.buffer(Okio.sink(file));
                            sink.writeAll(responseBodyResponse.body().source());
                            sink.flush();
                            sink.close();
                            subscriber.onNext(file);
                            subscriber.onCompleted();
                            file.deleteOnExit();
                        } catch (IOException e) {
                            Timber.e("Save pdf failed with error %s", e.getMessage());
                            subscriber.onError(e);
                        }
                    }
                });
            }
        });
    }
}

Retrofit part of the call

@Streaming
@GET
Observable<retrofit2.Response<ResponseBody>> getFile(@Url String fileUrl);
Ivan Milisavljevic
  • 2,048
  • 2
  • 13
  • 21
2

to download a file, you might want the raw InputStream of the response and write is content on the sdcard. To do so, you should use ResponseBody as T for your return type, Call<ResponseBody>. You will then use Retrofit to enqueue a

Callback<ResponseBody>

and when the onResponse

@Override
public void onResponse(final Response<ResponseBody> response, Retrofit retrofit) {

is invoked, you can retrieve the InputStream, with response.byteStream(), read from it, and write what you read on the sdcard (have a look here)

Community
  • 1
  • 1
Blackbelt
  • 156,034
  • 29
  • 297
  • 305
  • I proceeded your way, but I got stuck : I get a **FileNotFoundException** when USB debugging is active, & a **NetworkOnMainThreadException** when not. But I can't see where I messed up. One more thing : Downloads can only be saved to external storage ? If yes, why ? *(I will update my post with the code I wrote)* – Mohammed Aouf Zouag Dec 08 '15 at 19:29