More transition to the generic API response handling.

This commit is contained in:
ojiikun 2013-07-02 08:33:25 +00:00
parent ada0817905
commit 2db5f3b6c3
15 changed files with 87 additions and 162 deletions

View file

@ -19,7 +19,7 @@ import com.newsblur.R;
import com.newsblur.activity.Login;
import com.newsblur.activity.Main;
import com.newsblur.network.APIManager;
import com.newsblur.network.domain.LoginResponse;
import com.newsblur.network.domain.NewsBlurResponse;
import com.newsblur.util.PrefsUtils;
import com.newsblur.util.UIUtils;
@ -68,7 +68,7 @@ public class LoginProgressFragment extends Fragment {
return v;
}
private class LoginTask extends AsyncTask<String, Void, LoginResponse> {
private class LoginTask extends AsyncTask<Void, Void, NewsBlurResponse> {
@Override
protected void onPreExecute() {
@ -77,15 +77,15 @@ public class LoginProgressFragment extends Fragment {
}
@Override
protected LoginResponse doInBackground(String... params) {
LoginResponse response = apiManager.login(username, password);
protected NewsBlurResponse doInBackground(Void... params) {
NewsBlurResponse response = apiManager.login(username, password);
apiManager.updateUserProfile();
return response;
}
@Override
protected void onPostExecute(LoginResponse result) {
if (result.isError()) {
protected void onPostExecute(NewsBlurResponse result) {
if (!result.isError()) {
final Animation a = AnimationUtils.loadAnimation(getActivity(), R.anim.text_down);
updateStatus.setText(R.string.login_logged_in);
loggingInProgress.setVisibility(View.GONE);

View file

@ -25,7 +25,6 @@ import com.newsblur.network.domain.RegisterResponse;
public class RegisterProgressFragment extends Fragment {
private APIManager apiManager;
private String TAG = "RegisterProgress";
private String username;
private String password;
@ -85,33 +84,17 @@ public class RegisterProgressFragment extends Fragment {
return v;
}
private class RegisterTask extends AsyncTask<Void, Void, Boolean> {
private RegisterResponse response;
private class RegisterTask extends AsyncTask<Void, Void, RegisterResponse> {
@Override
protected Boolean doInBackground(Void... params) {
try {
// We include this wait simply as a small UX convenience. Otherwise the user could be met with a disconcerting flicker when attempting to register and failing.
Thread.sleep(700);
} catch (InterruptedException e) {
Log.e(TAG, "Error sleeping during login.");
}
response = apiManager.signup(username, password, email);
return response.code != -1 && response.authenticated;
protected RegisterResponse doInBackground(Void... params) {
return apiManager.signup(username, password, email);
}
@Override
protected void onPostExecute(Boolean hasFolders) {
if (response.code != -1 && response.authenticated) {
if (!hasFolders.booleanValue()) {
switcher.showNext();
} else {
Intent i = new Intent(getActivity(), LoginProgress.class);
i.putExtra("username", username);
i.putExtra("password", password);
startActivity(i);
}
protected void onPostExecute(RegisterResponse response) {
if (response.authenticated) {
switcher.showNext();
} else {
Toast.makeText(getActivity(), extractErrorMessage(response), Toast.LENGTH_LONG).show();
startActivity(new Intent(getActivity(), Login.class));
@ -119,18 +102,15 @@ public class RegisterProgressFragment extends Fragment {
}
private String extractErrorMessage(RegisterResponse response) {
String errorMessage = null;
if(response.errors != null) {
if(response.errors.email != null && response.errors.email.length > 0) {
errorMessage = response.errors.email[0];
} else if(response.errors.username != null && response.errors.username.length > 0) {
errorMessage = response.errors.username[0];
} else if(response.errors.message != null && response.errors.message.length > 0) {
errorMessage = response.errors.message[0];
}
}
// TODO: do we ever see these mysterious email/username messages in practice?
String errorMessage = null;
if(response.email != null && response.email.length > 0) {
errorMessage = response.email[0];
} else if(response.username != null && response.username.length > 0) {
errorMessage = response.username[0];
}
if(errorMessage == null) {
errorMessage = getResources().getString(R.string.login_message_error);
errorMessage = response.getErrorMessage(getResources().getString(R.string.login_message_error));
}
return errorMessage;
}

View file

@ -44,7 +44,6 @@ import com.newsblur.domain.ValueMultimap;
import com.newsblur.network.domain.CategoriesResponse;
import com.newsblur.network.domain.FeedFolderResponse;
import com.newsblur.network.domain.FeedRefreshResponse;
import com.newsblur.network.domain.LoginResponse;
import com.newsblur.network.domain.NewsBlurResponse;
import com.newsblur.network.domain.ProfileResponse;
import com.newsblur.network.domain.RegisterResponse;
@ -74,12 +73,12 @@ public class APIManager {
.create();
}
public LoginResponse login(final String username, final String password) {
public NewsBlurResponse login(final String username, final String password) {
final ContentValues values = new ContentValues();
values.put(APIConstants.PARAMETER_USERNAME, username);
values.put(APIConstants.PARAMETER_PASSWORD, password);
final APIResponse response = post(APIConstants.URL_LOGIN, values);
LoginResponse loginResponse = ((LoginResponse) response.getResponse(gson, LoginResponse.class));
NewsBlurResponse loginResponse = response.getResponse(gson);
if (!response.isError()) {
PrefsUtils.saveLogin(context, username, response.getCookie());
}
@ -90,7 +89,6 @@ public class APIManager {
ContentValues values = new ContentValues();
values.put("autofollow_friends", autofollow ? "true" : "false");
final APIResponse response = post(APIConstants.URL_AUTOFOLLOW_PREF, values);
response.close();
return (!response.isError());
}
@ -100,7 +98,6 @@ public class APIManager {
values.put(APIConstants.PARAMETER_CATEGORY, URLEncoder.encode(category));
}
final APIResponse response = post(APIConstants.URL_ADD_CATEGORIES, values, false);
response.close();
return (!response.isError());
}
@ -110,7 +107,6 @@ public class APIManager {
values.put(APIConstants.PARAMETER_FEEDID, feedId);
}
final APIResponse response = post(APIConstants.URL_MARK_FEED_AS_READ, values, false);
response.close();
if (!response.isError()) {
return true;
} else {
@ -122,7 +118,6 @@ public class APIManager {
final ValueMultimap values = new ValueMultimap();
values.put(APIConstants.PARAMETER_DAYS, "0");
final APIResponse response = post(APIConstants.URL_MARK_ALL_AS_READ, values, false);
response.close();
if (!response.isError()) {
return true;
} else {
@ -134,7 +129,6 @@ public class APIManager {
final ContentValues values = new ContentValues();
values.put(APIConstants.PARAMETER_MARKSOCIAL_JSON, updateJson);
final APIResponse response = post(APIConstants.URL_MARK_SOCIALSTORY_AS_READ, values);
response.close();
if (!response.isError()) {
return true;
} else {
@ -161,8 +155,7 @@ public class APIManager {
public CategoriesResponse getCategories() {
final APIResponse response = get(APIConstants.URL_CATEGORIES);
if (!response.isError()) {
CategoriesResponse categoriesResponse = gson.fromJson(response.getGsonReader(), CategoriesResponse.class);
response.close();
CategoriesResponse categoriesResponse = (CategoriesResponse) response.getResponse(gson, CategoriesResponse.class);
return categoriesResponse;
} else {
return null;
@ -175,9 +168,8 @@ public class APIManager {
values.put(APIConstants.PARAMETER_PASSWORD, password);
values.put(APIConstants.PARAMETER_EMAIL, email);
final APIResponse response = post(APIConstants.URL_SIGNUP, values);
RegisterResponse registerResponse = ((RegisterResponse) response.getResponse(gson, RegisterResponse.class));
if (!response.isError()) {
RegisterResponse registerResponse = gson.fromJson(response.getGsonReader(), LoginResponse.class);
response.close();
PrefsUtils.saveLogin(context, username, response.getCookie());
CookieSyncManager.createInstance(context.getApplicationContext());
@ -185,18 +177,14 @@ public class APIManager {
cookieManager.setCookie(".newsblur.com", response.getCookie());
CookieSyncManager.getInstance().sync();
return registerResponse;
} else {
return new RegisterResponse();
}
}
return registerResponse;
}
public ProfileResponse updateUserProfile() {
final APIResponse response = get(APIConstants.URL_MY_PROFILE);
if (!response.isError()) {
ProfileResponse profileResponse = gson.fromJson(response.getGsonReader(), ProfileResponse.class);
response.close();
ProfileResponse profileResponse = (ProfileResponse) response.getResponse(gson, ProfileResponse.class);
PrefsUtils.saveUserDetails(context, profileResponse.user);
return profileResponse;
} else {
@ -219,8 +207,7 @@ public class APIManager {
if (TextUtils.equals(pageNumber, "1")) {
contentResolver.delete(storyUri, null, null);
}
StoriesResponse storiesResponse = gson.fromJson(response.getGsonReader(), StoriesResponse.class);
response.close();
StoriesResponse storiesResponse = (StoriesResponse) response.getResponse(gson, StoriesResponse.class);
Uri classifierUri = FeedProvider.CLASSIFIER_URI.buildUpon().appendPath(feedId).build();
@ -257,8 +244,7 @@ public class APIManager {
values.put(APIConstants.PARAMETER_READ_FILTER, filter.getParameterValue());
final APIResponse response = get(APIConstants.URL_RIVER_STORIES, values);
StoriesResponse storiesResponse = gson.fromJson(response.getGsonReader(), StoriesResponse.class);
response.close();
StoriesResponse storiesResponse = (StoriesResponse) response.getResponse(gson, StoriesResponse.class);
if (!response.isError()) {
if (TextUtils.equals(pageNumber,"1")) {
Uri storyUri = FeedProvider.ALL_STORIES_URI;
@ -288,8 +274,7 @@ public class APIManager {
}
final APIResponse response = get(APIConstants.URL_STARRED_STORIES, values);
StoriesResponse storiesResponse = gson.fromJson(response.getGsonReader(), StoriesResponse.class);
response.close();
StoriesResponse storiesResponse = (StoriesResponse) response.getResponse(gson, StoriesResponse.class);
if (!response.isError()) {
if (TextUtils.equals(pageNumber,"1")) {
contentResolver.delete(FeedProvider.STARRED_STORIES_URI, null, null);
@ -317,9 +302,7 @@ public class APIManager {
}
final APIResponse response = get(APIConstants.URL_SHARED_RIVER_STORIES, values);
SocialFeedResponse storiesResponse = gson.fromJson(response.getGsonReader(), SocialFeedResponse.class);
response.close();
SocialFeedResponse storiesResponse = (SocialFeedResponse) response.getResponse(gson, SocialFeedResponse.class);
if (!response.isError()) {
// If we've successfully retrieved the latest stories for all shared feeds (the first page), delete all previous shared feeds
@ -365,8 +348,7 @@ public class APIManager {
}
Uri feedUri = Uri.parse(APIConstants.URL_SOCIALFEED_STORIES).buildUpon().appendPath(userId).appendPath(username).build();
final APIResponse response = get(feedUri.toString(), values);
SocialFeedResponse socialFeedResponse = gson.fromJson(response.getGsonReader(), SocialFeedResponse.class);
response.close();
SocialFeedResponse socialFeedResponse = (SocialFeedResponse) response.getResponse(gson, SocialFeedResponse.class);
if (!response.isError()) {
Uri storySocialUri = FeedProvider.SOCIALFEED_STORIES_URI.buildUpon().appendPath(userId).build();
@ -434,7 +416,6 @@ public class APIManager {
final ContentValues values = new ContentValues();
values.put(APIConstants.PARAMETER_USERID, userId);
final APIResponse response = post(APIConstants.URL_FOLLOW, values);
response.close();
if (!response.isError()) {
return true;
} else {
@ -446,7 +427,6 @@ public class APIManager {
final ContentValues values = new ContentValues();
values.put(APIConstants.PARAMETER_USERID, userId);
final APIResponse response = post(APIConstants.URL_UNFOLLOW, values);
response.close();
if (!response.isError()) {
return true;
} else {
@ -466,7 +446,6 @@ public class APIManager {
values.put(APIConstants.PARAMETER_STORYID, storyId);
final APIResponse response = post(APIConstants.URL_SHARE_STORY, values);
response.close();
if (!response.isError()) {
return true;
} else {
@ -489,8 +468,7 @@ public class APIManager {
final APIResponse response = get(APIConstants.URL_FEEDS, params);
// note: this response is complex enough, we have to do a custom parse in the FFR
final FeedFolderResponse feedUpdate = new FeedFolderResponse(response.getGsonReader(), gson);
response.close();
final FeedFolderResponse feedUpdate = new FeedFolderResponse(response.getResponseBody(), gson);
// there is a rare issue with feeds that have no folder. capture them for debug.
List<String> debugFeedIds = new ArrayList<String>();
@ -610,7 +588,6 @@ public class APIManager {
values.put(APIConstants.PARAMETER_FEEDID, feedId);
final APIResponse response = post(APIConstants.URL_CLASSIFIER_SAVE, values);
response.close();
return (!response.isError());
}
@ -619,8 +596,7 @@ public class APIManager {
values.put(APIConstants.PARAMETER_USER_ID, userId);
final APIResponse response = get(APIConstants.URL_USER_PROFILE, values);
if (!response.isError()) {
ProfileResponse profileResponse = gson.fromJson(response.getGsonReader(), ProfileResponse.class);
response.close();
ProfileResponse profileResponse = (ProfileResponse) response.getResponse(gson, ProfileResponse.class);
return profileResponse;
} else {
return null;
@ -630,8 +606,7 @@ public class APIManager {
public void refreshFeedCounts() {
final APIResponse response = get(APIConstants.URL_FEED_COUNTS);
if (!response.isError()) {
final FeedRefreshResponse feedCountUpdate = gson.fromJson(response.getGsonReader(), FeedRefreshResponse.class);
response.close();
final FeedRefreshResponse feedCountUpdate = (FeedRefreshResponse) response.getResponse(gson, FeedRefreshResponse.class);
for (String feedId : feedCountUpdate.feedCounts.keySet()) {
Uri feedUri = FeedProvider.FEEDS_URI.buildUpon().appendPath(feedId).build();
if (feedCountUpdate.feedCounts.get(feedId) != null) {
@ -655,7 +630,6 @@ public class APIManager {
values.put(APIConstants.PARAMETER_STORY_FEEDID, feedId);
values.put(APIConstants.PARAMETER_COMMENT_USERID, commentId);
final APIResponse response = post(APIConstants.URL_LIKE_COMMENT, values);
response.close();
return (!response.isError());
}
@ -665,7 +639,6 @@ public class APIManager {
values.put(APIConstants.PARAMETER_STORY_FEEDID, feedId);
values.put(APIConstants.PARAMETER_COMMENT_USERID, commentId);
final APIResponse response = post(APIConstants.URL_UNLIKE_COMMENT, values);
response.close();
return (!response.isError());
}
@ -676,13 +649,11 @@ public class APIManager {
values.put(APIConstants.PARAMETER_COMMENT_USERID, commentUserId);
values.put(APIConstants.PARAMETER_REPLY_TEXT, reply);
final APIResponse response = post(APIConstants.URL_REPLY_TO, values);
response.close();
return (!response.isError());
}
public boolean markMultipleStoriesAsRead(ContentValues values) {
final APIResponse response = post(APIConstants.URL_MARK_FEED_STORIES_AS_READ, values);
response.close();
if (!response.isError()) {
return true;
} else {
@ -697,7 +668,6 @@ public class APIManager {
values.put(APIConstants.PARAMETER_FOLDER, folderName);
}
final APIResponse response = post(APIConstants.URL_ADD_FEED, values);
response.close();
return (!response.isError());
}
@ -707,9 +677,7 @@ public class APIManager {
final APIResponse response = get(APIConstants.URL_FEED_AUTOCOMPLETE, values);
if (!response.isError()) {
FeedResult[] feedResult = gson.fromJson(response.getGsonReader(), FeedResult[].class);
response.close();
return feedResult;
return gson.fromJson(response.getResponseBody(), FeedResult[].class);
} else {
return null;
}
@ -722,7 +690,6 @@ public class APIManager {
values.put(APIConstants.PARAMETER_IN_FOLDER, folderName);
}
final APIResponse response = post(APIConstants.URL_DELETE_FEED, values);
response.close();
return (!response.isError());
}

View file

@ -4,13 +4,13 @@ import java.io.InputStreamReader;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Scanner;
import android.content.Context;
import android.text.TextUtils;
import android.util.Log;
import com.google.gson.Gson;
import com.google.gson.stream.JsonReader;
import org.apache.http.HttpStatus;
import com.newsblur.R;
@ -25,11 +25,10 @@ import com.newsblur.network.domain.NewsBlurResponse;
public class APIResponse {
private Context context;
private HttpURLConnection connection;
private boolean isError;
private String errorMessage;
private String cookie;
private JsonReader gsonReader;
private String responseBody;
/**
* Construct an online response. Will test the response for errors and extract all the
@ -37,9 +36,6 @@ public class APIResponse {
*/
public APIResponse(Context context, URL originalUrl, HttpURLConnection connection) {
this.context = context;
this.connection = connection;
this.errorMessage = context.getResources().getString(R.string.error_unset_message);
try {
@ -66,15 +62,25 @@ public class APIResponse {
this.cookie = connection.getHeaderField("Set-Cookie");
// make a GSON streaming reader for the response
try {
this.gsonReader = new JsonReader(new InputStreamReader(connection.getInputStream(), "UTF-8"));
StringBuilder builder = new StringBuilder();
Scanner scanner = new Scanner(connection.getInputStream(), "UTF-8");
while (scanner.hasNextLine()) { builder.append(scanner.nextLine()); }
this.responseBody = builder.toString();
} catch (Exception e) {
Log.e(this.getClass().getName(), e.getClass().getName() + " (" + e.getMessage() + ") calling " + originalUrl, e);
Log.e(this.getClass().getName(), e.getClass().getName() + " (" + e.getMessage() + ") reading " + originalUrl, e);
this.isError = true;
this.errorMessage = context.getResources().getString(R.string.error_read_connection);
return;
}
Log.d(this.getClass().getName(), "received API response: \n" + this.responseBody);
try {
connection.disconnect();
} catch (Exception e) {
Log.e(this.getClass().getName(), e.getClass().getName() + " caught closing connection: " + e.getMessage(), e);
}
}
@ -82,7 +88,6 @@ public class APIResponse {
* Construct and empty/offline response. Signals that the call was not made.
*/
public APIResponse(Context context) {
this.context = context;
this.isError = true;
this.errorMessage = context.getResources().getString(R.string.error_offline);
}
@ -91,13 +96,6 @@ public class APIResponse {
return this.isError;
}
/**
* Get the GSON reader that will have been left open for use if the API call was successful.
*/
public JsonReader getGsonReader() {
return this.gsonReader;
}
/**
* Get the response object from this call. A specific subclass of NewsBlurResponse
* may be used for calls that return data, or the parent class may be used if no
@ -110,28 +108,24 @@ public class APIResponse {
// it's message field
NewsBlurResponse response = new NewsBlurResponse();
response.message = this.errorMessage;
this.close();
return ((T) response);
} else {
// otherwise, parse the response as the expected class and defer error detection
// to the NewsBlurResponse parent class
T response = gson.fromJson(this.gsonReader, classOfT);
this.close();
return response;
return gson.fromJson(this.responseBody, classOfT);
}
}
public NewsBlurResponse getResponse(Gson gson) {
return getResponse(gson, NewsBlurResponse.class);
}
public String getResponseBody() {
return this.responseBody;
}
public String getCookie() {
return this.cookie;
}
public void close() {
try {
if (this.connection != null) this.connection.disconnect();
if (this.gsonReader != null) this.gsonReader.close();
} catch (Exception e) {
Log.e(this.getClass().getName(), "Error closing API connection.", e);
}
}
}

View file

@ -6,7 +6,7 @@ import com.google.gson.annotations.SerializedName;
import com.newsblur.domain.Category;
import com.newsblur.domain.Feed;
public class CategoriesResponse {
public class CategoriesResponse extends NewsBlurResponse {
@SerializedName("feeds")
public HashMap<String, Feed> feeds;

View file

@ -36,8 +36,9 @@ public class FeedFolderResponse {
public boolean isAuthenticated;
public FeedFolderResponse(JsonReader json, Gson gson) {
public FeedFolderResponse(String json, Gson gson) {
// TODO: is there really any good reason the default GSON parser doesn't work here?
JsonParser parser = new JsonParser();
JsonObject asJsonObject = parser.parse(json).getAsJsonObject();

View file

@ -7,7 +7,7 @@ import android.content.ContentValues;
import com.google.gson.annotations.SerializedName;
import com.newsblur.database.DatabaseConstants;
public class FeedRefreshResponse {
public class FeedRefreshResponse extends NewsBlurResponse {
@SerializedName("feeds")

View file

@ -1,7 +0,0 @@
package com.newsblur.network.domain;
public class LoginResponse extends NewsBlurResponse {
public String result;
}

View file

@ -1,11 +0,0 @@
package com.newsblur.network.domain;
public class Message {
// {"message": "Overloaded, no autocomplete results.", "code": -1, "authenticated": true, "result": "ok"}
public String message;
public int code;
public boolean authenticated;
public String result;
}

View file

@ -8,18 +8,28 @@ public class NewsBlurResponse {
public boolean authenticated;
public int code;
public String message;
public String[] errors;
public ResponseErrors errors;
public String result;
public boolean isError() {
if (message != null) return true;
if ((errors != null) && (errors.length > 0) && (errors[0] != null)) return true;
if ((errors != null) && (errors.message.length > 0) && (errors.message[0] != null)) return true;
return false;
}
public String getErrorMessage() {
/**
* Gets the error message returned by the API, or defaultMessage if none was found.
*/
public String getErrorMessage(String defaultMessage) {
if (message != null) return message;
if ((errors != null) && (errors.length > 0) && (errors[0] != null)) return errors[0];
return Integer.toString(code);
if ((errors != null) && (errors.message.length > 0) && (errors.message[0] != null)) return errors.message[0];
return defaultMessage;
}
/**
* Gets the error message returned by the API, or a simple numeric error code if non was found.
*/
public String getErrorMessage() {
return getErrorMessage(Integer.toString(code));
}
}

View file

@ -3,7 +3,7 @@ package com.newsblur.network.domain;
import com.google.gson.annotations.SerializedName;
import com.newsblur.domain.UserDetails;
public class ProfileResponse {
public class ProfileResponse extends NewsBlurResponse {
@SerializedName("user_profile")
public UserDetails user;

View file

@ -1,10 +1,8 @@
package com.newsblur.network.domain;
public class RegisterResponse {
public class RegisterResponse extends NewsBlurResponse {
public boolean authenticated;
public int code;
public LoginErrors errors;
public String result;
public String[] email;
public String[] username;
}

View file

@ -2,7 +2,7 @@ package com.newsblur.network.domain;
import com.google.gson.annotations.SerializedName;
public class LoginErrors {
public class ResponseErrors {
@SerializedName("__all__")
public String[] message;

View file

@ -1,16 +1,12 @@
package com.newsblur.network.domain;
import java.io.Serializable;
import com.google.gson.annotations.SerializedName;
import com.newsblur.domain.Feed;
import com.newsblur.domain.Story;
import com.newsblur.domain.UserProfile;
public class SocialFeedResponse implements Serializable {
public class SocialFeedResponse extends NewsBlurResponse {
private static final long serialVersionUID = 1L;
@SerializedName("stories")
public Story[] stories;
@ -20,6 +16,5 @@ public class SocialFeedResponse implements Serializable {
@SerializedName("user_profiles")
public UserProfile[] userProfiles;
public boolean authenticated;
}
}

View file

@ -5,7 +5,7 @@ import com.newsblur.domain.Classifier;
import com.newsblur.domain.Story;
import com.newsblur.domain.UserProfile;
public class StoriesResponse {
public class StoriesResponse extends NewsBlurResponse {
@SerializedName("stories")
public Story[] stories;
@ -15,6 +15,4 @@ public class StoriesResponse {
public Classifier classifiers;
public boolean authenticated;
}