@@ -947,6 +947,210 @@ Use `Response.challenges()` to get the schemes and realms of any authentication
947947 }
948948 ```
949949
950+ ### Upload Progress ([ .kt] [ UploadProgressKotlin ] , [ .java] [ UploadProgressJava ] )
951+
952+ Upload a file to a server (for example, Imgur) and report progress as the request body is being written. You can implement a ProgressListener to receive updates and wrap the original request body with ProgressRequestBody. This allows you to monitor how many bytes have been uploaded and calculate the percentage of completion.
953+
954+ === ":material-language-kotlin: Kotlin"
955+ ```kotlin
956+ class UploadProgress {
957+
958+ companion object {
959+ private const val IMGUR_CLIENT_ID = "9199fdef135c122"
960+ private val MEDIA_TYPE_PNG = "image/png".toMediaType()
961+
962+ @JvmStatic
963+ fun main(args: Array<String>) {
964+ UploadProgress().run()
965+ }
966+ }
967+
968+ private val client = OkHttpClient()
969+
970+ @Throws(Exception::class)
971+ fun run() {
972+ val progressListener = object : ProgressListener {
973+ private var firstUpdate = true
974+
975+ override fun update(bytesWritten: Long, contentLength: Long, done: Boolean) {
976+ if (done) {
977+ println("completed")
978+ } else {
979+ if (firstUpdate) {
980+ firstUpdate = false
981+ if (contentLength == -1L) {
982+ println("content-length: unknown")
983+ } else {
984+ println("content-length: $contentLength")
985+ }
986+ }
987+ println(bytesWritten)
988+ if (contentLength != -1L) {
989+ println("${100 * bytesWritten / contentLength}% done")
990+ }
991+ }
992+ }
993+ }
994+
995+ val file = File("docs/images/logo-square.png")
996+ val requestBody: RequestBody = file.asRequestBody(MEDIA_TYPE_PNG)
997+
998+ val request =
999+ Request.Builder().header("Authorization", "Client-ID $IMGUR_CLIENT_ID")
1000+ .url("https://api.imgur.com/3/image")
1001+ .post(ProgressRequestBody(requestBody, progressListener)).build()
1002+
1003+ client.newCall(request).execute().use { response ->
1004+ if (!response.isSuccessful) throw IOException("Unexpected code $response")
1005+ println(response.body.string())
1006+ }
1007+ }
1008+
1009+ private class ProgressRequestBody(
1010+ private val delegate: RequestBody, private val progressListener: ProgressListener
1011+ ) : RequestBody() {
1012+
1013+ override fun contentType() = delegate.contentType()
1014+
1015+ @Throws(IOException::class)
1016+ override fun contentLength(): Long = delegate.contentLength()
1017+
1018+ @Throws(IOException::class)
1019+ override fun writeTo(sink: BufferedSink) {
1020+ val forwardingSink = object : ForwardingSink(sink) {
1021+ private var totalBytesWritten: Long = 0
1022+ private var completed = false
1023+
1024+ override fun write(source: Buffer, byteCount: Long) {
1025+ super.write(source, byteCount)
1026+ totalBytesWritten += byteCount
1027+ progressListener.update(totalBytesWritten, contentLength(), completed)
1028+ }
1029+
1030+ override fun close() {
1031+ super.close()
1032+ if (!completed) {
1033+ completed = true
1034+ progressListener.update(totalBytesWritten, contentLength(), completed)
1035+ }
1036+ }
1037+ }
1038+
1039+ val bufferedSink = forwardingSink.buffer()
1040+ delegate.writeTo(bufferedSink)
1041+ bufferedSink.flush()
1042+ }
1043+ }
1044+
1045+ fun interface ProgressListener {
1046+ fun update(bytesWritten: Long, contentLength: Long, done: Boolean)
1047+ }
1048+ }
1049+ ```
1050+
1051+ === ":material-language-java: Java"
1052+ ```java
1053+ public final class UploadProgress {
1054+ private static final String IMGUR_CLIENT_ID = "9199fdef135c122";
1055+ private static final MediaType MEDIA_TYPE_PNG = MediaType.get("image/png");
1056+
1057+ private final OkHttpClient client = new OkHttpClient();
1058+
1059+ public void run() throws Exception {
1060+ final ProgressListener progressListener = new ProgressListener() {
1061+ boolean firstUpdate = true;
1062+
1063+ @Override public void update(long bytesWritten, long contentLength, boolean done) {
1064+ if (done) {
1065+ System.out.println("completed");
1066+ } else {
1067+ if (firstUpdate) {
1068+ firstUpdate = false;
1069+ if (contentLength == -1) {
1070+ System.out.println("content-length: unknown");
1071+ } else {
1072+ System.out.format("content-length: %d\n", contentLength);
1073+ }
1074+ }
1075+ System.out.println(bytesWritten);
1076+ if (contentLength != -1) {
1077+ System.out.format("%d%% done\n", (100 * bytesWritten) / contentLength);
1078+ }
1079+ }
1080+ }
1081+ };
1082+
1083+ RequestBody requestBody = RequestBody.create(
1084+ new File("docs/images/logo-square.png"),
1085+ MEDIA_TYPE_PNG);
1086+
1087+ Request request = new Request.Builder()
1088+ .header("Authorization", "Client-ID " + IMGUR_CLIENT_ID)
1089+ .url("https://api.imgur.com/3/image")
1090+ .post(new ProgressRequestBody(requestBody, progressListener))
1091+ .build();
1092+
1093+ Response response = client.newCall(request).execute();
1094+ if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
1095+
1096+ System.out.println(response.body().string());
1097+ }
1098+
1099+ public static void main(String... args) throws Exception {
1100+ new UploadProgress().run();
1101+ }
1102+
1103+ private static class ProgressRequestBody extends RequestBody {
1104+ private final ProgressListener progressListener;
1105+ private final RequestBody delegate;
1106+
1107+ public ProgressRequestBody(RequestBody delegate, ProgressListener progressListener) {
1108+ this.delegate = delegate;
1109+ this.progressListener = progressListener;
1110+ }
1111+
1112+ @Override public MediaType contentType() {
1113+ return delegate.contentType();
1114+ }
1115+
1116+ @Override public long contentLength() throws IOException {
1117+ return delegate.contentLength();
1118+ }
1119+
1120+ @Override public void writeTo(BufferedSink sink) throws IOException {
1121+ BufferedSink bufferedSink = Okio.buffer(sink(sink));
1122+ delegate.writeTo(bufferedSink);
1123+ bufferedSink.flush();
1124+ }
1125+
1126+ public Sink sink(Sink sink) {
1127+ return new ForwardingSink(sink) {
1128+ private long totalBytesWritten = 0L;
1129+ private boolean completed = false;
1130+
1131+ @Override public void write(Buffer source, long byteCount) throws IOException {
1132+ super.write(source, byteCount);
1133+ totalBytesWritten += byteCount;
1134+ progressListener.update(totalBytesWritten, contentLength(), completed);
1135+ }
1136+
1137+ @Override public void close() throws IOException {
1138+ super.close();
1139+ if (!completed) {
1140+ completed = true;
1141+ progressListener.update(totalBytesWritten, contentLength(), completed);
1142+ }
1143+ }
1144+ };
1145+ }
1146+ }
1147+
1148+ interface ProgressListener {
1149+ void update(long bytesWritten, long contentLength, boolean done);
1150+ }
1151+ }
1152+ ```
1153+
9501154 [ SynchronousGetJava ] : https://github.com/square/okhttp/blob/master/samples/guide/src/main/java/okhttp3/recipes/SynchronousGet.java
9511155 [ SynchronousGetKotlin ] : https://github.com/square/okhttp/blob/master/samples/guide/src/main/java/okhttp3/recipes/kt/SynchronousGet.kt
9521156 [ AsynchronousGetJava ] : https://github.com/square/okhttp/blob/master/samples/guide/src/main/java/okhttp3/recipes/AsynchronousGet.java
@@ -975,3 +1179,5 @@ Use `Response.challenges()` to get the schemes and realms of any authentication
9751179 [ PerCallSettingsKotlin ] : https://github.com/square/okhttp/blob/master/samples/guide/src/main/java/okhttp3/recipes/kt/PerCallSettings.kt
9761180 [ AuthenticateJava ] : https://github.com/square/okhttp/blob/master/samples/guide/src/main/java/okhttp3/recipes/Authenticate.java
9771181 [ AuthenticateKotlin ] : https://github.com/square/okhttp/blob/master/samples/guide/src/main/java/okhttp3/recipes/kt/Authenticate.kt
1182+ [ UploadProgressJava ] : https://github.com/square/okhttp/blob/master/samples/guide/src/main/java/okhttp3/recipes/UploadProgress.java
1183+ [ UploadProgressKotlin ] : https://github.com/square/okhttp/blob/master/samples/guide/src/main/java/okhttp3/recipes/kt/UploadProgress.kt
0 commit comments