Skip to content

Instantly share code, notes, and snippets.

@eduardb
Last active May 26, 2023 00:07
Show Gist options
  • Save eduardb/dd2dc530afd37108e1ac to your computer and use it in GitHub Desktop.
Save eduardb/dd2dc530afd37108e1ac to your computer and use it in GitHub Desktop.
Uploading a file with a progress displayed using OkHttp
public class CountingFileRequestBody extends RequestBody {
private static final int SEGMENT_SIZE = 2048; // okio.Segment.SIZE
private final File file;
private final ProgressListener listener;
private final String contentType;
public CountingFileRequestBody(File file, String contentType, ProgressListener listener) {
this.file = file;
this.contentType = contentType;
this.listener = listener;
}
@Override
public long contentLength() {
return file.length();
}
@Override
public MediaType contentType() {
return MediaType.parse(contentType);
}
@Override
public void writeTo(BufferedSink sink) throws IOException {
Source source = null;
try {
source = Okio.source(file);
long total = 0;
long read;
while ((read = source.read(sink.buffer(), SEGMENT_SIZE)) != -1) {
total += read;
sink.flush();
this.listener.transferred(total);
}
} finally {
Util.closeQuietly(source);
}
}
public interface ProgressListener {
void transferred(long num);
}
}
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);
}
}
}
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 OkHttpClient client;
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<>();
client = new OkHttpClient();
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;
final File file = new File(imagePath);
final long totalSize = file.length();
RequestBody requestBody = new MultipartBuilder()
.type(MultipartBuilder.FORM)
.addPart(
Headers.of("Content-Disposition", "form-data; name=\"image\"; filename=\"" + file.getName() + "\""),
new CountingFileRequestBody(file, "image/*", new CountingFileRequestBody.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);
}
});
}
})
)
.build();
Request request = new Request.Builder()
.tag(tempId)
.url(Constants.BASE_URL + apiPath)
.post(requestBody)
.build();
uploads.put(tempId, uploadData);
Call call = client.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(final Request request, IOException e) {
if (Constants.DEV) {
e.printStackTrace();
}
uploads.get(tempId).progressValue = -1;
if (uploadCanceled(tempId)) {
uploads.get(tempId).status = UPLOAD_ABORTED;
} else {
uploads.get(tempId).status = UPLOAD_ERROR;
}
handler.post(new Runnable() {
@Override
public void run() {
try {
ErrorModel body = gson.fromJson(request.body().toString(), ErrorModel.class);
//parse error and take action
callback.onError(ErrorType.UNKNOWN, null, null);
} catch (Throwable ex) {
callback.onError(ErrorType.UNKNOWN, null, null);
}
}
});
}
@Override
public void onResponse(Response response) throws IOException {
final BaseModel<StateModel> data = gson.fromJson(response.body().charStream(), new TypeToken<BaseModel<StateModel>>() {
}.getType());
handler.post(new Runnable() {
@Override
public void run() {
if (!data.error) {
uploads.remove(tempId);
callback.onSuccess(data.data, false);
} else {
uploads.get(tempId).status = UPLOAD_ERROR;
uploads.get(tempId).progressValue = -1;
callback.onDataError(ErrorType.DATA_INPUT, data);
}
}
});
}
});
}
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)) {
uploads.get(image).canceled = true;
client.cancel(image);
}
}
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;
UploadData() {
progressValue = -1;
}
}
}
@YounMario
Copy link

Not good solution in lower version of java environment such as jdk6.0

@priyochatterjee08
Copy link

where is the UserImage class?

@start141
Copy link

@durunvo I have the same problem! If you know how to fix this?

@eduardb
Copy link
Author

eduardb commented Mar 17, 2016

UserImage is just a model class. It can be anything, depending on your needs.

@Winghin2517
Copy link

Guys, does anyone have a working copy of this gist? I'm prepared to compile one and then share it here but if someone has a working copy and would like to share, it would save me a lot of time. thanks!

@mohdmurtuzakhan
Copy link

For this
UIUtils.loadImage(context, holder.avatar, userImage.image);
where can i get UIUtils class and ButterKnife for ButterKnife.inject(this, view);

@nb312
Copy link

nb312 commented May 3, 2018

How wonderful you are!

@aderinto05
Copy link

can share import library for these? this repo didn't put library package.

@smathur12
Copy link

does anyone have working directory or project

@pradyotprksh
Copy link

Thanks for the help. Is there any way to do it with input streams rather than files?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment