Skip to content

Instantly share code, notes, and snippets.

@eduardb
Last active May 26, 2023 00:07
Show Gist options
  • Star 64 You must be signed in to star a gist
  • Fork 30 You must be signed in to fork a gist
  • 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;
}
}
}
@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