Tuesday, December 10, 2013

ViewInflater: Managing Adapter Overgrowth

Unexcused Absence

I can't believe it has been over a year since my last post.  I started with good intentions, but ran out of time... juggling a full-time job, school work (I have finally graduated... with a B.S. in Computer Science from Utah Valley University), and various Android contract work on the side.

My goal from here on out is to publish at least one post per month (and hopefully two) with various tips and tricks I've learned and/or come across while developing for the Android platform.  I believe I'm at a point now that I can do that.

Now... on to the topic at hand!

Background

I started Android development before the T-Mobile G1 was available here in Utah.  It was announced. I had it preordered.  And I started looking into developing... A hobby and career was born.

While developing my apps I used lists... there were lots of lists.  And any good Android developer knows that when you have a list you need an adapter to manage the items in that list.  There are a number of adapter classes that can be used, and they all have different advantages and disadvantages.

Before I knew it, I had a project that had around 10 adapter subclasses... And all of them had pretty much the same code.  Upon closer inspection, I realized that most of them were virtually identical except for two main differences:
  1. The type of data backing the adapter
  2. The view that was inflated for each item
 After seeing this same pattern in many other projects, an idea began to form in my head... And the ViewInflater pattern was born.

ViewInflater: The Pattern

I wanted a single adapter class that would be able to deal with the two differences outlined above.  Java Generics was the obvious answer to dealing with the many different types of data.  Android already had an ArrayAdapter<T> class, but after looking at it, I realized it wasn't suited for dealing with the many different views that an adapter could inflate.

I wrote my own adapter class that uses Generics, and in that regards is very similar to Android's ArrayAdapter<T> class.  To deal with the different views that an adapter could inflate, I decided to promote the getView() method to an object.  In order to do that, I did the following:
  • Defined a ViewInflater<T> interface in my Adapter with an inflate() method
  • Had the Adapter hold a reference to the ViewInflater object
  • Had the Adapter's getView() method call ViewInflater.inflate() to generate the view
The entire class (minus boring boilerplate stuff like getCount(), add(), etc...) looks like this:
 
public class ViewInflaterAdapter<T> extends BaseAdapter
{
    private ArrayList<T> m_data;
    private ViewInflater m_inflater;

    public ViewInflaterAdapter (ViewInflater inflater)
    {
        m_inflater = inflater;
        m_data = new ArrayList<T>();
    }

    public ViewInflaterAdapter (ViewInflater inflater, List<T> data)
    {
        m_inflater = inflater;
        m_data = new ArrayList<T>(data);
    }

    /////////////////////////////////////////////////////
    //Typicial adapter code goes here
    /////////////////////////////////////////////////////

    public void setInflater(ViewInflater inflater)
    {
        m_inflater = inflater;
    }

    public T getTItem(int pos)
    {
        return m_data.get(pos);
    }

    @Override
    public View getView(int pos, View convertView, ViewGroup parent)
    {
        return m_inflater != null ? m_inflater.inflate(this, pos, convertView, parent) : null;
    }

    public interface ViewInflater<T>
    {
        View inflate (ViewInflaterAdapter<T> adapter, int pos, View ConvertView, ViewGroup parent);
    }
}

The above adapter now acts as a general purpose class... And unless you need something special from some of the other adapter classes you never have to write another Adapter again (but if you do need to write one based on one of the other adapter flavors, you can still use this pattern for it).  Just create a new class that implements ViewInflater and all the code that would normally go in getView() goes in your inflater's inflate() method.

A simplified version of an inflater would look something like this:
public class MyItemInflater implements ViewInflaterAdapter<String>
{
     @Override
     public View inflate(ViewInflaterAdapter<String> adapter, int pos, View convertView, ViewGroup parent)
     {
          LayoutInflater inflater = LayoutInflater.from(parent.getContext());
          View itemView = inflater.inflate(R.layout.my_item_inflater, parent, false);
          TextView textView = (TextView)itemView.findViewById(R.id.my_item_text);
          textView.setText(adapter.getTItem(pos));
          return itemView;
     }
}

To keep the example simple I assumed that R.layout.my_item_inflater exists and that it contains a TextView with an id set to R.id.my_item_text. I also did not incorporate the ViewHolder pattern, which is a very good design pattern when dealing with adapters and lists, and is very easy to add when using the ViewInflater pattern. If you have a more complicated layout you can add a constructor and pass in click listeners for buttons or checkboxes.

Since you are dealing with a class instead of just a method, you have a lot more flexibility and freedom to make the code work the way you want it to. The ViewInflater pattern separates UI code from non-UI code because the adapter is no longer directly in charge of creating views. It also promotes code reuse by keeping down the number of adapter classes and promotes flexibility by "promoting" the getView() method to a class.

I haven't tried this yet, but I would imagine you would be able to dynamically change the view that gets inflated.  You might have to do some special-case handling with the getItemViewType() method though.

Here is an example of how it might be used with a ListView:
ArrayList<String> data = new ArrayList<String>();
//Add items to data...
MyItemInflater inflater = new MyItemInflater();
ViewInflaterAdapter<String> adapter = new ViewInflaterAdapter(inflater, data);

//Assume that rootView is already defined and that it contains a ListView whose id is my_id 
ListView list = (ListView) rootView.findViewById(R.id.my_list);
list.setAdapter(adapter);
Happy coding... Hope you found this post useful!

1 comment:

  1. What is the best casino? - JTM Hub
    How 나주 출장안마 can I get a new casino? · You need to 보령 출장샵 login to 오산 출장안마 your account · Log into your account · You need to 보령 출장마사지 sign up on your My Account 포항 출장안마 · You have two accounts

    ReplyDelete