torsdag 5 juli 2012

Android : Simple HTML parsing & image downloader using AsyncTask

A barebone html parser and a simple image downloader for a certain comic. This one may please your girlfriend or wife. I'll show a simple DefaultHttpClient and ResponseHandle in conjunction with a AsyncTask class.(Although with almost no error checking) The comic we want is Love Is, i've stripped it so it's just a ImageView for the image, a button for Previous/Next and a simple TextView for the date.

Lets start with the simple helper class that will connect to the homepage, parse the html and download the image.

Update, GoComics.com have removed their Love Is... comic strip. Updated it with another page.

(note only updated LoveIsParser.java, they use strange dates for their pictures, reused ones ? So need to store url to previous and next picture before closing the AsyncTask... just did a quick hack to get it working again.)

src/LoveIsParser.java
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Log;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.BasicResponseHandler;
import org.apache.http.impl.client.DefaultHttpClient;

import java.io.BufferedInputStream;
import java.io.InputStream;
import java.util.Calendar;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class LoveIsParser {

    final String loveIsUrl = "http://loveiscomix.com/";
    private String urlImage;
    private Bitmap loveIsBitmap;
    private Pattern patternForImage = Pattern.compile("static/loveisbnk/.........gif");
    public LoveIsParser()
    { }

    public Bitmap getLoveIsBitmap()
    {
        return loveIsBitmap;
    }

    public boolean downloadImage(Calendar c)
    {
        //String urlExtension = c.get(Calendar.YEAR) + "/" + padString(c.get(Calendar.MONTH)+1) + "/" + padString(c.get(Calendar.DAY_OF_MONTH));
        //Log.d("LoveIS Url:", loveIsUrl + urlExtension);
        DefaultHttpClient httpClient = new DefaultHttpClient();
        BasicResponseHandler responseHandler = new BasicResponseHandler();
        HttpGet request = new HttpGet(loveIsUrl);
        try
        {
            String htmlBody = httpClient.execute(request, responseHandler);
            Matcher m = patternForImage.matcher(htmlBody);
            if(m.find())
            {
                urlImage = m.group();
                urlImage = loveIsUrl + urlImage;
                Log.d("Image Url:", urlImage);
                request = new HttpGet(urlImage);
                HttpResponse response = httpClient.execute(request);
                InputStream in = response.getEntity().getContent();
                BufferedInputStream bis = new BufferedInputStream(in, 8192);
                loveIsBitmap = BitmapFactory.decodeStream(bis);
                bis.close();
                in.close();
                return true;
            }
        }
        catch (Exception e)
        {
            Log.d("Exception", e.toString());
        }
        return  false;
    }

    public String padString(int number)
    {
        return String.format("%02d", number);
    }
}

Most is self explanatory, downloadImage function takes a Calendar, parses the date and completes the url.
Adding 1 to the month as it's zero-based and using padString to pad with a 0 if it's singledigit.

Lets move on to the layout.

layout/main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_width="fill_parent"
              android:layout_height="fill_parent">

    <ImageView  android:id="@+id/ivLove"
                android:layout_height="wrap_content"
                android:layout_width="fill_parent"/>

    <TextView android:id="@+id/tvDate"
              android:layout_height="wrap_content"
              android:layout_width="wrap_content"/>
    <Button android:layout_height="wrap_content"
            android:layout_width="fill_parent"
            android:id="@+id/btnPrevious"
            android:text="Previous"/>
    <Button android:layout_height="wrap_content"
            android:layout_width="fill_parent"
            android:id="@+id/btnNext"
            android:text="Next"/>
</LinearLayout>

And lastly our launcher activity.

src/MainActivity.java

import android.app.ProgressDialog;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import java.util.Calendar;

public class MainActivity extends FragmentActivity {

    Calendar c;
    TextView tvDate;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.loveis);
        c = Calendar.getInstance();
        ImageView imageView = (ImageView) findViewById(R.id.ivLove);
        tvDate = (TextView) findViewById(R.id.tvDate);
        tvDate.setText(c.getTime().toLocaleString());
        Button btnPrevious = (Button) findViewById(R.id.btnPrevious);
        Button btnNext = (Button) findViewById(R.id.btnNext);
        if(isOnline())
            new GetAndSetImage().execute(c);
        else
            Toast.makeText(this, "No Internet connection found.", Toast.LENGTH_LONG).show();

        btnPrevious.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                c.add(Calendar.DATE, -1);
                tvDate.setText(c.getTime().toLocaleString());
                new GetAndSetImage().execute(c);
            }
        });

        btnNext.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                c.add(Calendar.DATE, 1);
                tvDate.setText(c.getTime().toLocaleString());
                new GetAndSetImage().execute(c);
            }
        });
    }

    private class GetAndSetImage extends AsyncTask<Calendar, Void, Bitmap>
    {
        ProgressDialog pd;

        @Override
        protected Bitmap doInBackground(Calendar... c) {
            LoveIsParser parser = new LoveIsParser();
            if(parser.downloadImage(c[0]))
                return parser.getLoveIsBitmap();
            else
            {   // We just return a drawable if there's an error in the download.
                return BitmapFactory.decodeResource(getResources(), R.drawable.icon);
            }
        }

        @Override
        protected void onPreExecute()
        {
            pd = new ProgressDialog(MainActivity.this);
            pd.setProgressStyle(ProgressDialog.STYLE_SPINNER);
            pd.setMessage("Downloading image...");
            pd.show();
        }

        @Override
        protected void onPostExecute(Bitmap bm)
        {
            pd.dismiss();
            ImageView iv = (ImageView) findViewById(R.id.ivLove);
            iv.setImageBitmap(bm);
        }
    }

    public boolean isOnline() {
        ConnectivityManager cm =
                (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo netInfo = cm.getActiveNetworkInfo();
        if (netInfo != null && netInfo.isConnectedOrConnecting()) {
            return true;
        }
        return false;
    }

}

Buttons decrease and increase the date when clicked and download the image based on the current date using a simple AsyncTask that display a 'Progress Dialog' while it's downloading. 

Now just flash it up a bit (hearts and red layout ^^) and install on your girlfriends phone for some extra romance, or implement sharing so you can easily send the picture as an MMS whenever you want.

Screenshot: