SureshJoshi.com ▼

Android Adventures - Picasso es Su Casso


2015-09-29

Since Bryan was on a roll from last week, it only makes sense that he continue his blog series about his first Android app - this time, with even more Picasso.

Aside: I’m in the process of freshening up my website, so be forgiving if it’s periodically jumping around or down or something. Aside Over

Now, with even more obscure pop culture references…

Enter Bryan

Well, well, well.

We meet again.

Welcome to Part Two of Android Adventures! Last time, we threw together a simple Android application – used RetroFit to pull data from GitHub’s public repositories and display some of that data in a TextView.

Exciting!

Today, we’re going to be upgrading our application interface (if you can call it that… maybe just ‘face’) from Basic to positively Rudimentary.

That’s right, we’re going to display our data in a List View! I also wanted to introduce you to a very simple, very powerful image-handling library called Picasso, named after my favourite Moldovan singer and former member of the boy band O-Zone (NOTE: this is 100% false

  • my favourite member of O-Zone is Arsenie Todiras).

Getting started

Just like in Part One, we just need to add a line into our Gradle script in order to use the library.

compile 'com.squareup.picasso:picasso:2.5.2'

If you’ve been following along, you should have four classes in your application – the GitApi class, the GitModel class, the Owner class, and the MainActivity class. You should also have an XML layout file (activity_main) in your resources folder.

To display our list we first need to add a ListView element to our activity_main.xml file, like so:

    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="6dip" >
    <TextView
        android:id="@+id/texv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:maxLines = "100"
        android:scrollbars = "vertical"/>
    <ListView xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/listView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    </RelativeLayout>

You may have noticed we’ve kept our TextView from before - that’s okay! We’ll use it to display error messages for us in case things get kookier than usual.

We’re also going to need to add a layout file for our list element. Create a new XML file in your res/layout folder for it. I called mine activity_list_view.xml

<?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:padding="10dp"
        android:background="@android:color/white">
        <ImageView
            android:id="@+id/image_in_item"
            android:layout_width="100dp"
            android:layout_height="100dp"/>
        <TextView
            android:id="@+id/textview_in_item"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="18sp"
            android:layout_toRightOf="@+id/image_in_item"
            android:layout_marginLeft="10dp"/>
        <TextView
            android:id="@+id/textview_in_item_two"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="12sp"
            android:layout_toRightOf="@+id/image_in_item"
            android:layout_below="@+id/textview_in_item"
            android:layout_marginLeft="10dp"/>
    </RelativeLayout>

Each List item will have an ImageView and two TextViews, but you can always add or remove elements at your leisure.

For each repository we get from our JSON request, we’re going to display the name, ID, and avatar_url (the URL pointing to the avatar of the repository’s owner). If you’re wondering what we’re putting into our ImageView, I’m glad you asked (just remember who’s calling the shots around here) - we’ll be using that excellent open-source library, Picasso (named after professional Uruguayan medley swimmer, Francisco Picasso), to download and cache these avatar images and display them in our ListView.

So, we’ve got a ListView and a layout for the items with which we’re going to fill it. What now?

Making a custom adapter

We’re going to use an Adapter to inflate (or render) a view for each of the repositories from our request. I called mine ListViewAdapter.

public class ListViewAdapter extends BaseAdapter {

    LayoutInflater inflater;
    List<GitModel> gitModelList;

    public ListViewAdapter(LayoutInflater inflater, List<GitModel> gitModelList){
        this.inflater = inflater;
        this.gitModelList = gitModelList;
    }

    @Override
    public int getCount() {
        return gitModelList.size();
    }

    @Override
    public Object getItem(int position) {
        return gitModelList.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder;

        if (convertView == null) {
            convertView = inflater.inflate(R.layout.activity_list_view, parent, false);
            holder = new ViewHolder(convertView);
            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }

        Picasso.with(inflater.getContext())
                .load(gitModelList.get(position).getOwner().getAvatar_url())
                .into(holder.image);

        holder.text.setText(" Name: "+gitModelList.get(position).getName()
                +"\t id: "+gitModelList.get(position).getId()+"\n");
        holder.texttwo.setText(gitModelList.get(position).getOwner().getAvatar_url());

        return convertView;
    }

    static class ViewHolder{
        @Bind(R.id.image_in_item)
        ImageView image;
        @Bind(R.id.textview_in_item)
        TextView text;
        @Bind(R.id.textview_in_item_two)
        TextView texttwo;

        public ViewHolder(View view){
            ButterKnife.bind(this, view);
        }
    }

    public void setGitmodel(List<GitModel> gitModelList){
        clearGitmodel();
        this.gitModelList = gitModelList;
        notifyDataSetChanged();
    }

    public void clearGitmodel(){
        this.gitModelList.clear();
    }
}

Don’t worry if this doesn’t make sense right now - we’ll go over it piece by piece.

Our ListViewAdapter constructor takes two arguments - an inflater and an ArrayList of GitModels. In order to implement our custom adapter, we also need to override four methods, getCount(), getItem(), getItemId, and finally getView.

Briefly, the getCount method returns the number of items, which is set to the size() of your ArrayList. The getItem() and getItemId() methods returns an item given an ID and the ID of the current item, respectively.

@Override
public int getCount() {
    return gitModelList.size();
}

@Override
public Object getItem(int position) {
    return gitModelList.get(position);
}

@Override
public long getItemId(int position) {
    return position;
}

Better practices

Before we get into the really juicy stuff, let’s talk briefly about the ViewHolder class. We use a little bit of ButterKnife magic to instantiate a view in its constructor. We’re going to be using this class in the getView() method.

static class ViewHolder{
    @Bind(R.id.image_in_item)
    ImageView image;
    @Bind(R.id.textview_in_item)
    TextView text;
    @Bind(R.id.textview_in_item_two)
    TextView texttwo;

    public ViewHolder(View view){
        ButterKnife.bind(this, view);
    }
}

Our getView() method creates ViewHolder objects – but here is where we implement some best (or at least better) practices! In my first implementation, I instantiated a new ViewHolder every time the getView() method was called, which is a big no no - every time you’d scroll up or down, the application would have to re-load everything. This way (below), new ViewHolders are only instantiated if a corresponding View doesn’t already exist.

    if (convertView == null) {
        convertView = inflater.inflate(R.layout.activity_list_view, parent, false);
        holder = new ViewHolder(convertView);
        convertView.setTag(holder);
    } else {
        holder = (ViewHolder) convertView.getTag();
    }

Welcome Pablo

Now, in our getView() class, we want to set the information that is to be displayed in each of the Views within our List elements (remember, an ImageView and two TextViews). You probably recognize how we’re setting our TextViews, but our ImageView is where we finally get to use Picasso! Picasso downloads, caches, and places our image in the appropriate List element - and it’s where we use our avatar_url!

    Picasso.with(inflater.getContext())
            .load(""+GitModelList.get(position).getOwner().getAvatar_url()) //""+gitmodelList.get(position).getOwner().getAvatar_url()
            .into(holder.image);

    holder.text.setText(" Name: "+GitModelList.get(position).getName()
            +"\t id: "+GitModelList.get(position).getId()+"\n");

    holder.texttwo.setText(""+GitModelList.get(position).getOwner().getAvatar_url());

Finally, we put in some setters so we can change the attributes of our Adapter, which is especially important for our gitModelList, as well as a clear() method that we use to clear out our ArrayList before setting it to a new one. Note that we don’t have a setter for our inflater - this is because we won’t ever need to change it!

public void setGitmodel(List<GitModel> gitmodelList){
    clearGitmodel();
    this.GitModelList = gitmodelList;
    notifyDataSetChanged();
}

public void clearGitmodel(){
    this.GitModelList.clear();
}

The clearGitmodel() method is relatively straightforward, but the setGitmodel is the important one. We clear our ArrayList\<> before setting the new List, then we use the notifyDataSetChanged() method to ‘tell’ our ListView it’s time to update itself.

Finally, for our MainActivity, if you’ve got our code from Part One, we only need to make a few small tweaks.

We’re adding five lines before our onCreate() method:

LayoutInflater inflater;
List<GitModel> placeholderModel;
ListViewAdapter listAdapter;

@Bind(R.id.listView)
ListView list;

An inflater, placeholder List, our ListViewAdapter, and a ButterKnife @Bind for our ListView element.

After we set our Content View in the onCreate() method, we’re going to initialize our inflater, placeholder, and finally our listAdapter like so:

inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);

placeholderModel = new ArrayList<>();
listAdapter = new ListViewAdapter(inflater, placeholderModel);

list.setAdapter(listAdapter);

In my first implementation, I created a new Adapter each time we called the success() method, essentially recreating our entire ListView on every successful HTTP request (I’m in love with the no-nos - I got it for the low-lows). By initializing our Adapter in onCreate(), we only need to have a single line in our success() method!

listAdapter.setGitmodel(gitmodel);

Putting it all together, this is our MainActivity class:

public class MainActivity extends Activity {

    String API = "https://api.github.com"; // BASE URL
    LayoutInflater inflater;
    List<GitModel> placeholderModel;
    ListViewAdapter listAdapter;
    @Bind(R.id.listView)
    ListView list;
    @Bind(R.id.texv)
    TextView tv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);

        placeholderModel = new ArrayList<>();
        listAdapter = new ListViewAdapter(inflater, placeholderModel);

        list.setAdapter(listAdapter);
        RestAdapter restAdapter = new RestAdapter.Builder()
                .setEndpoint(API).build();  //create an adapter for retrofit with base url

        GitApi git = restAdapter.create(GitApi.class);  //creating a service for adapter with our GET class

        git.getFeed(new Callback<List<GitModel>>() {
            @Override
            public void failure(RetrofitError error) {
                tv.setText(error.getMessage());
            }
            @Override
            public void success(final List<GitModel> gitmodel, Response response) {
                listAdapter.setGitmodel(gitmodel);
            }
        });
    }
}

And there we have it!

We’ve replaced our ugly TextView with a slightly-less-ugly ListView! We also got some familiarity with Picasso - if you’d like, you can try to load images into the ImageView using more traditional methods and see just how well Picasso (named after the Picasso Triggerfish, a triggerfish found in the reefs of the Indo-Pacific region) streamlines image handling.

Next week, we’ll finish off this project by adding some interactivity to our List! So hold onto your seats and hopefully I’ll be seeing you in Part Three: Enter the Dragon.