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