Last active
April 4, 2018 08:40
-
-
Save eduardb/42e53ff9e49b52ca30ca to your computer and use it in GitHub Desktop.
Uploading a file with a progress displayed using Apache's HttpClient
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class CustomMultiPartEntity implements HttpEntity | |
{ | |
private final ProgressListener progressListener; | |
private HttpEntity mHttpEntity; | |
public CustomMultiPartEntity(ProgressListener progressListener, HttpEntity httpEntity) | |
{ | |
this.progressListener = progressListener; | |
mHttpEntity = httpEntity; | |
} | |
@Override | |
public void consumeContent() throws IOException { | |
mHttpEntity.consumeContent(); | |
} | |
@Override | |
public InputStream getContent() throws IOException, | |
IllegalStateException { | |
return mHttpEntity.getContent(); | |
} | |
@Override | |
public void writeTo(OutputStream outputStream) throws IOException { | |
mHttpEntity.writeTo(new ProgressiveOutputStream(outputStream, this.progressListener)); | |
} | |
@Override | |
public Header getContentEncoding() { | |
return mHttpEntity.getContentEncoding(); | |
} | |
@Override | |
public long getContentLength() { | |
return mHttpEntity.getContentLength(); | |
} | |
@Override | |
public Header getContentType() { | |
return mHttpEntity.getContentType(); | |
} | |
@Override | |
public boolean isChunked() { | |
return mHttpEntity.isChunked(); | |
} | |
@Override | |
public boolean isRepeatable() { | |
return mHttpEntity.isRepeatable(); | |
} | |
@Override | |
public boolean isStreaming() { | |
return mHttpEntity.isStreaming(); | |
} // CONSIDER put a _real_ delegator into here! | |
static class ProxyOutputStream extends FilterOutputStream { | |
public ProxyOutputStream(OutputStream proxy) { | |
super(proxy); | |
} | |
@Override | |
public void write(int idx) throws IOException { | |
out.write(idx); | |
} | |
@Override | |
public void write(byte[] bts) throws IOException { | |
out.write(bts); | |
} | |
@Override | |
public void write(byte[] bts, int st, int end) throws IOException { | |
out.write(bts, st, end); | |
} | |
@Override | |
public void flush() throws IOException { | |
out.flush(); | |
} | |
@Override | |
public void close() throws IOException { | |
out.close(); | |
} | |
} // CONSIDER import this class (and risk more Jar File Hell) | |
static class ProgressiveOutputStream extends ProxyOutputStream { | |
private long transferred; | |
private final ProgressListener listener; | |
public ProgressiveOutputStream(OutputStream proxy, final ProgressListener listener) { | |
super(proxy); | |
this.listener = listener; | |
this.transferred = 0; | |
} | |
@Override | |
public void write(byte[] b, int off, int len) throws IOException { | |
int BUFFER_SIZE = 10000; | |
int chunkSize; | |
int currentOffset = 0; | |
while (len>currentOffset) { | |
chunkSize = len - currentOffset; | |
if (chunkSize > BUFFER_SIZE) { | |
chunkSize = BUFFER_SIZE; | |
} | |
out.write(b, currentOffset, chunkSize); | |
currentOffset += chunkSize; | |
this.transferred += chunkSize; | |
//Log.i("CustomOutputStream WRITE","" + off + "|" + len + "|" + len + "|" + currentOffset + "|" + chunkSize + "|" + this.transferred); | |
this.listener.transferred(this.transferred); | |
} | |
} | |
@Override | |
public void write(int b) throws IOException | |
{ | |
out.write(b); | |
this.transferred++; | |
this.listener.transferred(this.transferred); | |
} | |
} | |
public static interface ProgressListener | |
{ | |
void transferred(long num); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class MyPicturesAdapter extends BaseAdapter { | |
private static final int ITEM_TYPE_ONLINE = 0; | |
private static final int ITEM_TYPE_DISK = 1; | |
private List<UserImage> userImages; | |
private Context context; | |
private OnActionListener onActionListener; | |
private LayoutInflater inflater; | |
public MyPicturesAdapter(Context context) { | |
super(); | |
this.context = context; | |
this.userImages = new ArrayList<UserImage>(); | |
this.inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); | |
} | |
@Override | |
public int getCount() { | |
return userImages.size(); | |
} | |
@Override | |
public int getViewTypeCount() { | |
return 2; | |
} | |
@Override | |
public int getItemViewType(int position) { | |
return getItem(position).status >= 0 ? ITEM_TYPE_ONLINE : ITEM_TYPE_DISK; | |
} | |
@Override | |
public UserImage getItem(int position) { | |
return userImages.get(position); | |
} | |
@Override | |
public long getItemId(int position) { | |
return userImages.get(position).image.hashCode(); | |
} | |
public void clear() { | |
this.userImages.clear(); | |
} | |
public void addAll(Collection<UserImage> images) { | |
if (images != null) { | |
this.userImages.addAll(images); | |
} | |
} | |
public void remove(UserImage userImage) { | |
this.userImages.remove(userImage); | |
} | |
@Override | |
public View getView(int position, View convertView, ViewGroup parent) { | |
ViewHolder holder; | |
if (convertView != null) { | |
holder = (ViewHolder) convertView.getTag(); | |
} else { | |
int itemType = getItemViewType(position); | |
if (itemType == ITEM_TYPE_ONLINE) | |
convertView = inflater.inflate(R.layout.item_my_pictures_image, parent, false); | |
else | |
convertView = inflater.inflate(R.layout.item_my_pictures_upload, parent, false); | |
holder = new ViewHolder(convertView); | |
if (itemType == ITEM_TYPE_DISK) { | |
holder.cancelButtonListener = new CancelButtonListener(); | |
holder.statusIcon.setOnClickListener(holder.cancelButtonListener); | |
} | |
convertView.setTag(holder); | |
} | |
final UserImage userImage = getItem(position); | |
if (getItemViewType(position) == ITEM_TYPE_DISK) { | |
Picasso.with(holder.avatar.getContext()) | |
.load(Uri.fromFile(new File(UploadsHandler.getInstance().getImagePath(userImage.image)))) | |
.noFade() | |
.fit() | |
.into(holder.avatar); | |
userImage.status = UploadsHandler.getInstance().getUploadStatus(userImage.image); | |
holder.cancelButtonListener.updatePosition(position); | |
switch (userImage.status) { | |
case UploadsHandler.UPLOAD_LOADING: | |
holder.uploadProgress.setVisibility(View.VISIBLE); | |
UploadsHandler.getInstance().setProgressBar(userImage.image, holder.uploadProgress); | |
holder.statusText.setVisibility(View.GONE); | |
holder.statusIcon.setImageResource(R.drawable.icon_mb_cancelupload); | |
break; | |
case UploadsHandler.UPLOAD_ERROR: | |
holder.uploadProgress.setVisibility(View.GONE); | |
holder.statusText.setVisibility(View.VISIBLE); | |
holder.statusText.setText(R.string.picture_upload_error); | |
holder.statusIcon.setImageResource(R.drawable.icon_mb_errorupload); | |
break; | |
case UploadsHandler.UPLOAD_ABORTED: | |
holder.uploadProgress.setVisibility(View.GONE); | |
holder.statusText.setVisibility(View.VISIBLE); | |
holder.statusText.setText(R.string.picture_upload_aborted); | |
holder.statusIcon.setImageResource(R.drawable.icon_mb_refreshupload); | |
break; | |
} | |
holder.statusIndicator.setBackgroundColor(Color.TRANSPARENT); | |
holder.uploadProgress.setProgress(UploadsHandler.getInstance().getProgress(userImage.image)); | |
} else { | |
switch (userImage.status) { | |
case 0: | |
holder.statusIndicator.setBackgroundColor(context.getResources().getColor(R.color.my_pictures_check_divider)); | |
holder.statusIcon.setImageResource(R.drawable.icon_mb_check); | |
holder.actionIcon.setImageResource(R.drawable.icon_mb_settings); | |
break; | |
// etc | |
} | |
holder.statusText.setText(userImage.description); | |
UIUtils.loadImage(context, holder.avatar, userImage.image); | |
} | |
return convertView; | |
} | |
public void add(UserImage image) { | |
this.userImages.add(image); | |
} | |
public void setOnActionListener(OnActionListener onActionListener) { | |
this.onActionListener = onActionListener; | |
} | |
static class ViewHolder { | |
@InjectView(R.id.user_image_avatar) | |
ImageView avatar; | |
@InjectView(R.id.user_image_status_indicator) | |
View statusIndicator; | |
@InjectView(R.id.user_image_status_icon) | |
ImageView statusIcon; | |
@InjectView(R.id.user_image_status_text) | |
TextView statusText; | |
@Optional @InjectView(R.id.user_image_upload_progress) | |
ProgressBar uploadProgress; | |
@Optional @InjectView(R.id.user_image_action) | |
ImageView actionIcon; | |
CancelButtonListener cancelButtonListener; | |
public ViewHolder(View view) { | |
ButterKnife.inject(this, view); | |
} | |
} | |
public interface OnActionListener { | |
public void onAction(int position); | |
} | |
private class CancelButtonListener implements View.OnClickListener { | |
private int position; | |
private void updatePosition(int pos){ | |
position = pos; | |
} | |
@Override | |
public void onClick(View v) { | |
if (onActionListener != null) | |
onActionListener.onAction(position); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class UploadsHandler { | |
public static final int UPLOAD_LOADING = -1; | |
public static final int UPLOAD_ABORTED = -2; | |
public static final int UPLOAD_ERROR = -3; | |
private static UploadsHandler ourInstance; | |
private Context context; | |
private volatile HashMap<String, UploadData> uploads; | |
private List<Pair<String, String>> uploadTempQueue;§ | |
private final Gson gson; | |
private final Handler handler; | |
public static UploadsHandler getInstance() { | |
if (ourInstance == null) | |
ourInstance = new UploadsHandler(Utils.getAppContext()); | |
return ourInstance; | |
} | |
private UploadsHandler(Context context) { | |
this.context = context; | |
uploads = new HashMap<>(); | |
uploadTempQueue = new ArrayList<>(); | |
gson = new Gson(); | |
handler = new Handler(context.getMainLooper()); | |
} | |
/** | |
* Starts uploading an image to the server | |
* | |
* @param apiPath api URL; e.g. /api/userImage/upload/ | |
* @param imagePath Path to the image on disk | |
* @param tempId The temoporary name that the image gets until it's uploaded | |
* @param callback Callback to be executed after the upload is finished | |
*/ | |
public void uploadImage(String apiPath, String imagePath, final String tempId, final ApiCallback<StateModel> callback) { | |
final UploadData uploadData = new UploadData(); | |
uploadData.imagePath = imagePath; | |
uploadData.progressValue = 0; | |
uploadData.status = UPLOAD_LOADING; | |
uploadData.apiMethod = apiPath; | |
uploadData.httpClient = HttpClients.createDefault(); | |
uploadData.httpContext = new BasicHttpContext(); | |
final File file = new File(imagePath); | |
final long totalSize = file.length(); | |
String url = Constants.BASE_URL + apiPath; | |
uploadData.httpPost = new HttpPost(url); | |
MultipartEntityBuilder builder = MultipartEntityBuilder.create(); | |
builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE); | |
builder.addPart("image", new FileBody(file)); | |
CustomMultiPartEntity multiPartEntity = new CustomMultiPartEntity( | |
new CustomMultiPartEntity.ProgressListener() { | |
@Override | |
public void transferred(long num) { | |
float progress = (num / (float) totalSize) * 100; | |
uploadData.progressValue = (int) progress; | |
handler.post(new Runnable() { | |
@Override | |
public void run() { | |
updateProgressBar(tempId); | |
} | |
}); | |
} | |
}, builder.build()); | |
uploadData.httpPost.setEntity(multiPartEntity); | |
uploads.put(tempId, uploadData); | |
// make sure this executes in a background thread | |
uploadData.httpResponse = httpClient.execute(httpPost, httpContext); | |
// handle the response here | |
String serverResponse = EntityUtils.toString(uploadData.httpResponse.getEntity()); | |
// blah blah; see the other gist, "Uploading a file with a progress displayed using OkHttp" | |
} | |
public List<UserImage> getCurrentUploadsList() { | |
List<UserImage> currentUploads = new ArrayList<UserImage>(); | |
for (Map.Entry<String, UploadData> entry : uploads.entrySet()) { | |
UserImage image = new UserImage(); | |
image.image = entry.getKey(); | |
image.status = entry.getValue().status; | |
currentUploads.add(image); | |
} | |
return currentUploads; | |
} | |
public boolean isUploading(String image) { | |
return uploads.containsKey(image); | |
} | |
public String getImagePath(String image) { | |
if (uploads.containsKey(image)) { | |
return uploads.get(image).imagePath; | |
} | |
return null; | |
} | |
public String getApiPath(String image) { | |
if (uploads.containsKey(image)) { | |
return uploads.get(image).apiMethod; | |
} | |
return null; | |
} | |
public int getProgress(String image) { | |
if (uploads.containsKey(image)) { | |
return uploads.get(image).progressValue; | |
} | |
return -1; | |
} | |
public void setProgressBar(String image, ProgressBar progressBar) { | |
if (uploads.containsKey(image)) { | |
uploads.get(image).uploadProgressBar = progressBar; | |
} | |
} | |
private void updateProgressBar(String image) { | |
if (uploads.containsKey(image) && uploads.get(image).uploadProgressBar != null) { | |
uploads.get(image).uploadProgressBar.setProgress(getProgress(image)); | |
} | |
} | |
public int getUploadStatus(String image) { | |
if (uploads.containsKey(image)) | |
return uploads.get(image).status; | |
return 0; | |
} | |
public boolean uploadCanceled(String image) { | |
return uploads.containsKey(image) && uploads.get(image).canceled; | |
} | |
public void cancelUpload(String image) { | |
if (uploads.containsKey(image)) { | |
UploadData uploadData = uploads.get(image); | |
uploadData.canceled = true; | |
if (uploadData.httpPost != null) | |
uploadData.httpPost.abort(); | |
if (uploadData.httpResponse != null) | |
try { | |
uploadData.httpResponse.close(); | |
} catch (IOException e) { | |
e.printStackTrace(); | |
} | |
if (uploadData.httpClient != null) | |
try { | |
uploadData.httpClient.close(); | |
} catch (IOException e) { | |
e.printStackTrace(); | |
} | |
} | |
} | |
public void retryUpload(String image, ApiCallback<StateModel> callback) { | |
if (uploads.containsKey(image)) { | |
uploadImage(getApiPath(image), getImagePath(image), image, callback); | |
} | |
} | |
private static class UploadData { | |
ProgressBar uploadProgressBar; | |
int progressValue; | |
String imagePath; | |
int status; | |
boolean canceled; | |
String apiMethod; | |
// kinda ugly leaving these here, but we just have to :D | |
HttpPost httpPost; | |
CloseableHttpClient httpClient; | |
CloseableHttpResponse httpResponse; | |
UploadData() { | |
progressValue = -1; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment