diff --git a/library/src/com/bumptech/glide/Glide.java b/library/src/com/bumptech/glide/Glide.java index 724441a88c..65c3576b90 100644 --- a/library/src/com/bumptech/glide/Glide.java +++ b/library/src/com/bumptech/glide/Glide.java @@ -54,6 +54,67 @@ public class Glide { private ImageManager imageManager = null; + /** + * A class for handling exceptions that occur while loading images + * + * @param The type of the model being loaded + */ + public static abstract class ExceptionHandler { + + /** + * Called when an exception occurs during a load. Will only be called if we currently want to display an image + * for the given model in the given target. It is recommended to create a single instance per activity/fragment + * rather than instantiate a new object for each call to {@code Glide.load()} to avoid object churn. + * + *

+ * It is safe to reload this or a different model or change what is displayed in the target at this point. + * For example: + *

+         * 
+         *     public void onException(Exception e, T model, Target target) {
+         *         target.setPlaceholder(R.drawable.a_specific_error_for_my_exception);
+         *         Glide.load(model).into(target);
+         *     }
+         * 
+         * 
+ *

+ * + *

+ * Note - if you want to reload this or any other model after an exception, you will need to include all + * relevant builder calls (like centerCrop, placeholder etc). + *

+ * + * @param e The exception, or null + * @param model The model we were trying to load when the exception occured + * @param target The {@link Target} we were trying to load the image into + */ + public abstract void onException(Exception e, T model, Target target); + + /** + * {@inheritDoc} + * + *

+ * By default we only check the both objects are not null and that their classes are identical. This assumes + * that two instances of the same anonymous inner class will behave identically. + *

+ */ + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + return true; + } + + /** + * {@inheritDoc } + */ + @Override + public int hashCode() { + throw new UnsupportedOperationException(); + } + } + /** * Get the singleton. * @@ -423,6 +484,7 @@ public static class Request { private int errorId = -1; private Downsampler downsampler = Downsampler.AT_LEAST; private ArrayList> transformationLoaders = new ArrayList>(); + private ExceptionHandler exceptionHandler; private Request(T model) { this(model, GLIDE.getFactory(model)); @@ -569,6 +631,19 @@ public Request error(int resourceId) { return this; } + /** + * Sets an exception handler to use if a load fails. Note it's best to create a single instance of an exception + * handler per activity/fragment rather than pass one in per request. + * + * @param exceptionHandler The exception handler to use + * @return This request + */ + public Request exception(ExceptionHandler exceptionHandler) { + this.exceptionHandler = exceptionHandler; + + return this; + } + /** * Start loading the image into the view. * @@ -641,7 +716,7 @@ private ImagePresenter getImagePresenter(Target target) { return result; } - private ImagePresenter buildImagePresenter(Target target) { + private ImagePresenter buildImagePresenter(final Target target) { TransformationLoader transformationLoader = getFinalTransformationLoader(); ImagePresenter.Builder builder = new ImagePresenter.Builder() @@ -670,6 +745,17 @@ public void onImageReady(Target target, boolean fromCache) { builder.setErrorResource(errorId); } + if (exceptionHandler != null) { + builder.setExceptionHandler(new ImagePresenter.ExceptionHandler() { + @Override + public void onException(Exception e, T model, boolean isCurrent) { + if (isCurrent) { + exceptionHandler.onException(e, model, target); + } + } + }); + } + return builder.build(); } @@ -732,6 +818,7 @@ private static class Metadata { private final String downsamplerId; private final String transformationId; + private final ExceptionHandler exceptionHandler; public Metadata(Request request) { modelClass = request.model.getClass(); @@ -741,6 +828,7 @@ public Metadata(Request request) { animationId = request.animationId; placeholderId = request.placeholderId; errorId = request.errorId; + exceptionHandler = request.exceptionHandler; } //we don't want to change behavior in sets/maps, just be able to compare properties @@ -753,6 +841,8 @@ public boolean isIdenticalTo(Metadata metadata) { if (!modelClass.equals(metadata.modelClass)) return false; if (!modelLoaderClass.equals(metadata.modelLoaderClass)) return false; if (!transformationId.equals(metadata.transformationId)) return false; + if (exceptionHandler == null ? metadata.exceptionHandler != null : + !exceptionHandler.equals(metadata.exceptionHandler)) return false; return true; } diff --git a/library/tests/src/com/bumptech/glide/GlideTest.java b/library/tests/src/com/bumptech/glide/GlideTest.java index bde7a89a34..b543950a56 100644 --- a/library/tests/src/com/bumptech/glide/GlideTest.java +++ b/library/tests/src/com/bumptech/glide/GlideTest.java @@ -8,6 +8,7 @@ import com.bumptech.glide.loader.stream.StreamLoader; import com.bumptech.glide.presenter.ImagePresenter; import com.bumptech.glide.presenter.target.ImageViewTarget; +import com.bumptech.glide.presenter.target.Target; import com.bumptech.glide.tests.R; import java.io.File; @@ -213,6 +214,23 @@ public void testDifferentErrorIdsReplacesPresenter() { ); } + public void testDifferentExceptionHandlersReplacesPresenter() { + assertDifferentPresenters( + Glide.load("a").exception(new Glide.ExceptionHandler() { + + @Override + public void onException(Exception e, String model, Target target) { + + } + }), + Glide.load("a").exception(new Glide.ExceptionHandler() { + @Override + public void onException(Exception e, String model, Target target) { + } + }) + ); + } + private void assertDifferentPresenters(Glide.Request a, Glide.Request b) { a.into(imageView); ImagePresenter first = getImagePresenterFromView();