Make Your First Android App: Part 3/3

Build upon the foundations of the first two part on how to make your first Android app and create a book search application using web APIs. By Matt Luedke.

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

JSON Basics

Great! Now it’s time to meet your datasource: the Open Library API. It’s a constantly-updated database of books, searchable by author and title. The wealth of data is enormous!

Try this query as an example: http://openlibrary.org/search.json?q=hunger+games+suzanne+collins

This is a relatively simple URL to understand; whatever is typed in after the ?q= is the query string that will be used to search the database. Feel free to change it to a different author name/title and compare results, remembering to use + to separate words.

The result is in the form of a large JSON response. Take a minute to look around at the sort of data the response includes.

If you’re not familiar with the JSON format, I suggest a quick glance through the JSON page. The basics are that there are two ways data can be arranged in JSON: arrays and objects.

JSONArrays list objects of the same type. For example, a list of books might look like this:

["The Hunger Games", "Harry Potter and the Sorcerer's Stone", "A Game Of Thrones"]

JSONObjects are a collection of key-value pairs. For example, a very simple object describing a book might be:

{"title" : "The Hunger Games", "author_name" : "Suzanne Collins"}

The results from your queries to the Open Library API are really just expansions on those two basic structures. They are larger, and nested into several levels, but the ideas remain the same.

Creating a Query

Now that you know roughly what to expect from the datasource, it’s time to set up the code to go make a sample query!

Add the following variable at the top of MainActivity.java:

	private static final String QUERY_URL = "http://openlibrary.org/search.json?q=";

The above is simply a reference to the URL you’ll be calling. It’s much better to hold this URL as a static String, so you don’t have to go searching through your code for it. Remember that you’re going to append the search string after the ?q=.

Next, add this new method:

	private void queryBooks(String searchString) {

		// Prepare your search string to be put in a URL
		// It might have reserved characters or something
		String urlString = "";
		try {
		   urlString = URLEncoder.encode(searchString, "UTF-8");
		} catch (UnsupportedEncodingException e) {

			// if this fails for some reason, let the user know why
			e.printStackTrace();
			Toast.makeText(this, "Error: " + e.getMessage(), Toast.LENGTH_LONG).show();
		}

		// Create a client to perform networking
		AsyncHttpClient client = new AsyncHttpClient();

		// Have the client get a JSONArray of data
		// and define how to respond
		client.get(QUERY_URL + urlString,
				new JsonHttpResponseHandler() {

					@Override
					public void onSuccess(JSONObject jsonObject) {}

					@Override
					public void onFailure(int statusCode, Throwable throwable, JSONObject error) {}
				});
	}

queryBooks handles the network call to the API. It encodes the input searchString into URL format, then appends that to the base URL you specified at the top of the class.

Calling new AsyncHttpClient() simply creates an instance of the HTTP client. It’s got a lot of great methods built-in, but the only one you need here is get(String url, ResponseHandlerInterface responseHandler).

The get method takes in two parameters:

  • String url is simply the URL from which you’d like to fetch data. You supply an input made of the base URL defined at the top of the class, plus the search string.
  • JsonHttpResponseHandler, which you define now, even though you don’t know whether the network call will succeed or fail, or how long it will take either way. It contains methods called onSuccess and onFailure to respond to the two cases once the handler does get a response from the server.

You might have noticed that onSuccess and onFailure are currently method stubs with no code. Fix that by fleshing out the onSuccess to match the following:

@Override
public void onSuccess(JSONObject jsonObject) {
	// Display a "Toast" message
	// to announce your success
	Toast.makeText(getApplicationContext(), "Success!", Toast.LENGTH_LONG).show();

	// 8. For now, just log results
	Log.d("omg android", jsonObject.toString());
}

Next, implement onFailure as follows:

@Override
public void onFailure(int statusCode, Throwable throwable, JSONObject error) {
	// Display a "Toast" message 
	// to announce the failure
	Toast.makeText(getApplicationContext(), "Error: " + statusCode + " " + throwable.getMessage(), Toast.LENGTH_LONG).show();

	// Log error message
	// to help solve any problems
	Log.e("omg android", statusCode + " " + throwable.getMessage());
}

In both cases, you simply present a Toast and log the results. Soon, though, the success case will get a lot more exciting.

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 the current implementation 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. But 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.

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: The one thing I’d like to briefly note before moving on is that the attributes for children of RelativeLayouts often reference other children using IDs, as you saw just now. Just make sure that the references don’t get circular (two attributes that depend on each other), or else your XML won’t inflate!
Matt Luedke

Contributors

Matt Luedke

Author

Over 300 content creators. Join our team.