In the last post we created a basic Android project using Android Studio templates. While it’s great that we have a fully functioning master/detail style app, it does look a bit bare. In this post, we’ll change this by styling our list view, incorporating (sort of) real data to feed our list. We’ll make a custom adapter to drive our list with custom layouts, and introduce testing into the mix to ensure that our app continues to perform as expected.
At the end of this post, this is how your pet list will look. I’ve made a few stylistic changes from the original mockups to allow the imagery to really fill the content area.
As always, now is the time to make a new feature branch:
Add Test Library
With any software project, it’s good to be able to run tests to ensure that your software works as intended and free of bugs. I’m, admitedly, relatively new to testing, but have found it quite helpful for Hashnote to raise my confidence in my software and ensure that bugs don’t creep in, especially ones I’ve already fixed that might come back.
Last year Google released Espresso, a functional testing framework that makes testing Android apps easy. Jake Wharton has converted Espresso into a Gradle based project, Double Espresso, to alleviate much of the headache of including it in your project.
We’ll be using Espresso for our testing, via Wharton’s gradle port, so we’ll need to add the dependencies. Open up your
adoptme/build.gradle file and add
instrumentTestCompile 'com.jakewharton.espresso:espresso:1.1-r2' to your dependencies section.
1 2 3 4 5
This tells Gradle to include the Espresso library for the
instrumentTestCompile build phase, but not the others. This means that it will be packaged with our app for testing but not production. We’ll be doing a slightly different version of this as we add libraries for other aspects of our app, but we’ll go into more detail about that later.
You also have to set the
testInstrumentationRunner like so:
1 2 3 4 5 6
That’s it for testing setup, now when we get into writing code for our app we’ll be all set to create tests to go along with it.
Add Sample Data
When I’m working on apps that don’t have live data yet, like in our case, I like to add sample data in a separate module from the main code. That makes it easy to remove later, once web service calls are in place.
To do this, simply right click on the root of the project view, and select New > Module. Then select Android Library as the module type, and fill in the rest of the data like we did before.
This will add a new section in the project browser called
sample. To here, add the sample data classes that I’ve created from here.
The sample data uses the Gson library to parse the sample JSON file, so you’ll need to add that dependency to your
1 2 3 4
The last thing we have to do is add a dependency to our project. Open up the project
build.gradle file, which you can find in the
adoptme directory, and modify the
dependencies closure to include
compile project(':sample'). This tells gradle that our main project depends on the local sample project.
1 2 3 4 5 6
This may seem like a lot of steps to go through just to include sample data, but it uses gradle to keep our sample classes segregated from the main app. That way, when we’re ready to implement real classes connecting with our web services, we just remove the sample module and we know we won’t have any legacy code left around.
What makes a dependency
Let’s take a moment to dive a little deeper into these dependency lines. So far we’ve added three types of dependencies, build phase specific dependencies, remote dependencies, and local dependencies.
First let’s examine the standard remote dependency line
compile 'com.google.code.gson:gson:2.2.4'. Many libraries are available as Maven dependencies, and you can search the Central Repository here. If you search there for
gson then click the latest version, you will see this:
You’ll notice that the
Version on the left align with the compile line in our
build.gradle file. This is, in fact, where I got the correct information to include Gson in our project.
While Maven Central is host to many projects, there are some that either aren’t there yet, or shouldn’t be there. Our sample data module is a good example of this. Libraries like this are included as local dependencies with the line
That sure is easy, but you might be wondering how Gradle knows what
:sample means. Open up the
settings.gralde file in the root of our project to find out.
This one line tells Gradle what projects are included in the build. These are really just simple paths with
: as a path separator.
So, for instance, if you wanted to include a library project who’s library was stored at
libraries/action-bar-sherlock/library, you would add
':libraries:action-bar-sherlock:library' to this string and include it with
compile project(':libraries:action-bar-sherlock:library') in your
Build phase specific dependencies
Sometimes you have a library that you only need for testing, as is the case with Espresso. This is done by replacing
compile in the dependency line with the name of the build phase, such as
instrumentTestCompile. This tells gradle to include that dependency when it compiles the instrumentTest phase of the build.
For more information about dependencies, read the Android Build System User Guide.
Create an Adapter
Now it’s time for the fun part, making Android dance. We’ll start out by creating an
Adapter to drive our ListView.
Since mobile devices have limited memory, it would be extremely innefficient to build all of the views that go in a ListView at once, then store them all in memory as the user scrolls through them. Imagine how much memory it would take to store your entire Gmail inbox in memory at once. This is where Adapters come into play.
An Adapter is an interface between an AdapterView (like ListView, GridView, Spinner, etc) and the underlying code. Effectively it’s a View factory that knows how to create a View for each item in a list, and does so on command.
As you scroll through a list, the ListView only keeps the views it needs to display in memory. When it is ready to display a new item on the screen, it requests that View from the Adapter, which builds the appropriate view based on it’s position in the list.
When an item is no longer being displayed in a list, that View is ‘recycled’, or sent back to the adapter so that it can simple modify values in the View instead of creating an entirely new View from scratch.
Start out by creating an Adapter for our Pet list (I put mine in the
1 2 3 4 5 6 7 8 9 10 11
Notice that we are actually using a concrete implementation of Adapter, ArrayAdapter, configured to store and serve
Pet objects. ArrayAdapter takes care of much of the legwork for us.
Create the adapter test
Before we get into any more code in our adapter, let’s create our first test.
Android Studio makes creating Test classes really easy. Anywhere in the contents of the PetsAdapter class, press command-shift-T and you will be presented with a dialog allowing you to create a new test.
Set your testing library to JUnit3 (Android’s testing tools still run version 3), and check the box to generate a setUp method. Then create your test class.
In this test class we will be testing to make sure that our Adapter is returning the correct Views. To start, let’s just make sure it returns the correct count. Update your
PetsAdapterTest class to look like this.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
Let’s go through this file piece by piece. First of all, notice that I changed the base class to
InstrumentationTestCase. This is an Android specific addition which adds support for things like target contexts and resources.
setUp() method is going to be called every time a test method is run, to configure the appropriate items in a clean manner. In this case, we’re going to fetch our sample Pet data from our
SampleDataUtils, then construct the adapter we wish to test with those Pets.
The next method,
testGetCount_returnsCorrectCount() is our first actual test method. JUnit 3 identifies test methods as public methods starting with
test. I’m not sure what the actual convention is, but I like to name my methods with the format
Method is the method on the target that I’m testing, and
behavior is the action that the test verifies. For this simple test that might seem like overkill, bit it scales well to more complex tests, as we’ll see later.
In this simple test, we just call the
getCount() method on the adapter and ensure that it returns the expected number of items.
Running the tests
What good is a test if you don’t run it. You could run these tests from the command line, as I often do, with the command
./gradlew cIT, but Android Studio also offers some very helpful ways to run arbitrary gradle tasks.
Click on the Gradle tab on the right edge of your Android Studio window and you’ll be presented with a list of available Gradle tasks.
adoptme here and find the task titled
connectedInstrumentTest. This will run our instrument tests on all devices currently connected. Double click this and you will notice your run bar changes to show the connectedInstrumentTest target and your tests are run.
From now on, whenever you want to run your tests, just click the Run button with the
connectedInstrumentTest configuration selected. Use the drop down to select
AdoptMe to run your app normally.
Your test should complete successfully, since
ArrayAdapter takes care of much of the work of our Adapter for us.
Add the layout
After all of this you’re probably thinking, “Hey Ryan, I’ve done all this and I still don’t have a cool looking list view!” I hear you, we’ll get to that now.
Create the layout
We’ll start out by creating our layout in XML. I would normally write a test first, but in order to access fields by id in a test, we need the XML in place to create the id.
Create a new layout file by right clicking on the
res/layout directory and selecting New > Layout Resource File. My convention is to name all layouts for list items with a
row_ prefix, so I named this one
row_pet.xml. Here’s the contents for our file.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
You’ll notice a few red lines since we’re referencing resources that don’t yet exist, so lets fix that first.
If you put the insertion point in the red part of the
@dimen/pet_list_photo_height line, you will notice a little red light bulb appear to the left of that line. Clicking that light bulb (or hitting option-return on Mac) will give you the option to create the appropriate dimension resource for this. Click that and set the value to
This is a common practice on Android, and allows you to externalize your values so that they can be different on different platforms. Perhaps on tablets we’ll want this to be
Do the same thing for the
@drawable/bg_pet_text line, only this time we’ll create a drawable xml resource file. This will be the gradient background for our text protection. Here are the contents of the file:
1 2 3 4 5 6 7 8 9
Moving on to the two style resources, create those and just leave the content field empty. Then, with the insertion point in one of the resources, hit command-B and you’ll be taken to that resource declaration.
These style resources allow the same sort of value extraction, and allow us to easily inherit from Android’s built in styles. Heres the code.
1 2 3 4 5 6 7 8 9
Create these color resources with the value
If you now go back to your
row_pet.xml file, you should be free of red lines and see a preview of the row layout to the right of the editor.
tools:prefix as opposed to the
android:prefix. This means that this attribute won’t be included in the production build, but allows the preview window to show it.
Write a test to verify the name
That’s right, another test. In the TDD fashion we’re going to write our tests first, watch them fail, and then make them succeed. This flow was described to me as “Red, Green, Refactor”, saying that you see the test fail to ensure the test is doing something right (red), then you make it succeed by implementing the code (green), then you go through your code and make it nicer (refactor).
We want to test that our Adapter returns views with the correct pet name, so we can do that like so.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
In this test we call the
getView method of the adapter to get a view for the first pet in the array. We then find the TextView with an id of name and check that the text matches the actual name of the pet.
We do the exact same thing for the breed, which is also displayed on the row.
Create the view
Now that we have a failing test, we can adjust our adapter to make the test pass. We’ll do that by implementing the
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
The first thing that I’ve done is add a member variable for our LayoutInflater. We’ll be using the LayoutInflater to inflate our views, and don’t want to have to fetch it from the system each time, so this allows us to just get it once.
The next thing to notice is the
ViewHolder class that I’ve created. This is a standard pattern in Android an allows our list scroll smoothly, since
findViewById calls can be expensive. You can read more about the ViewHolder pattern here. In our case, the ViewHolder will hold a reference to the ImageView and both TextViews.
Now onto the getView method. To start out we make a copy of the
convertView parameter that was passed in (just for clarity) and check to see if it’s null. If so, we inflate a new
row_pet and set up our ViewHolder.
convertView wasn’t null, then all we have to do is get the
ViewHolder that we previously set as a tag on the view. This is where the view recycling that I was talking about earlier comes into play, since the ListView doesn’t need this view anymore, we can reuse it by changing values instead of inflating an entirely new view.
That bit of code we just reviewed, the View inflation and ViewHolder setup block, is standard code that will likely be at the beginning of all of your
Moving on, we find the pet that this row is expected to represent by calling the Adapter’s
Next up we have a call to a library that we haven’t yet included. Picasso is an excellent library that makes loading images, particularly from URLs in Lists, super easy and takes care of things like cancelling network requests when views are recycled for you.
To use this library you need to add a compile dependency to your
In order for the Picasso library to fetch images from the internet, we have to enable internet permissions on the app. You can easily do that with one line in the manifest.
After loading the image the last piece is just setting the pet’s name and breed on the TextView’s we collected earlier.
Now, if you run your tests again, you should find that the tests pass, as our new view has the appropriate name and breed listed in the appropriate TextViews.
Using the adapter
The last piece to get this to affect our ListView is to set our new
PetsAdapter as the ListView’s adapter. We can easily do this by modifying the
onCreate method of the PetListFragment.
1 2 3 4 5 6 7 8 9
Now, if you run your app (don’t forget to change the Run Configuration back to
AdoptMe), you should see your beautifully styled list view showing sample dogs.
Committing our changes
The last step in any feature is committing our changes, and merging them back into the master branch.
1 2 3 4 5 6 7 8
In this article, we went through much of the mundane setup of the testing framework (don’t worry, we won’t do that every time), created a custom layout for each row of our Pet list, and created a custom Adapter to configure our rows with the appropriate Pet. We did all of this in a TDD fashion, allowing us to verify our code later to ensure that no bugs have crept in.
You can check out my code at this point on Github on the
step_2 branch and, as always, leave some comments if you have any comments or questions, and to share your progress.