Last active
May 26, 2023 00:07
-
-
Save eduardb/dd2dc530afd37108e1ac to your computer and use it in GitHub Desktop.
Uploading a file with a progress displayed using OkHttp
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 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); | |
} | |
} |
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 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; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thanks for the help. Is there any way to do it with input streams rather than files?