Friday, August 19, 2011

Android upload progress

Some time ago I got a task to create a file uploader for Android. Writing file uploader code it not difficult and I’ve done it quickly. To indicate the uploading progress I’ve shown standard rotating progress control while uploading.
But it’s not quite clear for users. Much better is to show the usual progress bar with bytes uploaded and total size. So I decided to implement this user friendliness.
Source code of BackgroundUploader class is here:

class BackgroundUploader extends AsyncTask<Void, Integer, Void> implements DialogInterface.OnCancelListener {

  private ProgressDialog progressDialog;
  private String url;
  private File file;

   public BackgroundUploader(String url, File file) {
    this.url = url;
    this.file = file;
  }

   @Override
  protected void onPreExecute() {
    progressDialog = new ProgressDialog(context);
    progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
    progressDialog.setMessage("Uploading...");
    progressDialog.setCancelable(false);
    progressDialog.setMax((int) file.length());
    progressDialog.show();
  }

   @Override
  protected Void doInBackground(Void... v) {
    HttpURLConnection.setFollowRedirects(false);
    HttpURLConnection connection = null;
    String fileName = file.getName();
    try {
      connection = (HttpURLConnection) new URL(url).openConnection();
      connection.setRequestMethod("POST");
      String boundary = "---------------------------boundary";
      String tail = "\r\n--" + boundary + "--\r\n";
      connection.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
      connection.setDoOutput(true);

      String metadataPart = "--" + boundary + "\r\n"
          + "Content-Disposition: form-data; name=\"metadata\"\r\n\r\n"
          + "" + "\r\n";

      String fileHeader1 = "--" + boundary + "\r\n"
          + "Content-Disposition: form-data; name=\"uploadfile\"; filename=\""
          + fileName + "\"\r\n"
          + "Content-Type: application/octet-stream\r\n"
          + "Content-Transfer-Encoding: binary\r\n";

       long fileLength = file.length() + tail.length();
      String fileHeader2 = "Content-length: " + fileLength + "\r\n";
      String fileHeader = fileHeader1 + fileHeader2 + "\r\n";
      String stringData = metadataPart + fileHeader;

      long requestLength = stringData.length() + fileLength;
      connection.setRequestProperty("Content-length", "" + requestLength);
      connection.setFixedLengthStreamingMode((int) requestLength);
      connection.connect();

      DataOutputStream out = new DataOutputStream(connection.getOutputStream());
      out.writeBytes(stringData);
      out.flush();

      int progress = 0;
      int bytesRead = 0;
      byte buf[] = new byte[1024];
      BufferedInputStream bufInput = new BufferedInputStream(new FileInputStream(file));
      while ((bytesRead = bufInput.read(buf)) != -1) {
        // write output
        out.write(buf, 0, bytesRead);
        out.flush();
        progress += bytesRead;
        // update progress bar
        publishProgress(progress);
      }

       // Write closing boundary and close stream
      out.writeBytes(tail);
      out.flush();
      out.close();

       // Get server response
      BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
      String line = "";
      StringBuilder builder = new StringBuilder();
      while((line = reader.readLine()) != null) {
        builder.append(line);
      }

    } catch (Exception e) {
      // Exception
    } finally {
      if (connection != null) connection.disconnect();
    }

    return null;
  }

   @Override
  protected void onProgressUpdate(Integer... progress) {
    progressDialog.setProgress((int) (progress[0]));
  }

   @Override
  protected void onPostExecute(Void v) {
    progressDialog.dismiss();
  }

   @Override
  public void onCancel(DialogInterface dialog) {
    cancel(true);
    dialog.dismiss();
  }
}

* This source code was highlighted with Source Code Highlighter.


To execute file uploader the next code should be used:

new BackgroundUploader(url, file).execute();

I wrote the code based on the DNDApplet.java.

28 comments:

  1. Man, thank you so much for posting this code! I have been playing with my own for a couple days and was about to pull my hair out. You have save me much more frustration by sharing your work, you rock!

    ~Stephen

    ReplyDelete
  2. thank you, this is a very useful code

    ReplyDelete
  3. Thanks DELIMITRY, Nice Share and great Code

    ReplyDelete
  4. This comment has been removed by the author.

    ReplyDelete
  5. You are awesome ! BTW it throws "ECONNRESET (Connection reset by peer)" exception for large files.

    ReplyDelete
  6. Changing connection.setRequestMethod("POST"); to connection.setRequestMethod("PUT"); fixes "ECONNRESET " exception !

    ReplyDelete
  7. where can i download the source code

    ReplyDelete
  8. Hey, thanks for the code, I'm having a fatal exception:
    on progressDialog = new ProgressDialog(myContext);

    "Can't create handler inside thread that has not called Looper.prepare()"

    I'm sending the context via a parameter (added a Context parameter on the constructor). Here's the code, not sure how to solve this, I tried creating calling runOnUiThread but doesn't let me do that not sure why. My modified code:

    private Context myContext;

    public BackgroundUploader(String url, File file, Context context) {
    this.url = url;
    this.file = file;
    myContext = context;


    }



    @Override
    protected void onPreExecute() {

    progressDialog = new ProgressDialog(myContext); // I have the exception here.
    progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
    progressDialog.setMessage("Uploading...");
    progressDialog.setCancelable(false);
    progressDialog.setMax((int) file.length());
    progressDialog.show();
    }

    ReplyDelete
  9. How can I send a string, suppose str, alongwith the multipart data?

    ReplyDelete
  10. How can I send a string, suppose str, alongwith the multipart data?

    ReplyDelete
  11. Great article, even through posted from 2011, effectively solved my problem of httpurlconnection upload progress issue.

    ReplyDelete
  12. Thank you for sharing your code - helped me a lot!

    ReplyDelete
  13. thanks very much! I was thinking whether it's possible...
    It seems the key point is that we should connect at first and then open the outputstream and write data.

    ReplyDelete
  14. This comment has been removed by the author.

    ReplyDelete
  15. Can make a server side in PHP to receive these files?? if yes, how?

    ReplyDelete
    Replies
    1. Please take a look at the server code in this answer: http://stackoverflow.com/a/5034974/821093

      Delete
  16. I'm getting this error when send a file after other file sended with sucess

    java.net.SocketException: sendto failed: EPIPE (Broken pipe)

    what is it? how i solve this?? And other question, this PHP script http://stackoverflow.com/a/5034974/821093 support for unlimited users send files at the same time?? or only works with one image per time?

    ReplyDelete
    Replies
    1. other bug I found is that when he sends a larger image than 2MB he says he sends everything right without error but the image does not appear on the server, and when you send images smaller than 2MB it appears normally

      Delete
    2. For someone who have same problem in android, this is not a problem with the code, is only bad configuration of php config, just search to upload_max_filesize and set for the max size who want..

      Delete
  17. Please provide valid php script for uploading...

    ReplyDelete
  18. How can we modify code to include FTP IP address, username and password? Does this code work only on a public access webserver only?

    ReplyDelete