/**
* Listener for an async event.
* Sometimes you will see many similar listeners rather than templates.
*/
interface AsyncListener<T> {
/**
* Everything went okay and you use the result.
* You may not include the request object as a parameter,
* although I have found that it makes the API easier to use if you do.
*/
onSuccess(Object request, T result);
/**
* The async call failed for some reason.
* What type of class should ErrorInfo be?
*/
onError(Object request, ErrorInfo error);
}
Typically you will add this as a parameter to an async call, or you will have addListener() method for some kind of request object.
// As a parameter to an async call void execute(AsyncListener<Result> listener); // Using an addListener() method void addListener(AsyncListener<Result> listener); void execute();What kind of class should ErrorInfo be? You are going to use this class a lot so you are going to want to it to be robust and easy to understand. You are going to want to preserve all the information you need for logging and responding programmatically to different types of errors.
The following class is small and simple, yet has had a few iterations and is battle hardened from many projects.
A Favorite Story
/**
* An error that occurred that has to be reported to a listener or on another thread.
*/
public class ErrorInfo implements Parcelable {
private int mErrorCode = 0;
private String mDebugMessage;
private String mUserMessage;
private Bundle mContextData;
private Throwable mException;
/**
* Use Builder.
*/
protected ErrorInfo() {
}
/**
* @return Code that can be used for custom logic flow.
* 0 means no code has been set.
* Should be from an id defined in XML to avoid clashes.
*/
public int getErrorCode() {
return mErrorCode;
}
public String getDebugMessage() {
return mDebugMessage;
}
/**
* @return Message to be displayed to the user. This might be a special message from the server via an HTTP error
* response.
*/
public String getUserMessage() {
return mUserMessage;
}
public Bundle getContextData() {
return mContextData;
}
public Throwable getException() {
return mException;
}
@Override
public String toString() {
// Note: do not use reflectionToString() because we want these names kept after obfuscation.
ToStringBuilder builder = new ToStringBuilder(this, new ShortToStringStyle());
if (mErrorCode != 0) {
builder.append("errorCode", mErrorCode);
}
builder.append("debugMessage", mDebugMessage)
.append("userMessage", mUserMessage);
if (mContextData != null) {
// output the contextData keys individually so even if there is a really long key we will get a chance to
// see some of the other keys.
for (String key : new TreeSet<String>(mContextData.keySet())) {
String value = String.valueOf(mContextData.get(key));
builder.append("contextData:"+key, StringUtils.abbreviate(value, 1000));
}
}
if (mException != null) {
builder.append("exception", ExceptionUtils.getStackTrace(mException));
}
return builder.build();
}
/**
* Parcelable constructor.
*/
private ErrorInfo(Parcel in) {
mErrorCode = in.readInt();
mDebugMessage = in.readString();
mUserMessage = in.readString();
mContextData = in.readBundle();
mException = (Throwable) in.readSerializable();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeInt(mErrorCode);
out.writeString(mDebugMessage);
out.writeString(mUserMessage);
out.writeBundle(mContextData);
out.writeSerializable(mException);
}
public static final Creator<ErrorInfo> CREATOR = new Creator<ErrorInfo>() {
@Override
public ErrorInfo createFromParcel(Parcel in) {
return new ErrorInfo(in);
}
@Override
public ErrorInfo[] newArray(int size) {
return new ErrorInfo[size];
}
};
public static class Builder {
private ErrorInfo mError = new ErrorInfo();
public Builder setErrorCode(int errorCode) {
mError.mErrorCode = errorCode;
return this;
}
public Builder setDebugMessage(String debugMessage) {
mError.mDebugMessage = debugMessage;
return this;
}
public Builder setUserMessage(String userMessage) {
mError.mUserMessage = userMessage;
return this;
}
public Builder setContextData(Bundle contextData) {
mError.mContextData = contextData;
return this;
}
/**
* Convenience method for creating a bundle and adding a serializable to it.
*/
public Builder setContextData(Serializable contextData) {
if (mError.mContextData == null) {
mError.mContextData = new Bundle();
}
mError.mContextData.putSerializable("data", contextData);
return this;
}
/**
* Convenience method for creating a bundle and adding a serializable to it.
*/
public Builder setContextData(Parcelable contextData) {
if (mError.mContextData == null) {
mError.mContextData = new Bundle();
}
mError.mContextData.putParcelable("data", contextData);
return this;
}
/**
* Chains ErrorInfo using contextData. Copies debugMessage and userMessage if it has not been set yet.
*/
public Builder setContextData(ErrorInfo contextData) {
if (mError.mContextData == null) {
mError.mContextData = new Bundle();
}
mError.mContextData.putParcelable("caused_by", contextData);
if (mError.mErrorCode == 0) {
mError.mErrorCode = contextData.mErrorCode;
}
if (mError.mDebugMessage == null) {
mError.mDebugMessage = contextData.mDebugMessage;
}
if (mError.mUserMessage == null) {
mError.mUserMessage = contextData.mUserMessage;
}
return this;
}
public Builder setException(Throwable exception) {
mError.mException = exception;
return this;
}
public ErrorInfo build() {
if (mError.mException == null) {
mError.mException = new Exception("ErrorInfo built here");
}
return mError;
}
}
}
Usage
// When catching an exception
try {
Result result = calculateResult();
listener.onSuccess(this, result);
} catch (Exception ex) {
listener.onError(new ErrorInfo.Builder()
.setException(ex)
.build());
}
No comments:
Post a Comment