Monday, April 27, 2015

HTML parsing using JSoup and Volley in Android Application

Standard
Hello All:

In application development, sometimes we want to parse HTML data to get relevant information. In this post I will show you how to parse HTML data using JSoup and Volley.

Let us start.

We will be using following external libraries
  1. JSoup: JSoup is a Java library for working with real-world HTML. It provides a very convenient API for extracting and manipulating data, using the best of DOM, CSS, and jquery-like methods. More info can be found here.
  2. Volley: Volley is an excellent framework to make network calls.More info can be found here.
Project structure
  1.  Interfaces
    1. IHTMLParser
    2. IAsyncCallback
  2. Classes
    1. HTMLParseAsyncTask: Extends Async task
    2. BlogPostParser: Implements  IHTMLParser
    3. BaseHttpRequest: Main class to make network calls
 For Volley, we will be using Singleton.One way of achieving this is by extending Application class.
Below are some of the important classes to use. I will be sharing the link for fully functional code at the bottom of the post.
BaseApplication
public class BaseApplication extends Application {

    private static BaseApplication sInstance;
    private RequestQueue mRequestQueue;

    public synchronized static BaseApplication getInstance() {
        return sInstance;
    }

    public RequestQueue getRequestQueue() {
        return mRequestQueue;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        sInstance = this;
        mRequestQueue = Volley.newRequestQueue(this);
    }
}
Interfaces
public interface IAsyncCallback {
     void onComplete(WebResponse responseContent);
     void onError(String errorData);
}

public interface IHTMLParser {
    BaseObject parseHTML(String htmlToParse);
}

Classes
public class BaseHttpRequest {

    ProgressDialog progressDialog;
    int responseCode;
    IAsyncCallback callback;
    Response.Listener<String> stringResponseListener;
    private IHTMLParser htmlParser;
    private String url;
    Response.ErrorListener errorListener = new Response.ErrorListener() {
        @Override
        public void onErrorResponse(VolleyError error) {
            StringWriter errors = new StringWriter();
            error.printStackTrace(new PrintWriter(errors));
            dismissProgressDialog();
            callback.onError(error.toString());
        }
    };
    private Context context;

    public BaseHttpRequest(Activity localActivity,
                           String url) {

        context = localActivity;
        this.url = url;
        setListeners();
    }

    public IHTMLParser getHtmlParser() {
        return htmlParser;
    }

    public void setHtmlParser(IHTMLParser htmlParser) {
        this.htmlParser = htmlParser;
    }


    public IAsyncCallback getCallback() {
        return callback;
    }

    public Context getContext() {
        return context;
    }

    private void setListeners() {
        stringResponseListener = new Response.Listener<String>() {
            @Override
            public void onResponse(String response) {
                HTMLParseAsyncTask task = new HTMLParseAsyncTask();
                task.setCurrentRequest(BaseHttpRequest.this);
                task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, response);
            }
        };
    }

    public void dismissProgressDialog() {
        if (progressDialog != null && progressDialog.isShowing()) {
            progressDialog.dismiss();
        }
    }

    public void execute(IAsyncCallback callback, RequestQueue requestQueue) {
        this.callback = callback;
        progressDialog = new ProgressDialog(getContext());
        progressDialog.setCancelable(false);
        progressDialog.setMessage("Please wait...");
        dismissProgressDialog();
        progressDialog.show();
        addToRequestQueue(requestQueue, getStringRequest());
    }

    public <X> void addToRequestQueue(RequestQueue requestQueue, Request<X> req) {
        requestQueue.add(req);
    }

    Request<String> getStringRequest() {
        return new StringRequest(Request.Method.GET, url, stringResponseListener, errorListener);
    }

}

public class HTMLParseAsyncTask extends AsyncTask<String, Void, Void> {

    private BaseObject baseObject;
    private BaseHttpRequest currentRequest;

    public BaseHttpRequest getCurrentRequest() {
        return currentRequest;
    }

    public void setCurrentRequest(BaseHttpRequest currentRequest) {
        this.currentRequest = currentRequest;
    }

    @Override
    protected void onPreExecute() {
        super.onPreExecute();
    }

    @Override
    protected Void doInBackground(String... params) {
        if (getCurrentRequest().getHtmlParser() != null) {
            baseObject = getCurrentRequest().getHtmlParser().parseHTML(params[0]);
        }
        return null;
    }

    @Override
    protected void onPostExecute(Void result) {
        getCurrentRequest().dismissProgressDialog();
        try {
            WebResponse webResponse = new WebResponse(baseObject);
            getCurrentRequest().getCallback().onComplete(webResponse);
        } catch (Exception exception) {
            exception.printStackTrace();
        }
    }
}

public class BlogPostParser implements IHTMLParser {
    @Override
    public BaseObject parseHTML(String htmlToParse) {
        BlogResponse response = new BlogResponse();
        try {
            Document doc = Jsoup.parse(htmlToParse);
            response.setPosts(new ArrayList<BlogPost>());
            BlogPost post;
            Elements anchors = doc.select(".entry-header a");
            for (Element anchor : anchors) {
                post = new BlogPost();
                post.setURL(anchor.attr("href"));
                post.setText(anchor.text());
                response.getPosts().add(post);
            }
        } catch (Exception exception) {
            exception.printStackTrace();
        }
        return response;
    }
}

public class MainActivity extends FragmentActivity {

    Button button;
    ListView listView;
    BlogResponse blogPosts;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        button = (Button) findViewById(R.id.button);
        listView = (ListView) findViewById(R.id.listView);

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                try {
                    BaseHttpRequest request = new BaseHttpRequest(MainActivity.this, "http://blog.ashwanik.in/search?max-results=50");
                    request.setHtmlParser(new BlogPostParser());
                    IAsyncCallback callback = new IAsyncCallback() {
                        @Override
                        public void onComplete(WebResponse responseContent) {
                            blogPosts = (BlogResponse) responseContent.getTypedObject();
                            listView.setAdapter(new BlogPostAdapter(MainActivity.this));
                        }

                        @Override
                        public void onError(String errorData) {
                            Toast.makeText(MainActivity.this, errorData, Toast.LENGTH_SHORT).show();
                        }
                    };
                    request.execute(callback, BaseApplication.getInstance().getRequestQueue());
                } catch (Exception e) {
                    Toast.makeText(MainActivity.this, "Some error occurred.", Toast.LENGTH_SHORT).show();
                    e.printStackTrace();
                }
            }
        });

        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
                try {
                    MainActivity.this.startActivity(new Intent(Intent.ACTION_VIEW, Uri
                            .parse(blogPosts.getPosts().get(i).getURL())));
                } catch (Exception exception) {
                    exception.printStackTrace();
                }
            }
        });
    }


    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }


    static class ViewHolder {
        TextView post;
    }

    public class BlogPostAdapter extends BaseAdapter {
        Activity context;
        ArrayList<BlogPost> arrayList;
        LayoutInflater inflater;

        public BlogPostAdapter(Activity context) {
            super();
            this.context = context;
            this.arrayList = blogPosts.getPosts();
            inflater = context.getLayoutInflater();
        }

        public int getCount() {
            return arrayList.size();
        }

        public BlogPost getItem(int position) {
            return arrayList.get(position);
        }

        public long getItemId(int position) {
            return 0;
        }

        public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder holder;
            BlogPost metaData = getItem(position);
            if (convertView == null) {
                convertView = inflater.inflate(R.layout.r_blogpost, parent, false);
                holder = new ViewHolder();

                holder.post = (TextView) convertView
                        .findViewById(R.id.post);
                convertView.setTag(holder);
            } else {
                holder = (ViewHolder) convertView.getTag();
            }

            holder.post.setText(metaData.getText());
            return convertView;
        }

    }
}

Hope this helps. The full working code can be found here.

Thanks for printing this post. Hope you liked it.
Keep visiting and sharing.
Thanks,
Ashwani.

2 comments :