Skip to content

Instantly share code, notes, and snippets.

@kimji1
Created October 20, 2023 01:30
Show Gist options
  • Select an option

  • Save kimji1/a606278dbc73933539d2effa33b830cc to your computer and use it in GitHub Desktop.

Select an option

Save kimji1/a606278dbc73933539d2effa33b830cc to your computer and use it in GitHub Desktop.
Display image upload progress using Retrofit2 in Android
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
}
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
}
interface ApiService {
@Multipart
@POST("api_path/uploadImage")
suspend fun uploadImage(@Part request: MultipartBody.Part): ApiResponse<String>
}
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)
}
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()
}
}
class SomeRepository @Inject constructor(
private val service: ApiService
) {
suspend fun uploadImage(request: MultipartBody.Part): ApiResponse<String> = service.uploadImage(request)
}
@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)
}
}
@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