Skip to content

Instantly share code, notes, and snippets.

@FrancoisBlavoet
Created March 27, 2015 10:52
Show Gist options
  • Save FrancoisBlavoet/bfddf6cbddc8e61ade80 to your computer and use it in GitHub Desktop.
Save FrancoisBlavoet/bfddf6cbddc8e61ade80 to your computer and use it in GitHub Desktop.
Glide two levels Fetcher (and its associated ModelLoader) allowing to query another source (local storage, other API, ....) before calling the network fetcher.
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.bumptech.glide.Priority;
import com.bumptech.glide.load.data.DataFetcher;
import java.io.InputStream;
/**
* Created by François on 29/10/14.
* {@link com.bumptech.glide.load.data.DataFetcher} implementation that allows to implement the
* following flow :
* <ol>
* <li>check in RAM LRU</li>
* <li>check in disk LRU</li>
* <li>check in our synchro module, in case the image belongs to a synchronized media</li>
* <li>fetch from our API throughout a network connection</li>
* </ol>
*
* @see ASynchronizableImageLoader
*/
public abstract class ASynchronizableDataFetcher<Model> implements DataFetcher<InputStream> {
@Nullable
private final DataFetcher<InputStream> networkFetcher;
@NonNull
private final Model model;
private final int width;
private final int height;
public ASynchronizableDataFetcher(@Nullable DataFetcher<InputStream> networkFetcher,
@NonNull Model model,
int width,
int height) {
this.networkFetcher = networkFetcher;
this.model = model;
this.width = width;
this.height = height;
}
@Override
public InputStream loadData(Priority priority) throws Exception {
InputStream result = loadFromSynchro(model, width, height);
if (result == null && networkFetcher != null) {
// you can eventually also add another condition allowing to force the Fetcher to do not use the networkFetcher
// -> that way you can provide an 'offline mode' in your app in order to let the user economize data.
result = networkFetcher.loadData(priority);
}
return result;
}
@Nullable
public abstract InputStream loadFromSynchro(@NonNull Model model, int width, int height);
@Override
public void cleanup() {
if (networkFetcher != null) networkFetcher.cleanup();
}
@Override
public void cancel() {
if (networkFetcher != null) networkFetcher.cancel();
}
}
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.data.DataFetcher;
import com.bumptech.glide.load.model.GlideUrl;
import com.bumptech.glide.load.model.ModelCache;
import com.bumptech.glide.load.model.ModelLoader;
import com.bumptech.glide.load.model.stream.StreamModelLoader;
import java.io.InputStream;
/**
* A base class for loading an image associated with a {@link Model} that can be present
* in our synchronisation module.<br>
* Coupled with the corresponding {@link com.bumptech.glide.load.data.DataFetcher},
* this class allows to implement the following flow :<br>
* <ol>
* <li>check in RAM LRU </li>
* <li>check in disk LRU </li>
* <li>check in our synchro module, in case the image belongs to a synchronized media </li>
* <li>fetch from our API, throughout the network connection</li>
* </ol>
* You can optionally override {@link ASynchronizableImageLoader#getModelCacheSize()}
* in order to specify how many urls you want to cache.
*
* @param <Model> The type of the model
*/
public abstract class ASynchronizableImageLoader<Model> implements StreamModelLoader<Model> {
private final static int DEFAULT_MODEL_CACHE_SIZE = 100;
private final ModelLoader<GlideUrl, InputStream> mBaseLoader;
@Nullable
private ModelCache<Model, GlideUrl> modelCache;
public ASynchronizableImageLoader(Context context) {
mBaseLoader = Glide.buildModelLoader(GlideUrl.class, InputStream.class, context);
if (getModelCacheSize() > 0) {
modelCache = new ModelCache<>(getModelCacheSize());
}
}
@Override
@Nullable
public final DataFetcher<InputStream> getResourceFetcher(final @Nullable Model model,
int width,
int height) {
if (model == null) {
return null;
}
GlideUrl glideUrl = null;
if (modelCache != null) {
glideUrl = modelCache.get(model, width, height);
}
if (glideUrl == null) {
String url = getUrl(model, width, height);
if (TextUtils.isEmpty(url)) {
return null;
}
glideUrl = new GlideUrl(url);
if (modelCache != null) {
modelCache.put(model, width, height, glideUrl);
}
}
final DataFetcher<InputStream> networkFetcher =
mBaseLoader.getResourceFetcher(glideUrl, width, height);
return getFetcher(networkFetcher, model, width, height);
}
/**
* override this method if you want to modify the size of the modelCache
*
* @return modelCache size, the cache will be {@code null} if size >= 0 <br>
* By default, modelCache contains the last
* {@link ASynchronizableImageLoader#DEFAULT_MODEL_CACHE_SIZE}
* = {@value ASynchronizableImageLoader#DEFAULT_MODEL_CACHE_SIZE} requests.
*/
protected int getModelCacheSize() {
return DEFAULT_MODEL_CACHE_SIZE;
}
/**
* Implement this method in order to provide the Fetcher you want to use with this loader.<br>
* You can return {@code null}, in this case the Loader will only rely on Glide caches.
*
* @param defaultFetcher the default fetcher to use as a fallback
* @param model the model to load
* @param width the target's width
* @param height the target's height
* @return the fetcher to use or {@code null}.
*/
@Nullable
public abstract ASynchronizableDataFetcher<Model> getFetcher(DataFetcher<InputStream> defaultFetcher,
@NonNull Model model,
int width,
int height);
/**
* Get a valid url http:// or https:// for the given model and dimensions as a string.
*
* @param model The model.
* @param width The width in pixels of the view/target the image will be loaded into.
* @param height The height in pixels of the view/target the image will be loaded into.
* @return The String url or <code>null</code>
*/
@Nullable
protected abstract String getUrl(@NonNull Model model,
int width,
int height);
}
@erseno
Copy link

erseno commented Dec 1, 2015

How do you register it in the Glide Module ?
Can you please add this. Thanks

@TWiStErRob
Copy link

@Teovald can you explain why was it necessary to always create a fetcher for the GlideUrl?

final DataFetcher<InputStream> networkFetcher = mBaseLoader.getResourceFetcher(glideUrl, width, height);
return getFetcher(networkFetcher, model, width, height);

I think it would be better to ask the getFetcher first, and if that returns null, then, and only then do all the GlideUrl stuff, it's a lot of unnecessary work done and potentially discarded if the model has pinned content. It also shifts the burden of falling back to defaultFetcher to the extending class, why?

I'm not saying it's wrong, I'm just trying to understand the reasons you chose to do that.

EDIT: never mind, I just saw that you need the defaultFetcher to construct an ASynchronizableDataFetcher.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment