Browse Reddit with AeroGear

Posted By Hoyt Summers Pittman

Reddit is an online community with a RESTful API. If you havn’t had the chance to participate in the “Front Page of the Internet” I suggest you take this chance to. It is a great site with lots of content and a great community. It is also all open sourced. (www.github.com/reddit)

For the last few months I’ve been working on the Android version of the AeroGear library. AeroGear is a library made by JBoss to facilitate consuming RESTful services on mobile devices. Each platform follows a common pattern which complements a server API. It is all really neat and you should check out aerogear.org. AeroGear also provides for a simple REST consumption API.

Since Reddit offers a large and reasonably well documented API as well as good community support, I decided to give AeroGear’s generic REST support a whirl. I would 1) download the Reddit front page anonymously, 2) download a logged in user’s front page and 3) display articles in a WebView.

The full source of my demo is on github, and I will only detail the AG specific parts here.

AeroGear has a concept of Pipelines and Pipes. A Pipeline is a collection of data endpoints on a common server. A Pipe is a endpoint for a single type of object. For example, An AeroGear based blogging service can be represented as a Pipeline on the client. Pipes on this service would exist for Posts, Authors, Comments, etc. On an AeroGear server a single Pipe can perform all CRUD operations. In the case of third party sites such as Reddit, this metaphor may not be enforced and multiple Pipes may have to be created to support the same type.

Now with all this out of the way, let’s consume the anonymous front page.

public class StoryListApplication extends Application {

	Pipeline pipeline; //1

	@Override
	public void onCreate() {
		super.onCreate();
		URL redditURL;
		try {
			redditURL = new URL(getString(R.string.reddit_base));
		} catch (MalformedURLException e) {
			throw new RuntimeException(e);
		}
		pipeline = new Pipeline(redditURL);
		
		PipeConfig config = new PipeConfig(redditURL, Listing.class);//2
		config.setGsonBuilder(new GsonBuilder().registerTypeAdapter(Listing.class, new ListingTypeAdapter()));
		config.setEndpoint(".json");
		
		pipeline.pipe(Listing.class, config);//3
		
	}

	public Pipe<Listing> getListing() {
		return pipeline.get(Listing.class.getSimpleName().toLowerCase());//4
	}
	
}

This code should be fairly simple.

  1. Defines the pipeline field.
  2. The PipeConfig is a value object which can be filled with common values and then parsed by Pipeline into a Pipe object. AeroGear Android uses the gson library to perform object serialization and deserialization. In this block, it is configured to deserialize Reddit “Listing” objects.
  3. We add our configuration to the Pipeline and create a Pipe object.
  4. We may fetch any Pipe managed by the system using a key that we passed in. By default this key is the simle name of the generic class of the Pipe.

To actually fetch data

public void reload() {
		StoryListApplication applicaiton = (StoryListApplication) getActivity().getApplication();
		Pipe<Listing> listing = applicaiton.getListing();
		listing.read(new Callback<List<Listing>>() {

			public void onSuccess(List<Listing> data) {
				Log.d("Reddt", "success");
				Log.d("Reddit", data.toString());
				setListAdapter(new ArrayAdapter<T3>(getActivity(),
		                R.layout.simple_list_item_activated_1,
		                R.id.text1,
		                data.get(0).getData().getChildren()));
			}

			public void onFailure(Exception e) {
				Log.d("Reddt", "failure", e);
				if (e instanceof HttpException) {
					HttpException httpException = (HttpException) e;
					Log.d("Reddit", new String(httpException.getData()));
				}
			}
			
		});
	}

This code gets a reference to the Pipe object and calls the read method with a Callback. The callback method onSuccess is called after the network operation is completed and is passed a collection of the objects returned from the server. If an error occurs, the onFailure method is called instead,

Now we can pull the data and display it in a ListView (code not shown, but use your imagination. Or you can check out the repo and run the app),

Now on to authentication and authorization. This is also relatively easy to do, but requires a bit of knowledge about the Reddit API first.

Reddit authentication is done by POSTing the users username and password to “http://www.reddit.com/api/login/${username}”. This will return a json object with a cookie and a modhash. This cookie is the reddit_session cookie, and the modhash is a value to be set for the query parameter “uh” aka user hash.

AeroGear provides a few classes to support authentication as well. There is Authenticator (a factory/collection class similar to Pipeline), AuthConfig (an analog to PipeConfig) and AuthenticationModule (similar to Pipe). For simplicity’s sake, we will implement AuthenticationModule and instantiate it directly. AuthenticationModules may be passed to a PipeConfig before you create the Pipe to support authentication and authorization. The AuthenticationModule provides some basic methods for registering (called enroll()), logging in (login()), and logging out(logout()). There is an abstract class which provides default implementations of these methods which throw exceptions if they are called. Additionally there is a method, getAuthoriztionFields() which returns the current Authorizaiton data to the user.


public class RedditAuthenticationModule extends AbstractAuthenticationModule {

	private String authToken = "";
	private String modHash;

	public void login(final String username, final String password,
		    final Callback<HeaderAndBody> callback) {
        new AsyncTask<Void, Void, Void>() {
            private Exception exception;
            private HeaderAndBody result;

            @Override
            protected Void doInBackground(Void... params) {
                
                try {
                    HttpProvider provider = new HttpRestProvider(getLoginURL(username));
                    provider.setDefaultHeader("User-Agent", "AeroGear StoryList Demo /u/secondsun");
                    provider.setDefaultHeader("Content-Type", "application/x-www-form-urlencoded");
                    
                    String loginData = buildLoginData(username, password);
                    result = provider.post(loginData);
                    
                    String json = new String(result.getBody());
                    JsonObject obj = new JsonParser().parse(json).getAsJsonObject().get("json").getAsJsonObject();
                    modHash = obj.get("data").getAsJsonObject().get("modhash").getAsString();
                    authToken = obj.get("data").getAsJsonObject().get("cookie").getAsString();
                    isLoggedIn = true;
                } catch (Exception e) {
                    Log.e(RedditAuthenticationModule.class.getSimpleName(), 
                    	  "Error with Login", e);
                    exception = e;
                }
                return null;
            }

	    @Override
            protected void onPostExecute(Void ignore) {
                super.onPostExecute(ignore);
                if (exception == null) {
                    callback.onSuccess(this.result);
                } else {
                    callback.onFailure(exception);
                }
            }

        }.execute((Void) null);

	}

	public AuthorizationFields getAuthorizationFields() {

		AuthorizationFields fields = new AuthorizationFields();
		fields.addHeader("User-Agent", "AeroGear StoryList Demo /u/secondsun");
		if (isLoggedIn) {
			fields.addHeader("Cookie", "reddit_session="+authToken);
			fields.addQueryParameter("uh", modHash);
		}
		return fields;
	}
	
}

The code is hopefully quite boring and quite self explanitory. In login the login data is posted and the cookie and modhash fields are extracted from the result. When a Pipe needs Authorizaiton it calls getAuthorizedFields() and returns the current auth data. The Pipe will apply the query parameters and headers to its request before it is sent.

Download the APK

Dec 4th, 2012

No Comments! Be The First!

Leave a Reply