Created
October 20, 2023 01:30
-
-
Save kimji1/a606278dbc73933539d2effa33b830cc to your computer and use it in GitHub Desktop.
Display image upload progress using Retrofit2 in Android
This file contains hidden or 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
| sealed class ApiResponse<out T> { | |
| data class Success<T>(val data: T) : ApiResponse<T>() | |
| sealed class Failure(val message: String?) : ApiResponse<Nothing>() { | |
| data class HttpError( | |
| val statusCode: Int, | |
| val statusMessage: String?, | |
| val error: ErrorDto? | |
| ) : Failure(error?.message ?: statusMessage) | |
| data class NetworkError(val throwable: Throwable) : Failure(throwable.localizedMessage) | |
| data class Exception(val throwable: Throwable) : Failure(throwable.localizedMessage) | |
| } | |
| val messageOrNull: String? | |
| get() = if (this is Failure) message else null | |
| } |
This file contains hidden or 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
| inline fun <T> ApiResponse<T>.onSuccess( | |
| crossinline onResult: ApiResponse.Success<T>.() -> Unit, | |
| ): ApiResponse<T> { | |
| if (this is ApiResponse.Success) { | |
| onResult(this) | |
| } | |
| return this | |
| } | |
| inline fun <T> ApiResponse<T>.onFailure( | |
| crossinline onResult: ApiResponse.Failure.() -> Unit, | |
| ): ApiResponse<T> { | |
| if (this is ApiResponse.Failure) { | |
| onResult(this) | |
| } | |
| return this | |
| } |
This file contains hidden or 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
| interface ApiService { | |
| @Multipart | |
| @POST("api_path/uploadImage") | |
| suspend fun uploadImage(@Part request: MultipartBody.Part): ApiResponse<String> | |
| } |
This file contains hidden or 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
| class BitmapRequestBody(private val bitmap: Bitmap) : RequestBody() { | |
| override fun contentType(): MediaType = "image/jpeg".toMediaType() | |
| override fun writeTo(sink: BufferedSink) { | |
| bitmap.compress(Bitmap.CompressFormat.JPEG, 100, sink.outputStream()) | |
| } | |
| override fun contentLength(): Long { | |
| val baos = ByteArrayOutputStream() | |
| bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos) | |
| return baos.toByteArray().size.toLong() | |
| } | |
| fun withProgress(listener: UploadingProgressListener) = ProgressRequestBody(this, listener) | |
| } |
This file contains hidden or 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
| class ProgressRequestBody( | |
| private val delegate: RequestBody, | |
| private val progressListener: UploadingProgressListener | |
| ) : RequestBody() { | |
| override fun contentType(): MediaType? { | |
| return delegate.contentType() | |
| } | |
| override fun contentLength(): Long { | |
| try { | |
| return delegate.contentLength() | |
| } catch (e: IOException) { | |
| e.printStackTrace() | |
| } | |
| return -1 | |
| } | |
| @Throws(IOException::class) | |
| override fun writeTo(@NonNull sink: BufferedSink) { | |
| val totalBytes = contentLength() | |
| val progressSink: BufferedSink = object: ForwardingSink(sink) { | |
| private var bytesWritten = 0L | |
| @Throws(IOException::class) | |
| override fun write(source: Buffer, byteCount: Long) { | |
| bytesWritten += byteCount | |
| progressListener.onRequestProgress(bytesWritten, totalBytes) | |
| super.write(source, byteCount) | |
| } | |
| }.buffer() | |
| delegate.writeTo(progressSink) | |
| progressSink.flush() | |
| } | |
| } |
This file contains hidden or 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
| class SomeRepository @Inject constructor( | |
| private val service: ApiService | |
| ) { | |
| suspend fun uploadImage(request: MultipartBody.Part): ApiResponse<String> = service.uploadImage(request) | |
| } |
This file contains hidden or 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
| @HiltViewModel | |
| class SomeViewModel @Inject constructor( | |
| private val someRepository: SomeRepository | |
| ) : UploadingProgressListener { | |
| private val _uploadingProgress = MutableStateFlow<UiState<String?>>(null) | |
| val uploadingProgress = _uploadingProgress.asStateFlow() | |
| fun uploadImage(imageUri: Uri) = viewModelScope.launch(Dispatchers.IO) { | |
| _uploadingProgress.value = null | |
| val imageMultipartBody = imageUri.toMultipartBody() ?: return | |
| repository.analysisFoodShot(imageMultipartBody) | |
| .onSuccess { | |
| // upload success | |
| _uploadingProgress.value = null | |
| } | |
| .onFailure { | |
| // upload failure | |
| _uploadingProgress.value = null | |
| } | |
| } | |
| private fun Uri.toMultipartBody(): MultipartBody.Part? { | |
| val bitmap = imageUtil.decodeBitmap(this) ?: return null | |
| val imageRequestBody = BitmapRequestBody(bitmap).withProgress(this@SomeViewModel) | |
| return MultipartBody.Part.createFormData(MULTIPART_IMAGE_NAME, "image.jpg", imageRequestBody) | |
| } | |
| fun onRequestProgress(bytesWritten: Long, contentLength: Long) { | |
| val percent = (bytesWritten.toFloat() / contentLength.toFloat()) * 100f | |
| _uploadingProgress.value = String.format("%3.2f%%", percent) | |
| } | |
| } |
This file contains hidden or 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
| @FunctionalInterface | |
| interface UploadingProgressListener { | |
| fun onRequestProgress(bytesWritten: Long, contentLength: Long) | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment