I am using Glide to show previews of images located on the remote server. There is a need to handle different response codes in a different way. For example when the server responses with code 202
, I have to wait for certain socket event. Here is my code:
fun ImageView.loadRounded(glideUrl: GlideUrl, radius: Int = 20) {
GlideApp.with(this.context)
.load(glideUrl)
.apply(RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL))
.apply(RequestOptions.bitmapTransform(RoundedCorners(radius)))
.listener(object : RequestListener<Drawable> {
override fun onLoadFailed(e: GlideException?, model: Any?, target: Target<Drawable>?, isFirstResource: Boolean): Boolean {
e?.rootCauses?.forEach {
logd("${it.message}")
}
return false
}
override fun onResourceReady(resource: Drawable?, model: Any?, target: Target<Drawable>?, dataSource: DataSource?, isFirstResource: Boolean): Boolean {
return false
}
})
.into(this)
}
In the case when server sends response code 202
, in onLoadFailed
I get this output:
java.io.IOException(java.lang.RuntimeException: setDataSource failed: status = 0x80000000)
It happens because 202
is one of successfull status codes. Glide tries to download picture, but it is not there.
Method e?.logRootCauses()
doesn't give response codes neither.
Is there a way to get http response code in Glide listener?
Found a way to intercept status code and replace it with another one. For this I have replaced DataFetcher
implemetation used in Glide
with modified version.
Here are the steps:
Create @GlideModule
if you don't have it already:
@GlideModule
public class MyGlideModule extends AppGlideModule {
@Override
public void registerComponents(@NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) {
registry.append(GlideUrl.class, InputStream.class,
new MyOkHttpUrlLoader.Factory(new OkHttpClient()));
}
}
where standard OkHttpUrlLoader
is also replaced with modified version. Unfortunately, you can't just extend standard implementation since you have to interact with private members of it. So It should be a full copy of original OkHttpUrlLoader
, but return custom DataFetcher
in buildLoadData
method. Also build
method should return instance of modified version of loader:
import androidx.annotation.NonNull;
import com.bumptech.glide.load.Options;
import com.bumptech.glide.load.model.GlideUrl;
import com.bumptech.glide.load.model.ModelLoader;
import com.bumptech.glide.load.model.ModelLoaderFactory;
import com.bumptech.glide.load.model.MultiModelLoaderFactory;
import java.io.InputStream;
import okhttp3.Call;
import okhttp3.OkHttpClient;
/**
* A simple model loader for fetching media over http/https using OkHttp.
*/
public class MyOkHttpUrlLoader implements ModelLoader<GlideUrl, InputStream> {
private final Call.Factory client;
// Public API.
@SuppressWarnings("WeakerAccess")
public MyOkHttpUrlLoader(@NonNull Call.Factory client) {
this.client = client;
}
@Override
public boolean handles(@NonNull GlideUrl url) {
return true;
}
@Override
public LoadData<InputStream> buildLoadData(@NonNull GlideUrl model, int width, int height,
@NonNull Options options) {
return new LoadData<>(model, new MyOkHttpStreamFetcher(client, model));
}
/**
* The default factory for {@link com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader}s.
*/
// Public API.
@SuppressWarnings("WeakerAccess")
public static class Factory implements ModelLoaderFactory<GlideUrl, InputStream> {
private static volatile Call.Factory internalClient;
private final Call.Factory client;
/**
* Constructor for a new Factory that runs requests using a static singleton client.
*/
public Factory() {
this(getInternalClient());
}
/**
* Constructor for a new Factory that runs requests using given client.
*
* @param client this is typically an instance of {@code OkHttpClient}.
*/
public Factory(@NonNull Call.Factory client) {
this.client = client;
}
private static Call.Factory getInternalClient() {
if (internalClient == null) {
synchronized (Factory.class) {
if (internalClient == null) {
internalClient = new OkHttpClient();
}
}
}
return internalClient;
}
@NonNull
@Override
public ModelLoader<GlideUrl, InputStream> build(MultiModelLoaderFactory multiFactory) {
return new MyOkHttpUrlLoader(client);
}
@Override
public void teardown() {
// Do nothing, this instance doesn't own the client.
}
}
}
And here is my modified version of DataFetcher, which is almost a copy of a standard implementation (OkHttpStreamFetcher). Difference is that in onResponse
method I check code, and in case it is 202
I replace it with error code 418:
import android.util.Log;
import androidx.annotation.NonNull;
import com.bumptech.glide.Priority;
import com.bumptech.glide.load.DataSource;
import com.bumptech.glide.load.HttpException;
import com.bumptech.glide.load.data.DataFetcher;
import com.bumptech.glide.load.model.GlideUrl;
import com.bumptech.glide.util.ContentLengthInputStream;
import com.bumptech.glide.util.Preconditions;
import com.zultys.mobiledroid.utils.logger.Logger;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import okhttp3.Call;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
/**
* Fetches an {@link InputStream} using the okhttp library.
*/
public class MyOkHttpStreamFetcher implements DataFetcher<InputStream>, okhttp3.Callback {
private static final String TAG = "OkHttpFetcher";
private final Call.Factory client;
private final GlideUrl url;
private InputStream stream;
private ResponseBody responseBody;
private DataCallback<? super InputStream> callback;
// call may be accessed on the main thread while the object is in use on other threads. All other
// accesses to variables may occur on different threads, but only one at a time.
private volatile Call call;
// Public API.
@SuppressWarnings("WeakerAccess")
public ZultysOkHttpStreamFetcher(Call.Factory client, GlideUrl url) {
this.client = client;
this.url = url;
}
@Override
public void loadData(@NonNull Priority priority,
@NonNull final DataCallback<? super InputStream> callback) {
Request.Builder requestBuilder = new Request.Builder().url(url.toStringUrl());
for (Map.Entry<String, String> headerEntry : url.getHeaders().entrySet()) {
String key = headerEntry.getKey();
requestBuilder.addHeader(key, headerEntry.getValue());
}
Request request = requestBuilder.build();
this.callback = callback;
call = client.newCall(request);
call.enqueue(this);
}
@Override
public void onFailure(@NonNull Call call, @NonNull IOException e) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "OkHttp failed to obtain result", e);
}
callback.onLoadFailed(e);
}
@Override
public void onResponse(@NonNull Call call, @NonNull Response response) {
responseBody = response.body();
// Code 202 means that file was sent from another MX in MX net,
// and you have to wait until it gets copied to your MX. After that you will receive
// csta packet with messageStatus IMS_FILE_DOWNLOAD, and can repeat same request.
if (response.code() == 202) {
callback.onLoadFailed(new HttpException("I'm a teapot", 418));
} else if (response.isSuccessful()) {
long contentLength = Preconditions.checkNotNull(responseBody).contentLength();
stream = ContentLengthInputStream.obtain(responseBody.byteStream(), contentLength);
callback.onDataReady(stream);
} else {
callback.onLoadFailed(new HttpException(response.message(), response.code()));
}
}
@Override
public void cleanup() {
try {
if (stream != null) {
stream.close();
}
} catch (IOException e) {
// Ignored
}
if (responseBody != null) {
responseBody.close();
}
callback = null;
}
@Override
public void cancel() {
Call local = call;
if (local != null) {
local.cancel();
}
}
@NonNull
@Override
public Class<InputStream> getDataClass() {
return InputStream.class;
}
@NonNull
@Override
public DataSource getDataSource() {
return DataSource.REMOTE;
}
}