Skip to content

Commit

Permalink
Add API to start a new Request when a load fails.
Browse files Browse the repository at this point in the history
We’re going with .error() to match the behavior of the .error Drawable
and resource id methods. fallback() is a somewhat better name, but we
unfortunately already use it to handle cases where models are null and
expected to be null.

Fixes #451.
  • Loading branch information
sjudd committed Oct 7, 2017
1 parent a136852 commit b06b0cc
Show file tree
Hide file tree
Showing 5 changed files with 249 additions and 11 deletions.
80 changes: 78 additions & 2 deletions library/src/main/java/com/bumptech/glide/RequestBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import android.support.annotation.Nullable;
import android.widget.ImageView;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.request.ErrorRequestCoordinator;
import com.bumptech.glide.request.FutureTarget;
import com.bumptech.glide.request.Request;
import com.bumptech.glide.request.RequestCoordinator;
Expand Down Expand Up @@ -55,6 +56,7 @@ public class RequestBuilder<TranscodeType> implements Cloneable {
// than relying on model not to be null.
@Nullable private RequestListener<TranscodeType> requestListener;
@Nullable private RequestBuilder<TranscodeType> thumbnailBuilder;
@Nullable private RequestBuilder<TranscodeType> errorBuilder;
@Nullable private Float thumbSizeMultiplier;
private boolean isDefaultTransitionOptionsSet = true;
private boolean isModelSet;
Expand Down Expand Up @@ -115,7 +117,7 @@ public RequestBuilder<TranscodeType> transition(
}

/**
* Sets a RequestBuilder listener to monitor the resource load. It's best to create a single
* Sets a {@link RequestListener} to monitor the resource load. It's best to create a single
* instance of an exception handler per type of request (usually activity/fragment) rather than
* pass one in per request to avoid some redundant object allocation.
*
Expand All @@ -131,6 +133,34 @@ public RequestBuilder<TranscodeType> listener(
return this;
}

/**
* Sets a {@link RequestBuilder} that is built and run iff the load started by this
* {@link RequestBuilder} fails.
*
* <p>If this {@link RequestBuilder} uses a thumbnail that succeeds the given error
* {@link RequestBuilder} will be started anyway if the non-thumbnail request fails.
*
* <p>Recursive calls to {@link #error(RequestBuilder)} as well as calls to
* {@link #thumbnail(float)} and {@link #thumbnail(RequestBuilder)} are supported for the given
* error {@link RequestBuilder}.
*
* <p>Unlike {@link #thumbnail(RequestBuilder)} and {@link #thumbnail(float)}, no options from
* this primary {@link RequestBuilder} are propagated to the given error {@link RequestBuilder}.
* Options like priority, override widths and heights and transitions must be applied
* independently to the error builder.
*
* <p>The given {@link RequestBuilder} will start and potentially override a fallback drawable
* if it's set on this {@link RequestBuilder} via
* {@link RequestOptions#fallback(android.graphics.drawable.Drawable)} or
* {@link RequestOptions#fallback(int)}.
*
* @return This {@link RequestBuilder}.
*/
public RequestBuilder<TranscodeType> error(@Nullable RequestBuilder<TranscodeType> errorBuilder) {
this.errorBuilder = errorBuilder;
return this;
}

/**
* Loads and displays the resource retrieved by the given thumbnail request if it finishes before
* this request. Best used for loading thumbnail resources that are smaller and will be loaded
Expand Down Expand Up @@ -607,9 +637,55 @@ private Request buildRequest(Target<TranscodeType> target, RequestOptions reques
}

private Request buildRequestRecursive(Target<TranscodeType> target,
@Nullable ThumbnailRequestCoordinator parentCoordinator,
@Nullable RequestCoordinator parentCoordinator,
TransitionOptions<?, ? super TranscodeType> transitionOptions,
Priority priority, int overrideWidth, int overrideHeight, RequestOptions requestOptions) {

// Build the ErrorRequestCoordinator first if necessary so we can update parentCoordinator.
ErrorRequestCoordinator errorRequestCoordinator = null;
if (errorBuilder != null) {
errorRequestCoordinator = new ErrorRequestCoordinator(parentCoordinator);
parentCoordinator = errorRequestCoordinator;
}

Request mainRequest =
buildThumbnailRequestRecursive(
target,
parentCoordinator,
transitionOptions,
priority,
overrideWidth,
overrideHeight,
requestOptions);

if (errorRequestCoordinator == null) {
return mainRequest;
}

int errorOverrideWidth = errorBuilder.requestOptions.getOverrideWidth();
int errorOverrideHeight = errorBuilder.requestOptions.getOverrideHeight();
if (Util.isValidDimensions(overrideWidth, overrideHeight)
&& !errorBuilder.requestOptions.isValidOverride()) {
errorOverrideWidth = requestOptions.getOverrideWidth();
errorOverrideHeight = requestOptions.getOverrideHeight();
}

Request errorRequest = errorBuilder.buildRequestRecursive(
target,
errorRequestCoordinator,
errorBuilder.transitionOptions,
errorBuilder.requestOptions.getPriority(),
errorOverrideWidth,
errorOverrideHeight,
errorBuilder.requestOptions);
errorRequestCoordinator.setRequests(mainRequest, errorRequest);
return errorRequestCoordinator;
}

private Request buildThumbnailRequestRecursive(Target<TranscodeType> target,
@Nullable RequestCoordinator parentCoordinator,
TransitionOptions<?, ? super TranscodeType> transitionOptions,
Priority priority, int overrideWidth, int overrideHeight, RequestOptions requestOptions) {
if (thumbnailBuilder != null) {
// Recursive case: contains a potentially recursive thumbnail request builder.
if (isThumbnailBuilt) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
package com.bumptech.glide.request;

/**
* Runs a single primary {@link Request} until it completes and then a fallback error request only
* if the single primary request fails.
*/
public final class ErrorRequestCoordinator implements RequestCoordinator,
Request {

private final RequestCoordinator coordinator;
private Request primary;
private Request error;

public ErrorRequestCoordinator(RequestCoordinator coordinator) {
this.coordinator = coordinator;
}

public void setRequests(Request primary, Request error) {
this.primary = primary;
this.error = error;
}

@Override
public void begin() {
if (!primary.isRunning()) {
primary.begin();
}
}

@Override
public void pause() {
if (!primary.isFailed()) {
primary.pause();
}
if (error.isRunning()) {
error.pause();
}
}

@Override
public void clear() {
if (primary.isFailed()) {
error.clear();
} else {
primary.clear();
}
}

@Override
public boolean isPaused() {
return primary.isFailed() ? error.isPaused() : primary.isPaused();
}

@Override
public boolean isRunning() {
return primary.isFailed() ? error.isRunning() : primary.isRunning();
}

@Override
public boolean isComplete() {
return primary.isFailed() ? error.isComplete() : primary.isComplete();
}

@Override
public boolean isResourceSet() {
return primary.isFailed() ? error.isResourceSet() : primary.isResourceSet();
}

@Override
public boolean isCancelled() {
return primary.isFailed() ? error.isCancelled() : primary.isCancelled();
}

@Override
public boolean isFailed() {
return primary.isFailed() && error.isFailed();
}

@Override
public void recycle() {
primary.recycle();
error.recycle();
}

@Override
public boolean isEquivalentTo(Request o) {
if (o instanceof ErrorRequestCoordinator) {
ErrorRequestCoordinator other = (ErrorRequestCoordinator) o;
return primary.isEquivalentTo(other.primary) && error.isEquivalentTo(other.error);
}
return false;
}

@Override
public boolean canSetImage(Request request) {
return parentCanSetImage() && isValidRequest(request);
}

private boolean parentCanSetImage() {
return coordinator == null || coordinator.canSetImage(this);
}

@Override
public boolean canNotifyStatusChanged(Request request) {
return parentCanNotifyStatusChanged() && isValidRequest(request);
}

private boolean parentCanNotifyStatusChanged() {
return coordinator == null || coordinator.canNotifyStatusChanged(this);
}

private boolean isValidRequest(Request request) {
return request.equals(primary) || (primary.isFailed() && request.equals(error));
}

@Override
public boolean isAnyResourceSet() {
return parentIsAnyResourceSet() || isResourceSet();
}

private boolean parentIsAnyResourceSet() {
return coordinator != null && coordinator.isAnyResourceSet();
}

@Override
public void onRequestSuccess(Request request) {
if (coordinator != null) {
coordinator.onRequestSuccess(this);
}
}

@Override
public void onRequestFailed(Request request) {
if (!request.equals(error)) {
if (!error.isRunning()) {
error.begin();
}
return;
}

if (coordinator != null) {
coordinator.onRequestFailed(error);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ public interface RequestCoordinator {
boolean isAnyResourceSet();

/**
* Must be called when a request coordinated by this object completes successfully.
* Must be called when a {@link Request} coordinated by this object completes successfully.
*/
void onRequestSuccess(Request request);

/** Must be called when a {@link Request} coordinated by this object fails. */
void onRequestFailed(Request request);
}
Original file line number Diff line number Diff line change
Expand Up @@ -277,8 +277,9 @@ void cancel() {
private void assertNotCallingCallbacks() {
if (isCallingCallbacks) {
throw new IllegalStateException("You can't start or clear loads in RequestListener or"
+ " Target callbacks. If you must do so, consider posting your into() or clear() calls"
+ " to the main thread using a Handler instead.");
+ " Target callbacks. If you're trying to start a fallback request when a load fails, use"
+ " RequestBuilder#error(RequestBuilder). Otherwise consider posting your into() or"
+ " clear() calls to the main thread using a Handler instead.");
}
}

Expand Down Expand Up @@ -492,6 +493,12 @@ private void notifyLoadSuccess() {
}
}

private void notifyLoadFailed() {
if (requestCoordinator != null) {
requestCoordinator.onRequestFailed(this);
}
}

/**
* A callback method that should never be invoked directly.
*/
Expand Down Expand Up @@ -595,6 +602,8 @@ private void onLoadFailed(GlideException e, int maxLogLevel) {
} finally {
isCallingCallbacks = false;
}

notifyLoadFailed();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,17 @@ public void onRequestSuccess(Request request) {
}
}

@Override
public void onRequestFailed(Request request) {
if (!request.equals(full)) {
return;
}

if (coordinator != null) {
coordinator.onRequestFailed(this);
}
}

private boolean parentIsAnyResourceSet() {
return coordinator != null && coordinator.isAnyResourceSet();
}
Expand All @@ -102,9 +113,6 @@ public void pause() {
thumb.pause();
}

/**
* {@inheritDoc}
*/
@Override
public void clear() {
isRunning = false;
Expand Down Expand Up @@ -151,9 +159,6 @@ public boolean isFailed() {
return full.isFailed();
}

/**
* {@inheritDoc}.
*/
@Override
public void recycle() {
full.recycle();
Expand Down

0 comments on commit b06b0cc

Please sign in to comment.