Android Tutorial for Beginners: Part 3

An Android Tutorial that shows you how to make your first app app step-by-step, using Android Studio! By Darryl Bayliss.

Leave a rating/review
Save for later
Share
You are currently viewing page 3 of 6 of this article. Click here to view the first page.

Making the API Call

queryBooks is a great method and all, but it still needs to be hooked up to the EditText and Button to complete the search capability.

Fortunately, that’s pretty simple. In MainActivity.java, find onClick and replace everything inside the Method with this:

// 9. Take what was typed into the EditText and use in search
queryBooks(mainEditText.getText().toString());

Now, every time the user taps the button, this method takes the user’s input in the EditText control and queries the Open Library for books and authors matching that string. Run your app and try it out to see what happens!

Note: If Studio runs into any issues with its Gradle sync, you may get a NoClassDefFoundError upon querying. If so, not to worry — simply select Build > Rebuild Project and try again!

Remember that you haven’t yet hooked up the results to anything that will display them on the screen. You will still see something happening though, open LogCat and you can see the resulting JSON spilled out whenever the API call finishes.

query_in_logcat

This is already exciting and clearly has the potential to be something cool very soon! But it’s still just a jumble of data. Your next challenge, then, is to display this data on the screen in a more organized manner.

Creating the List Rows

First, you need to set up the layout for each of the rows in your list. A simple row of text won’t cut it anymore — you need space for a thumbnail image on the left and then two rows of text for the title and author, respectively. A layout which would look something like this:

cell_sample

Right-click on the res/layout folder in the Studio left pane, and select New > Layout resource file.

new_layout_res_file

Name your file row_book.xml with a root element of RelativeLayout, then click OK.

new_xml_file

The new file will open in Design mode. So, switch to Text mode, as before. Now change the layout_height attribute from this:

android:layout_height="match_parent"

To this:

android:layout_height="75dp"

You simply set the layout to have a specific height instead of matching the height of the parent container.

Next, you’re going to add three views inside the RelativeLayout, and then you’ll witness a few of the capabilities of this type of layout in action. First, add the thumbnail view:

<ImageView
    android:id="@+id/img_thumbnail"
    android:layout_width="50dp"
    android:layout_height="50dp"
    android:layout_marginLeft="25dp"
    android:layout_alignParentLeft="true"
    android:layout_centerVertical="true"
    android:scaleType="centerInside"/>

The ImageView has the width and height set and there’s a little margin to the left of the picture. This is stuff you’ve seen before.

Then it gets interesting with layout_alignParentLeft and layout_centerVertical. These attributes are available since this ImageView is a child of a RelativeLayout. Because of these two attributes, the ImageView stays tight to the left of the cell with that margin intact, and centers vertically.

The last attribute, scaleType, specifies how you’d like the image to display within the amount of space it’s given. Especially given the unpredictable list of screen sizes you’d have to support, it’s often important to set this beforehand.

Using centerInside means that you’ll preserve the aspect ratio of the image and that both dimensions will fit inside the given space. You might, however, have some blank space above and below the image if it’s too short, or space on the sides if it’s too thin. If you’re interested, you can read up on the various ScaleType options.

Next, add a TextView for the book’s title:

<TextView
    android:id="@+id/text_title"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginLeft="25dp"
    android:layout_toRightOf="@+id/img_thumbnail"
    android:layout_alignTop="@+id/img_thumbnail"/>

Read through the XML first and see if you can tell what’s going on. The commands should be starting to make a bit of sense by now. The only thing that might give you pause might be the @+id/img_thumbnail bit – but that’s just a reference to another control by ID. In this case, the ID refers to the ImageView you added previously.

Basically, the title will sit to the right of the thumbnail, such that the top of the title will be at the same height as the top of the thumbnail. There’s some space in between the two controls, as well.

Finally, add the TextView for the author’s name:

<TextView
    android:id="@+id/text_author"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_below="@+id/text_title"
    android:layout_alignLeft="@+id/text_title"/>

By now, these attributes should all make sense. The author’s name will be below the title, with its left side aligned to that of the title.

Note: One quick thing before moving on. Attributes for children of RelativeLayouts often reference other children using IDs, as you saw just now. Make sure that the references don’t get circular (two attributes that depend on each other), or else your XML won’t inflate!

Adapting JSON for a ListView

In Part Two, you made a ListView, at which point I mentioned that ListViews are a bit picky. They don’t want to deal with the data directly — you can hardly blame them after seeing that confusing JSON response that popped into LogCat earlier. The simple, built-in adapter you used in Part Two won’t cut it here; you need a custom one.

Right-click on the com.example.omgandroid folder (or whatever package name you used when creating the project) and select New > Java Class.

new_java_class

Then type in JSONAdapter as the new class name, make sure the Kind is set to Class and then hit OK.

create_jsonadapter

Once you have your new class open in the editor, add the following code so the class looks like this:

public class JSONAdapter {

    private static final String IMAGE_URL_BASE = "http://covers.openlibrary.org/b/id/";

    Context mContext;
    LayoutInflater mInflater;
    JSONArray mJsonArray;

    public JSONAdapter(Context context, LayoutInflater inflater) {
        mContext = context;
        mInflater = inflater;
        mJsonArray = new JSONArray();
    }
}

This is still just a basic class, beginning with the first part of the URL you’ll use to download images — more on that when you implement the image download code.

Next, there are three simple variables:

  • A Context. Your old friend Context. Remember Context is the object that lets other objects know what is happening in your App. Here Context is going to help Picasso, the image downloader Library, display the images you download in your App
  • A LayoutInflater. You need this to inflate a View out of that list item XML you just wrote.
  • A JSONArray. This is the datasource that will be coming in from the server in response to your query!

The JSONAdapter method is the class constructor – that’s what you call when you create a new instance of JSONAdapter. So, anyone who wants to ask JSONAdapter to do anything has got to create an instance of it first, which in turn requires submitting the Context and LayoutInflater via the constructor.

The constructor currently simply saves the passed in references and creates an empty JSONArray. You’ll pass the real data to the class after the search results are in.

Now you need to convert this class into an actual Adapter class. This is quite easy in an object-oriented programming language like Java – simply change the top line of the class from:

public class JSONAdapter {

To:

public class JSONAdapter extends BaseAdapter {
Note: Those with a fuzzy grasp of object inheritance and other object-oriented programming concepts may want a refresher like this one, but in essence, you’re saying that JSONAdapter is going to build on the basics provided by the BaseAdapter class.

Right away, Android Studio will underline the line you just modified in red to let you know that you need to add more to JSONAdapter before it accurately extends BaseAdapter. Studio isn’t just a naysayer, though — it can help, too! Click the underlined line, then click the red light bulb that pops up next to it, and then select Implement Methods from the menu.

implement_baseadapter_methods

When asked to select methods to implement, make sure all four methods are highlighted and click OK.

select_methods_baseadapter

Magically, Android Studio creates four methods for you and the red underlining disappears. This means that you’ve satisfactorily extended BaseAdapter.

But… all the methods are empty. It’s time to go through each one in turn and make them do what you want.

So, first replace the current implementation for getCount with the following:

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

getCount answers the question: How long does your ListView need to be? In this example, the answer is simply the length of your JSONArray. Each entry in that array represents a book and so each one gets a row in the ListView.

Now replace getItem with this version:

@Override
public JSONObject getItem(int position) {
    return mJsonArray.optJSONObject(position);
}

getItem returns the book for a given position, counting up from 0. A single book is represented by a JSONObject. They all just happen to be stored in a JSONArray. So all you have to do is complete a lookup on the array for the JSONObject at the given position.

Next, replace the stub for getItemId with this code:

@Override
public long getItemId(int position) {
    // your particular dataset uses String IDs
    // but you have to put something in this method
    return position;
}

This can be a very helpful method in some situations, but in this case, you don’t really need it. So, you just set it to position. Imagine a situation where you have a list of books, as a subset of a larger database, and each book has an ID in the larger database. If you needed to go back and query for more information based on a certain item’s ID number, this method would be helpful for you.