Tuesday 27 November 2012

Implementing the holder pattern in ArrayAdapter

An ArrayAdapter is really cool. You can take any list of data and have it display in a ListView with next to no code.

Lets make a ListView full of Animals.

package com.bradleycurran.android.multiconfigeditor;

public class Animal {

    private String mName;

    private int mImageResourceID;

    public Animal(String name, int imageID) {
        mName = name;
        mImageResourceID = imageID;
    }

    public String getName() {
        return mName;
    }

    public int getImageResourceID() {
        return mImageResourceID;
    }
}

From here, we make a class that extends ArrayAdapter and overrides the getView method.


    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View row = convertView;

        if (row == null) {
            LayoutInflater inflator = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            row = inflator.inflate(R.layout.row_animal, parent, false);
        }

        TextView name = (TextView)row.findViewById(R.id.text_name);
        ImageView image = (ImageView)row.findViewById(R.id.image_photo);

        Animal animal = getItem(position);

        name.setText(animal.getName());
        image.setImageResource(animal.getImageResourceID());

        return row;
    }

See how we call findViewById every time we create a row? Calling findViewById is an expensive operation, so caching the references will speed up your row creation by a lot.

Create a private inner class called Holder and add the TextView and ImageView as member variables.


    private class Holder
    {
        private TextView mName;
        private ImageView mImage;
    }

Next, create a constructor that takes a row as a parameter.


        public Holder(View row) {
            mName = (TextView)row.findViewById(R.id.text_name);
            mImage = (ImageView)row.findViewById(R.id.image_photo);
        }

Now add a method to Holder called setup that takes an Animal object as a parameter.


        public void setup(Animal animal) {
            mName.setText(animal.getName());
            mImage.setImageResource(animal.getImageResourceID());
        }

Our Holder class is complete! Remove the old code that finds the children Views and sets them up and replace them with the new methods you've written.

Here's the full code for this example:


package com.bradleycurran.android.multiconfigeditor;

import java.util.List;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;

public class AnimalAdapter extends ArrayAdapter<Animal> {

    public AnimalAdapter(Context context, List<Animal> list) {
        super(context, 0, list);
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View row = convertView;

        if (row == null) {
            LayoutInflater inflator = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            row = inflator.inflate(R.layout.row_animal, parent, false);
            row.setTag(new Holder(row));
        }

        Holder holder = (Holder)row.getTag();
        holder.setup(getItem(position));

        return row;
    }

    private class Holder {

        private TextView mName;
        private ImageView mImage;

        public Holder(View row) {
            mName = (TextView)row.findViewById(R.id.text_name);
            mImage = (ImageView)row.findViewById(R.id.image_photo);
        }

        public void setup(Animal animal) {
            mName.setText(animal.getName());
            mImage.setImageResource(animal.getImageResourceID());
        }
    }
}

Now the Adapter code is not only cleaner but it runs faster too.

Monday 12 November 2012

The arrowhead anti-pattern and how to get rid of it

We've all seen code like this before:

public void checkForCats()
{
    if (mStillCounting)
    {
        for (String animal : mAnimals)
        {
            if (animal != null)
            {
                if (animal.startsWith("cat"))
                {
                    mCats++;
                }
            }
        }
    }
}

The above code is a series of nested ifs and whiles. The set of nested statements creates what looks like an arrow head pointing to mCats++. Hence the name of this: the arrowhead anti-pattern.

This code is impossible to read, how do I get rid of it?

Trick #1: Guard Clauses

Let's take a look at that first if statement.

if (mStillCounting)

Because this if contains the rest of the method, we can reverse the condition of it and provide an early return instead.

This is what the new code looks like:

public void checkForCats()
{
    if (!mStillCounting)
    {
        return;
    }
    
    for (String animal : mAnimals)
    {
        if (animal != null)
        {
            if (animal.startsWith("cat"))
            {
                mCats++;
            }
        }
    }
}

It's looking less like an arrow head now, though there's still some more we can do.

Trick #2: Extract Methods


See the code inside the for loop? Lets extract that code into another method.

public void checkForCats()
{
    if (!mStillCounting)
    {
        return;
    }

    for (String animal : mAnimals)
    {
        checkForCat(animal);
    }
}

private void checkForCat(String animal)
{
    if (animal != null)
    {
        if (animal.startsWith("cat"))
        {
            mCats++;
        }
    }
}

Unsurprisingly this refactoring step is known as Extract Method. You're breaking down a larger method into multiple methods that are easier to understand.

To make our new method easier to understand, we'll do another Guard Clause on that first if in checkForCat.

private void checkForCat(String animal)
{
    if (animal == null)
    {
        return;
    }
    
    if (animal.startsWith("cat"))
    {
        mCats++;
    }
}

By reversing the condition of that if from (animal != null) to (animal == null), we now have a positive conditional. This improves the readability of code a lot.

In general you should try to write your conditionals as a positive check.

These two tricks should be enough to get you started. :)

A Timer class in Java

Have you ever wanted to time how long it takes to execute a particular section of your code?

Java makes it easy to create a simple class to achieve this.

We want the following things:

  • Tell the timer when to start counting
  • Tell the timer when to stop counting
  • Give us the time elapsed
 Those dot points look like methods to me. Lets write the code:


public class Timer
{
    private long mInitialTime;

    private long mFinalTime;

    public Timer()
    {
        start();
    }

    public void start()
    {
        mInitialTime = System.currentTimeMillis();
        mFinalTime = mInitialTime;
    }

    public String end(String description)
    {
        mFinalTime = System.currentTimeMillis() - mInitialTime;
        return description + ": total time " + getTimeElapsed();
    }

    public long getTimeElapsed()
    {
        return mFinalTime;
    }
}

The timer will start either on object construction or on calling start()

The timer will end when you call end(String) and return a convenient description of how long it took.

If you want the time elapsed in long form, use getTimeElapsed()

Simple as that. :)

Quickly implement constructors based on a superclass in Eclipse

package com.bc.android;

import android.view.View;

public class MyCustomView extends View
{
}


How annoying is it when you inherit from another class and you get this error:

Implicit super constructor View() is undefined for default constructor. Must define an explicit constructor

This means we need to implement at least one of the constructors from the superclass in the subclass.

Turns out Eclipse makes it easy to pick and choose what constructors you want to implement and automatically inserts these constructors into your code.

Open the Source menu and select "Generate Constructors from Superclass..."


Now set a check mark on every constructor you want to implement in your subclass and hit OK.


And there you have it! Three constructors automatically implemented that use the correct parameter list and call the constructor of the superclass.

Here's the final result:


package com.bc.android;

import android.content.Context;
import android.util.AttributeSet;
import android.view.View;

public class MyCustomView extends View
{
    public MyCustomView(Context context, AttributeSet attrs, int defStyle)
    {
        super(context, attrs, defStyle);
        // TODO Auto-generated constructor stub
    }

    public MyCustomView(Context context, AttributeSet attrs)
    {
        super(context, attrs);
        // TODO Auto-generated constructor stub
    }

    public MyCustomView(Context context)
    {
        super(context);
        // TODO Auto-generated constructor stub
    }
}

How to attach Android source to Eclipse

Don't you hate it when you hit F3 on an Android class and you get Eclipse's ugly class viewer instead of Android's actual source for that class?



There's an easy way to fix it.


In Package Explorer expand your Android library entry (in the case above it's Android 4.1) and right click the android.jar file.

Select Properties...

Fill out the information like I've done here.


In summary, select Java Source Attachment in the left pane.
Mark the External location radio button.
Enter the path of the Android source you've downloaded through the Android SDK Manager.

If you don't have the contents of <android_sdk_location>/sources/<android_version> then you'll need to open your Android SDK Manager and download the source for the appropriate version.

Now next time you want to Open Declaration for an Android class, it'll be in a format that's not going to burn out your eyes. :]

Thursday 8 November 2012

Referenced Projects and the ClassNotFoundException

You're running an Android application on device or on the emulator through Eclipse.
You get a ClassNotFoundException on a class that's in one of one of your referenced projects.

Well, what now? You can see the file right there. Why doesn't the app have that class?

There's a simple reason for this. It could be one of two issues.

One: You're including a basic Java project to your Android project

The fix:
Open your Android project properties and select Java Build Path in the left pane.
Select the Order and Export tab.
From there make sure that your referenced project has the checkbox enabled. This will put the classes into the final Android .apk file.





Two: You're including an Android library project to your Android project.

The fix:
Open your Android project properties on your Android library project.
Select Android in the left pane.
Make sure the Is Library checkbox is checked.



Open your Android project properties on your Android project.
Select Android in the left pane.
Click the Add... button on the right and select your library project in the dialog.

Now, clean and run your Android app again and no more ClassNotFoundExceptions should appear.