Retrofit: How to setup timeout dynamically with retrofit2?

Created on 17 Nov 2017  路  6Comments  路  Source: square/retrofit

There is a api need a larger timeout, but my retrofit is a singleton.

And i do not want to make all timeout too large.

Is there something like '@TIMEOUT'?

Is there a better solution without creating new instance?

Most helpful comment

Thanks a lot.

here is my code:

public static final String CONNECT_TIMEOUT = "CONNECT_TIMEOUT";
public static final String READ_TIMEOUT = "READ_TIMEOUT";
public static final String WRITE_TIMEOUT = "WRITE_TIMEOUT";

...

Interceptor timeoutInterceptor = new Interceptor() {
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();

        int connectTimeout = chain.connectTimeoutMillis();
        int readTimeout = chain.readTimeoutMillis();
        int writeTimeout = chain.writeTimeoutMillis();

        String connectNew = request.header(CONNECT_TIMEOUT);
        String readNew = request.header(READ_TIMEOUT);
        String writeNew = request.header(WRITE_TIMEOUT);

        if (!TextUtils.isEmpty(connectNew)) {
            connectTimeout = Integer.valueOf(connectNew);
        }
        if (!TextUtils.isEmpty(readNew)) {
            readTimeout = Integer.valueOf(readNew);
        }
        if (!TextUtils.isEmpty(writeNew)) {
            writeTimeout = Integer.valueOf(writeNew);
        }

        return chain
                .withConnectTimeout(connectTimeout, TimeUnit.MILLISECONDS)
                .withReadTimeout(readTimeout, TimeUnit.MILLISECONDS)
                .withWriteTimeout(writeTimeout, TimeUnit.MILLISECONDS)
                .proceed(request);
    }
};

OkHttpClient httpClient = new OkHttpClient.Builder()
        .connectTimeout(DEFAULT_CONNECT_TIMEOUT, TimeUnit.MILLISECONDS)
        .writeTimeout(DEFAULT_WRITE_TIMEOUT, TimeUnit.MILLISECONDS)
        .readTimeout(DEFAULT_READ_TIMEOUT, TimeUnit.MILLISECONDS)
        .addInterceptor(timeoutInterceptor)
        .build();

retrofit = new Retrofit.Builder()
        .baseUrl(Host.Develop.ACCOUNT_HOST)
        .addConverterFactory(GsonConverterFactory.create())
        .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
        .client(httpClient)
        .build();

...

example usage:

/**
 * check account status
 *
 * @param account
 * @return
 */
@Headers({"CONNECT_TIMEOUT:10000", "READ_TIMEOUT:10000", "WRITE_TIMEOUT:10000"})
@GET("login/prelogin")
Observable<ResponseBody> preLogin(@Query("account") String account);

All 6 comments

Retrofit doesn't know anything about timeouts because it doesn't know anything about the HTTP client. The easiest way to control per-request is to add a synthetic @Header to indicate you want different timeouts and then parse that header in an OkHttp interceptor in which you can then customize the timeouts with its interceptor methods. This requires OkHttp 3.9.0 (or newer).

Thanks a lot.

here is my code:

public static final String CONNECT_TIMEOUT = "CONNECT_TIMEOUT";
public static final String READ_TIMEOUT = "READ_TIMEOUT";
public static final String WRITE_TIMEOUT = "WRITE_TIMEOUT";

...

Interceptor timeoutInterceptor = new Interceptor() {
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();

        int connectTimeout = chain.connectTimeoutMillis();
        int readTimeout = chain.readTimeoutMillis();
        int writeTimeout = chain.writeTimeoutMillis();

        String connectNew = request.header(CONNECT_TIMEOUT);
        String readNew = request.header(READ_TIMEOUT);
        String writeNew = request.header(WRITE_TIMEOUT);

        if (!TextUtils.isEmpty(connectNew)) {
            connectTimeout = Integer.valueOf(connectNew);
        }
        if (!TextUtils.isEmpty(readNew)) {
            readTimeout = Integer.valueOf(readNew);
        }
        if (!TextUtils.isEmpty(writeNew)) {
            writeTimeout = Integer.valueOf(writeNew);
        }

        return chain
                .withConnectTimeout(connectTimeout, TimeUnit.MILLISECONDS)
                .withReadTimeout(readTimeout, TimeUnit.MILLISECONDS)
                .withWriteTimeout(writeTimeout, TimeUnit.MILLISECONDS)
                .proceed(request);
    }
};

OkHttpClient httpClient = new OkHttpClient.Builder()
        .connectTimeout(DEFAULT_CONNECT_TIMEOUT, TimeUnit.MILLISECONDS)
        .writeTimeout(DEFAULT_WRITE_TIMEOUT, TimeUnit.MILLISECONDS)
        .readTimeout(DEFAULT_READ_TIMEOUT, TimeUnit.MILLISECONDS)
        .addInterceptor(timeoutInterceptor)
        .build();

retrofit = new Retrofit.Builder()
        .baseUrl(Host.Develop.ACCOUNT_HOST)
        .addConverterFactory(GsonConverterFactory.create())
        .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
        .client(httpClient)
        .build();

...

example usage:

/**
 * check account status
 *
 * @param account
 * @return
 */
@Headers({"CONNECT_TIMEOUT:10000", "READ_TIMEOUT:10000", "WRITE_TIMEOUT:10000"})
@GET("login/prelogin")
Observable<ResponseBody> preLogin(@Query("account") String account);

Only @Headers({"CONNECT_TIMEOUT:10000", "READ_TIMEOUT:10000", "WRITE_TIMEOUT:10000"}) work for me. Thanks @zhufeng1222

@zhufeng1222
You should not send these headers to the Webserver. You can easily remove them:

Interceptor timeoutInterceptor = new Interceptor() {

    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();

        int connectTimeout = chain.connectTimeoutMillis();
        int readTimeout = chain.readTimeoutMillis();
        int writeTimeout = chain.writeTimeoutMillis();

        String connectNew = request.header(CONNECT_TIMEOUT);
        String readNew = request.header(READ_TIMEOUT);
        String writeNew = request.header(WRITE_TIMEOUT);

        if (!TextUtils.isEmpty(connectNew)) {
            connectTimeout = Integer.valueOf(connectNew);
        }
        if (!TextUtils.isEmpty(readNew)) {
            readTimeout = Integer.valueOf(readNew);
        }
        if (!TextUtils.isEmpty(writeNew)) {
            writeTimeout = Integer.valueOf(writeNew);
        }

        Request.Builder builder = request.newBuilder();
        builder.removeHeader(CONNECT_TIMEOUT);
        builder.removeHeader(READ_TIMEOUT);
        builder.removeHeader(WRITE_TIMEOUT);

        return chain
                .withConnectTimeout(connectTimeout, TimeUnit.MILLISECONDS)
                .withReadTimeout(readTimeout, TimeUnit.MILLISECONDS)
                .withWriteTimeout(writeTimeout, TimeUnit.MILLISECONDS)
                .proceed(builder.build());
    }
};

great

@zhufeng1222 @SimonStefan
Thanks a lot.
This really help me.

To attain the object , I was using the operator "timeout" of RxJava in my request,but that always not work and it would be blocked in weak network. And I didn't get TimeoutException in the method "onError".
I investigate the issue for a long time and I get nothing useful reason.
I don't want to give up the concise way after all it only need to add one row of code.

@JakeWharton Could you help me find the possible reason?
Here's my code.

        mModel.getQRData(1, remainOfflineQRNumber)
                .timeout(qrDataTimeOut, TimeUnit.MILLISECONDS)
                .compose(RxUtils.applySchedulers(mRootView, false))
                .subscribe(new ErrorHandleSubscriber<BaseJson<QRData>>(mRxErrorHandler) {
public static <T> ObservableTransformer<T, T> applySchedulers(final IView view, boolean isShowLoading) {
        return new ObservableTransformer<T, T>() {
            @Override
            public Observable<T> apply(Observable<T> observable) {
                return observable
                        .subscribeOn(Schedulers.io())
                        .doOnSubscribe(disposable -> {
                            if (isShowLoading) {
                                view.showLoading();
                            }
                        })
                        .subscribeOn(AndroidSchedulers.mainThread())
                        .observeOn(AndroidSchedulers.mainThread())
                        .doAfterTerminate(new Action() {
                            @Override
                            public void run() {
                                if (isShowLoading) {
                                    view.hideLoading();
                                }
                            }
                        })
                        .compose(RxUtils.bindToLifecycle(view));
            }
        };
    }

private static <T> LifecycleTransformer<T> bindToLifecycle(IView view) {
        if (view instanceof LifecycleProvider) {
            return ((LifecycleProvider) view).bindToLifecycle();
        } else {
            throw new IllegalArgumentException("view isn't activity or fragment");
        }

    }
Was this page helpful?
0 / 5 - 0 ratings