Lesson Tuesday

Now we know how to work with the standard ArrayAdapter that is part of the Android package. Great! However, the standard ArrayAdapter really can’t do very much - it can pull items, usually of type String out of an array, and draw a line underneath each one, but that’s about it.

Further along in the course we will learn about more powerful (and complex!) ways to efficiently show data that is part of a collection, especially data that is more structured such as an Object. But for now, let’s ease into this topic by learning about how we can customize an ArrayAdapter. Work through this slowly, as many of these concepts will be used extensively later on.

Create the custom adapter

First, create a new Java class in the main package called MyRestaurantsArrayAdapter.

Our custom MyRestaurantsArrayAdapter class will need to extend the ArrayAdapter class, like this:

MyRestaurantsArrayAdapter.java

public class MyRestaurantsAdapter extends ArrayAdapter {
    }

Adding this code will temporarily throw an error; don't worry, this is expected! Continue following along with the lesson to implement the rest of the required code, which will resolve this error.

We are already showing the name of the restaurant in our ListView. Let’s add some more information, such as a cuisine type. We'll store this information in a parallel array to keep things simple. (We could use a HashMap, but we'll stick with Arrays for now.) We’ll need to pass these two bits of information (restaurants and cuisines) to our constructor, so we can use them when we output the data back to the UI. But we also need to pass in the Context, and a resource. Make sure the cuisines and the restaurants can be reached via the same index (i.e they match up)

RestaurantsActivity.java

...
    private String[] restaurants = new String[] {"Sweet Hereafter", "Cricket", "Hawthorne Fish House", "Viking Soul Food", "Red Square", "Horse Brass", "Dick's Kitchen", "Taco Bell", "Me Kha Noodle Bar", "La Bonita Taqueria", "Smokehouse Tavern", "Pembiche", "Kay's Bar", "Gnarly Grey", "Slappy Cakes", "Mi Mero Mole" };

    private String[] cuisines = new String[] {"Vegan Food", "Breakfast", "Fishs Dishs", "Scandinavian", "Coffee", "English Food", "Burgers", "Fast Food", "Noodle Soups", "Mexican", "BBQ", "Cuban", "Bar Food", "Sports Bar", "Breakfast", "Mexican" };
...

Our constructor for our adapter now looks like this:

MyRestaurantsArrayAdapter.java
public class MyRestaurantsArrayAdapter extends ArrayAdapter {
   private Context mContext;
   private String[] mRestaurants;
   private String[] mCuisines;

   public MyRestaurantsArrayAdapter(Context mContext, int resource, String[] mRestaurants, String[] mCuisines) {
       super(mContext, resource);
       this.mContext = mContext;
       this.mRestaurants = mRestaurants;
       this.mCuisines = mCuisines;
   }
}

Context in Android is a term that comes up a lot, and is frequently required for many methods to run, such as Toasts. Context, simply put, is the current state of the application or object. We need it to get information regarding our app, or other parts of our app. Depending on where you are located in your app, such as an activity class, you can access Context via different methods such as getApplicationContext(), getBaseContext(),getContext(), or simply falling back on this.

Context is just another Object in Android, so don’t let it scare you! You can also pass Context around like any other variable, including into a constructor, like we are doing here.

The resource here refers to the XML file we are using in order to display our data. We are not changing this at this time. Remember, adapters need three pieces of information, minimum:

  • Information about where they are being invoked (Context),
  • Information about which layout file they are utilizing (resource, in this case simple_list_item_1)
  • Some form of data storage (an Array or ArrayList of String's or Objects, most likely).

So far so good. Our custom adapter is taking shape.

Because MyRestaurantsArrayAdapter inherits from ArrayAdapter, we need to Override some of ArrayAdapter’s methods and replace them with our own custom versions. This is where we can format out output string! Getting closer.

MyRestaurantsArrayAdapter.java

public class MyRestaurantsArrayAdapter extends ArrayAdapter {
    private Context mContext;
    private String[] mRestaurants;
    private String[] mCuisines;

    public MyRestaurantsArrayAdapter(Context mContext, int resource, String[] mRestaurants, String[] mCuisines) {
        super(mContext, resource);
        this.mContext = mContext;
        this.mRestaurants = mRestaurants;
        this.mCuisines = mCuisines;
    }

    @Override
    public Object getItem(int position) {
        String restaurant = mRestaurants[position];
        String cuisine = mCuisines[position];
        return String.format("%s \nServes great: %s", restaurant, cuisine);
    }

    @Override
    public int getCount() {
        return mRestaurants.length;
    }
}

OK, so let’s take a look at this weird line:

String formattedString = String.format("%s \nServes great: %s", restaurant, cuisine);

is what every list item will look like. We could concatenate with + here, but this is a good opportunity to practice String interpolation. The %s represent each value after the closing “, separated by commas. The \n is how we can create a new line in Java, to make our formatting even nicer. String interpolation is a little more sophisticated than concatenating, so make sure you use it!

What else?

Calling super() from a subclass allows you to reference a method that was overridden. It also allows you to call both the original and the overridden method from a subclass, or call the method from the superclass from within the overriding method from the subclass. If you remove this line, you'll see that Android Studio will get unhappy. But why? We need this here as we are inheriting from a class that doesn't provide a default, parameterless constructor. In these cases, you'll need to explicitly call super() from the constructor of your subclass, passing in parameters the base constructor needs.

We don’t need to worry about where the int position is coming from, ArrayAdapter takes care of that under the hood.

Similarly, as mentioned in the comment, we do not explicitly call getCount(), but we need this to be here nonetheless. ListView requires it, so it knows how many items to draw into the UI.

Alright, we are nearly done with our adapter - now we just need to change how our adapter is called.

Jump back over to RestaurantsActivity.java.

Here, we need to change how from invoking boring ArrayAdapter to invoking our cool custom MyRestaurantsArrayAdapter as follows:

RestaurantsActivity.java

MyRestaurantsArrayAdapter adapter = new MyRestaurantsArrayAdapter(this, android.R.layout.simple_list_item_1, restaurants, cuisines); //must match constructor!

Take it for a spin, you should see something that looks like this:

custom_arrayadapter_complete

Great work!