Wednesday, 3 July 2013

How to parse and render Reddit comments with Android

This is the fourth part of the Reddit Android app creation series. You might want to read the earlier parts here,
In this part, we will learn how to render all the comments of a given post. This is not very different from rendering all the posts of a subreddit. However, one important concept to consider is that the comments are threaded. That means, a comment can have replies, and each reply can have its own replies. Hence, our algorithm needs to be recursive.

So, we are going to recursively load all comments, and store them in an ArrayList. We pass this ArrayList to an ArrayAdapter, and we are done.

Step 1
Create a file named Comment.java that stores the details of a comment.
package com.jdepths.alien;
/**
* This class holds the details about a comment
* @author Hathy
*/
public class Comment {
    String htmlText;
    String author;
    String points;
    String postedOn;

    // The 'level' field indicates how deep in the hierarchy
    // this comment is. A top-level comment has a level of 0
    // where as a reply has level 1, and reply of a reply has
    // level 2 and so on...
    int level;
}

Step 2
Now we create a CommentsLoader.java class, that is responsible for fetching and creating the ArrayList of comments. This expects the JSON url of a comments page. For example,
http://www.reddit.com/r/Android/comments/1hk1ei/samsung_sells_20_million_galaxy_s_4s/.json
Here is the complete code for this class.
package com.jdepths.alien;

import java.util.ArrayList;
import java.util.Date;

import org.json.JSONArray;
import org.json.JSONObject;

import android.util.Log;

/**
* This class is responsible for processing the comments JSON API
* @author Hathy
*/
public class CommentProcessor {
    
    // This will be the URL of the comments page, suffixed with .json
    private String url;    
    
    CommentProcessor(String u){
        url=u;
    }
        
    // Load various details about the comment
    private Comment loadComment(JSONObject data, int level){
        Comment comment=new Comment();
        try{
            comment.htmlText = data.getString("body_html");
            comment.author = data.getString("author");
            comment.points = (data.getInt("ups")
                             - data.getInt("downs"))
                             + "";
            comment.postedOn = new Date((long)data
                                  .getDouble("created_utc"))
                                  .toString();
            comment.level=level;
        }catch(Exception e){
            Log.d("ERROR","Unable to parse comment : "+e);
        }
        return comment;
    }
    
    // This is where the comment is actually loaded
    // For each comment, its replies are recursively loaded
    private void process(ArrayList<Comment> comments
                         , JSONArray c, int level)
                               throws Exception {
        for(int i=0;i<c.length();i++){
            if(c.getJSONObject(i).optString("kind")==null)
                  continue;
            if(c.getJSONObject(i).optString("kind").equals("t1")==false)
                  continue;
            JSONObject data=c.getJSONObject(i).getJSONObject("data");
            Comment comment=loadComment(data,level);
            if(comment.author!=null) {
                comments.add(comment);
                addReplies(comments,data,level+1);
            }
        }
    }

    // Add replies to the comments
    private void addReplies(ArrayList<Comment> comments, 
                            JSONObject parent, int level){
        try{            
            if(parent.get("replies").equals("")){
                // This means the comment has no replies
                return;
            }
            JSONArray r=parent.getJSONObject("replies")
                              .getJSONObject("data")
                              .getJSONArray("children");
            process(comments, r, level); 
        }catch(Exception e){
            Log.d("ERROR","addReplies : "+e);
        }
    } 
    
    // Load the comments as an ArrayList, so that it can be
    // easily passed to the ArrayAdapter
    ArrayList<Comment> fetchComments(){        
        ArrayList<Comment> comments=new ArrayList<Comment>();
        try{
            
            // Fetch the contents of the comments page
            String raw=Connector.readContents(url);
            
            JSONArray r=new JSONArray(raw)
                            .getJSONObject(1)
                            .getJSONObject("data")
                            .getJSONArray("children");
            
            // All comments at this point are at level 0
            // (i.e., they are not replies)
            process(comments, r, 0);
            
        }catch(Exception e){
            Log.d("ERROR","Could not connect: "+e);
        }
        return comments;
    }

}
There, we now have the comments loaded in to an ArrayList. This can be directly passed to an ArrayAdapter. Now, you can simply follow the steps in the Part I of this series, to override the getView() method of the ArrayAdapter, and render your comments. To show the tree-like structure, adjust the left-padding of the View that getView() returns. This would look something like this,
commentContainer.setPadding(comments.get(position).level*10, 0, 0, 0);
Hope this was useful to you. You now know how to use the Reddit JSON comments API to display the comments of a post. Please do write to me if you have any comments or feedback.

No comments:

Post a Comment