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.
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) {
}
});
}
}
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);
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)
Related
I have an app the stores photos with Room in the database I store a image in a byte[].
I have an activity where when I press the button upload the images through Retrofit but when I start to upload, the app throws an error.
Here is the code of the function to upload the images;
for (int i = 0; i < evidences.size(); i++) {
order = evidences.get(i).getMobmx();
ticketEv = evidences.get(i).getTicketEv();
Toast.makeText(this, "MOBMX: " + order, Toast.LENGTH_SHORT).show();
File filesDir = this.getFilesDir();
File photo = new File(filesDir, "" + order + ".jpeg");
FileOutputStream fos;
try {
fos = new FileOutputStream(photo);
fos.write(ticketEv);
fos.flush();
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
RequestBody requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), photo);
MultipartBody.Part body = MultipartBody.Part.createFormData("image", photo.getName(), requestFile);
apiService.uploadPod(getString(R.string.uploadPodOfflinePetition), body, RequestBody.create(MediaType.parse("text/plain"), order))
.enqueue(new Callback<UserData>() {
#Override
public void onResponse(Call<UserData> call, Response<UserData> response) {
if (response.isSuccessful()) {
if (response.body() != null) {
try {
processResult(response.body().getMsg(), response.body().getData());
} catch (Exception e) {
Toast.makeText(OnlineEvidenceActivity.this, "Error procesando la respuesta del servidor, por favor intente de nuevo", Toast.LENGTH_SHORT).show();
}
}
} else {
try {
String erroResp = response.errorBody().string();
Log.i("ERROR", erroResp);
processError(erroResp);
} catch (IOException | JSONException e) {
e.printStackTrace();
Toast.makeText(OnlineEvidenceActivity.this, "Error al procesar respuesta", Toast.LENGTH_SHORT).show();
}
}
}
#Override
public void onFailure(Call<UserData> call, Throwable t) {
//RequestFailHandler.handleFail(OnlineEvidenceActivity.this, t);
Log.e("ERROREVIDENCIA", t.getMessage());
}
});
}
Here is the function when the response is successful:
private void processResult(String msg, Object data) {
StatusPODS statusPODS = new StatusPODS();
if (msg.equals("ok")) {
Toast.makeText(this, "Procesar evidencia", Toast.LENGTH_SHORT).show();
statusPODS.setMobmx(order);
statusPODS.setStatus("Exitoso");
evidenceDAO.insertStatus(statusPODS);
//evidenceDAO.deleteRow(order);
} else if (msg.equals("warning")) {
Toast.makeText(this, "" + data, Toast.LENGTH_SHORT).show();
statusPODS.setMobmx(order);
statusPODS.setStatus("Warning");
evidenceDAO.insertStatus(statusPODS);
//evidenceDAO.deleteRow(order);
} else {
//Error
Toast.makeText(this, "" + data, Toast.LENGTH_SHORT).show();
statusPODS.setMobmx(order);
statusPODS.setStatus("Error");
evidenceDAO.insertStatus(statusPODS);
//evidenceDAO.deleteRow(order);
}
}
This is the Service endpoint:
#Multipart
#POST("mobile/uploadpodoffline")
Call<UserData> uploadPod(#Header("petition") String petition,
#Part MultipartBody.Part image,
#Part ("order") RequestBody order);
And finally this is the error when upload the first image:
HTTP FAILED: java.net.ProtocolException: expected 422764 bytes but received 425984
Any solution to upload the images creating a new File and send through the for loop with retrofit (?)
Thanks
I'm working on an android app that needs to save a pdf file from an api. So I had to extend Request volley class to a ByteArray class:
package br.com.tarcisojunior.myapp;
import android.support.annotation.NonNull;
import com.android.volley.NetworkResponse;
import com.android.volley.Request;
import com.android.volley.Response;
import com.android.volley.toolbox.HttpHeaderParser;
/**
* Created by tarcisojunior on 18/04/18.
*/
public class ByteArrayRequest extends Request<byte[]> {
private final Response.Listener<byte[]> mListener;
public ByteArrayRequest(String url, Response.Listener<byte[]> listener,
Response.ErrorListener errorListener) {
this(Method.GET, url, listener, errorListener);
}
public ByteArrayRequest(int method, String url, Response.Listener<byte[]> listener, Response.ErrorListener errorListener) {
super(method, url, errorListener);
mListener = listener;
}
#Override
protected Response parseNetworkResponse(NetworkResponse response) {
return Response.success(response.data, HttpHeaderParser.parseCacheHeaders(response));
}
#Override
protected void deliverResponse(byte[] response) {
if(null != mListener){
mListener.onResponse(response);
}
}
#Override
public String getBodyContentType() {
return "application/octet-stream";
}
}
I my Activity i'm calling ByteArrayRequest to perform an api request:
private void getCarnePDF(final int empreendimento,final int coligada,final String quadra,final String lote){
RequestQueue requestQueue;
ByteArrayRequest request;
requestQueue = Volley.newRequestQueue(this);
request = new ByteArrayRequest(Request.Method.GET, getString(R.string.baseUrl) + getString(R.string.carnePdfUrl),
new Response.Listener<byte[]>() {
#Override
public void onResponse(byte[] response) {
Log.i("getBilletCard", response.toString());
try {
byte[] bytes = response;
saveToFile(bytes, "card.pdf");
}catch (Exception e){
Toast.makeText(BilletCardActivity.this, "Erro ao converter resposta", Toast.LENGTH_LONG).show();
}
}
},
new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
error.printStackTrace();
}
}){
#Override
public Map<String, String> getHeaders() throws AuthFailureError {
HashMap<String, String> headers = new HashMap<String, String>();
//headers.put("Content-Type", "application/json");
headers.put("token", token);
headers.put("empreendimento", String.valueOf(empreendimento));
headers.put("coligada", String.valueOf(coligada));
headers.put("quadra", quadra);
headers.put("lote",lote);
return headers;
}
};
requestQueue.add(request);
}
and in listener the saveToFile function should create the pdf file. The pdf file is created but raises an error "Can't open file". Here's my saveToFile function:
public void saveToFile(byte[] byteArray, String pFileName){
File f = new File(Environment.getExternalStorageDirectory() + "/myappname");
if (!f.isDirectory()) {
f.mkdir();
}
String fileName = Environment.getExternalStorageDirectory() + "/myappname/" + pFileName;
try {
FileOutputStream fPdf = new FileOutputStream(fileName);
fPdf.write(byteArray);
fPdf.flush();
fPdf.close();
Toast.makeText(this, "File successfully saved", Toast.LENGTH_LONG).show();
} catch (FileNotFoundException e) {
Toast.makeText(this, "File create error", Toast.LENGTH_LONG).show();
} catch (IOException e) {
Toast.makeText(this, "File write error", Toast.LENGTH_LONG).show();
}
}
The file is successfully create, but it doesn't open
When I tried same endpoint from Postman, everything works fine and file is successful saved and opened
after comparing Postman headers with my code, I've found that a "Cache-control=no-cache" header was missing in request.
After add this header, file was correctly downloaded.
so changed to this:
.
.
.
.
.
#Override
public Map<String, String> getHeaders() throws AuthFailureError {
HashMap<String, String> headers = new HashMap<String, String>();
headers.put("Cache-Control", "no-cache");
headers.put("token", token);
headers.put("empreendimento", String.valueOf(empreendimento));
headers.put("coligada", String.valueOf(coligada));
headers.put("quadra", quadra);
headers.put("lote",lote);
return headers;
}
.
.
.
.
.
The error happened when i upload a 115KB image file to server.(the most answer of stackoverflow is about download the file.I do not know if it is the same to those)
the error information is below:
onFailure : java.net.ProtocolException: unexpected end of stream
Relevant Code:
public void upLoadImageFile(String uploadUrl, File file, Map<String, String> maps, final HWUploadListener listener) {
final CallbackHandler handler = new CallbackHandler(listener);
try {
MultipartBody.Builder builder = new MultipartBody.Builder().setType(MultipartBody.FORM);
if (maps == null) {
builder.addPart(Headers.of("Content-Disposition", "form-data; name=\"image\";filename=\"file.jpg\""),
RequestBody.create(MediaType.parse("image/jpeg"), file)).build();
} else {
for (String key : maps.keySet()) {
builder.addFormDataPart(key, maps.get(key));
}
builder.addPart(Headers.of("Content-Disposition", "form-data; name=\"image\";filename=" + file.getName()), RequestBody.create(MediaType.parse("image/jpeg"), file)
);
}
RequestBody body = builder.build();
final Request request = new Request.Builder().url(uploadUrl).post(body).build();
final Call call = mOkHttpClient.newBuilder().writeTimeout(50, TimeUnit.SECONDS).build().newCall(request);
call.enqueue(new Callback() {
#Override
public void onFailure(Call call, IOException e) {
UtilUtils1.log("HuowuSdk", "onFailure :" + e.toString());
handler.uploadFailure(e.toString());
}
#Override
public void onResponse(Call call, Response response) throws IOException {
if (response.isSuccessful()) {
String result = response.body().string();
handler.uploadSuccess(result);
} else {
handler.uploadFailure(response.message());
}
}
});
} catch (Exception e) {
UtilUtils1.log("HuowuSdk", e.toString());
handler.uploadError(e.toString());
}
}
Appreciate your answer!!
Here in this line below you have to increase write timeout because while uploading your write timeout expires that may be the reason so in below line increase writeTimeout limit:
final Call call = mOkHttpClient.newBuilder().writeTimeout(50, TimeUnit.SECONDS).build().newCall(request);
i have urls of i got as response from a volley JsonObectRequest. What i want to be able to do is save those images directly into a folder on my external storage so i don't have to load them from the internet anymore. Please keep in mind that download may also include videos...
//Here is the volley code for retrieving the urls
private static final String endpoint = "http://api.androidhive.info/json/glide.json";
//Code to extract image url
JsonArrayRequest req = new JsonArrayRequest(endpoint,
new Response.Listener<JSONArray>() {
#Override
public void onResponse(JSONArray response) {
Log.d(TAG, response.toString());
pDialog.hide();
images.clear();
for (int i = 0; i < response.length(); i++) {
try {
JSONObject object = response.getJSONObject(i);
Image image = new Image();
image.setName(object.getString("name"));
JSONObject url = object.getJSONObject("url");
image.setSmall(url.getString("small"));
image.setMedium(url.getString("medium"));
image.setLarge(url.getString("large"));
image.setTimestamp(object.getString("timestamp"));
} catch (JSONException e) {
Log.e(TAG, "Json parsing error: " + e.getMessage());
}
}
mAdapter.notifyDataSetChanged();
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
Log.e(TAG, "Error: " + error.getMessage());
pDialog.hide();
}
});
Now, how do i request a download so they files are save in my external using volley. Thank you
public boolean storeImages(Bitmap imageBitmap, String fileName, String dirName, int index) {
File file;
if (isExternalStorageWritable() && isExternalStorageReadable()) {
file = storeImageExternalMemory(dirName, albumName, String.valueOf(index));
}
try {
assert file != null;
FileOutputStream out = new FileOutputStream(file);
imageBitmap.compress(Bitmap.CompressFormat.JPEG, 100, out);
out.flush();
out.close();
updateImageTable(file, index); // Implement Your own method to update ur DB table, U can access file location from DB table for future use of images
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
Convert your downloaded image into bitmap and the save to desired location in cellphone. Then You can reuse image.
private File storeImageExternalMemory(String dirName, String mediaName) {
String packageName = mContext.getPackageName();
File mediaStorageDir = new File(Environment.getExternalStorageDirectory().getAbsolutePath()
+ "/Android/data/" + packageName + dirName);
if (!mediaStorageDir.exists()) {
if (!mediaStorageDir.mkdirs()) {
return null;
}
}
File mediaFile;
mediaFile = new File(mediaStorageDir.getPath(), mediaName + ".jpeg");
return mediaFile;
}
/* Checks if external storage is available for read and write */
public boolean isExternalStorageWritable() {
String state = Environment.getExternalStorageState();
return Environment.MEDIA_MOUNTED.equals(state);
}
/* Checks if external storage is available to at least read */
public boolean isExternalStorageReadable() {
String state = Environment.getExternalStorageState();
return Environment.MEDIA_MOUNTED.equals(state) || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state);
}
I used a jersey server and I want that a endpoint redirect to the download of a file depending on parameters.
I have difficulties with the function below :
#GET
#Path("/get/{id}/{chunk}")
public Response getDescription(#PathParam("id") String id, #PathParam("chunk") String chunk) {
{
StreamingOutput fileStream = new StreamingOutput()
{
#Override
public void write(java.io.OutputStream output, String id) throws IOException, WebApplicationException
{
try
{
if (Objects.equals(chunk, new String("init"))) {
java.nio.file.Path path = Paths.get("src/main/uploads/example/frame_init.pdf");
}
else {
java.nio.file.Path path = Paths.get("src/main/uploads/example/"+ id +".pdf");
}
byte[] data = Files.readAllBytes(path);
output.write(data);
output.flush();
}
catch (Exception e)
{
throw new WebApplicationException("File Not Found !!");
}
}
};
return Response
.ok(fileStream, MediaType.APPLICATION_OCTET_STREAM)
.header("content-disposition","attachment; filename = myfile.pdf")
.build();
}
I have a problem with passing parameters to the function write. I have my parameters id and chunk by the endpoint but I can't use it in the write method because it implements StreamingOutput().
How I can handle it ? Thank you
For java, final keyword should solve your problem.
As updated code;
#GET
#Path("/get/{id}/{chunk}")
public Response getDescription(#PathParam("id") final String id, #PathParam("chunk") final String chunk) {
{
StreamingOutput fileStream = new StreamingOutput()
{
#Override
public void write(java.io.OutputStream output, String id2) throws IOException, WebApplicationException
{
try
{
if (Objects.equals(chunk, new String("init"))) {
java.nio.file.Path path = Paths.get("src/main/uploads/example/frame_init.pdf");
}
else {
java.nio.file.Path path = Paths.get("src/main/uploads/example/"+ id2 +".pdf");
}
byte[] data = Files.readAllBytes(path);
output.write(data);
output.flush();
}
catch (Exception e)
{
throw new WebApplicationException("File Not Found !!");
}
}
};
return Response
.ok(fileStream, MediaType.APPLICATION_OCTET_STREAM)
.header("content-disposition","attachment; filename = myfile.pdf")
.build();
}