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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Android Tutorial for Beginners: Part 3
55 mins
- Getting Started
- Networking Considerations
- A Glance at Gradle
- JSON Basics
- Creating a Query
- Making the API Call
- Creating the List Rows
- Adapting JSON for a ListView
- Putting Together the Insta-Row
- Connecting the List to the Adapter
- Updating the List Data
- Showing Progress
- The Detail Activity
- The Up and Back Buttons
- An Intent to Show the Detail Activity
- Sharing the Image
- Where to Go From Here?
Putting Together the Insta-Row
The last method, getView
, answers the ListView
when it comes to the adapter and asks: What should I show at position X?
To begin to answer that question, you first need to create what’s called a view holder. Add the following to the end of your JSONAdapter
code (but before the final closing curly brace):
// this is used so you only ever have to do
// inflation and finding by ID once ever per View
private static class ViewHolder {
public ImageView thumbnailImageView;
public TextView titleTextView;
public TextView authorTextView;
}
This class is simply a packager of the three subviews that every row in your list will have. Think of it as a Do-It-Yourself kit for your list cells. All each row needs to do is get one of these, update it with the right data based on the row and presto: an Insta-Row!
The trick is that as you scroll around through who-knows-how-many books in your list, the app shows the data using the same cells, over and over. There are only just enough list cells to fill the screen, plus a few extras. Keeping all of the list cells in memory, even while they’re off-screen, would get crazy!
As a view scrolls out of sight, the recycling crew comes by and dumps out everything inside the view, but hangs onto the ViewHolder
. That same view, and the ViewHolder
, then get handed over to a list cell about to scroll into sight.
The re-used view is handed one of these ready-made Insta-Row kits (aka a ViewHolder
), and simply fills the contents of each subview as needed, rather than inflating a brand new view from XML and creating all those subviews from scratch every single time.
For more details on the view recycling process, here is a helpful blog post about it.
With that in mind, replace the stub for getView
with this code:
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
// check if the view already exists
// if so, no need to inflate and findViewById again!
if (convertView == null) {
// Inflate the custom row layout from your XML.
convertView = mInflater.inflate(R.layout.row_book, null);
// create a new "Holder" with subviews
holder = new ViewHolder();
holder.thumbnailImageView = (ImageView) convertView.findViewById(R.id.img_thumbnail);
holder.titleTextView = (TextView) convertView.findViewById(R.id.text_title);
holder.authorTextView = (TextView) convertView.findViewById(R.id.text_author);
// hang onto this holder for future recyclage
convertView.setTag(holder);
} else {
// skip all the expensive inflation/findViewById
// and just get the holder you already made
holder = (ViewHolder) convertView.getTag();
}
// More code after this
return convertView;
}
If it happens to be the first time for the view, then you need to use your custom row XML using mInflater
and find all your subviews using findViewById
. But as mentioned earlier, the view might already exist — in which case you want to skip all that from-scratch stuff.
You use the setTag
and getTag
methods to hang onto the ViewHolder
and easily pack/unpack it while scrolling around.
Next, you need to handle the image thumbnail of the book’s cover. Put this new code right after the // More code after this
comment line:
// Get the current book's data in JSON form
JSONObject jsonObject = (JSONObject) getItem(position);
// See if there is a cover ID in the Object
if (jsonObject.has("cover_i")) {
// If so, grab the Cover ID out from the object
String imageID = jsonObject.optString("cover_i");
// Construct the image URL (specific to API)
String imageURL = IMAGE_URL_BASE + imageID + "-S.jpg";
// Use Picasso to load the image
// Temporarily have a placeholder in case it's slow to load
Picasso.with(mContext).load(imageURL).placeholder(R.drawable.ic_books).into(holder.thumbnailImageView);
} else {
// If there is no cover ID in the object, use a placeholder
holder.thumbnailImageView.setImageResource(R.drawable.ic_books);
}
In this section, you first get the JSONObject
for the precise book whose data you want to display. Of course, this is dependent on the item’s position in the list.
Next, you check to see if there’s a cover ID for that book. Unfortunately, many books don’t have covers in the Open Library database. So, you look to see if a cover is there by calling has("cover_i")
, which returns a true-or-false boolean
. If it returns true
, then you parse out the cover ID from the JSONObject
and use it to construct a URL specific to Open Library.
You can change the “-S.jpg” to “-L.jpg” for a larger version of the same image: http://covers.openlibrary.org/b/id/6845816-L.jpg
You can change the “-S.jpg” to “-L.jpg” for a larger version of the same image: http://covers.openlibrary.org/b/id/6845816-L.jpg
Once you have the URL, you simply tell Picasso to download it and display it in your ImageView
. You also specify a placeholder image to show while the cover image is downloading.
If the book doesn’t have a cover assigned, you show the standard icon.
Finally, you need to populate the book title and author name. So, add the following code immediately after the block of code you added above:
// Grab the title and author from the JSON
String bookTitle = "";
String authorName = "";
if (jsonObject.has("title")) {
bookTitle = jsonObject.optString("title");
}
if (jsonObject.has("author_name")) {
authorName = jsonObject.optJSONArray("author_name").optString(0);
}
// Send these Strings to the TextViews for display
holder.titleTextView.setText(bookTitle);
holder.authorTextView.setText(authorName);
This step is similar to the last. As long as the JSONObject
contains the title and author name, you parse the values and set the text of each TextView
!
Connecting the List to the Adapter
The last thing you need to do before you can test your newly-webified app is connect the ListView
to the JSONAdapter
.
Remove the following code from onCreate
in MainActivity.java:
// Create an ArrayAdapter for the ListView
mArrayAdapter = new ArrayAdapter(this,
android.R.layout.simple_list_item_1,
mNameList);
// Set the ListView to use the ArrayAdapter
mainListView.setAdapter(mArrayAdapter);
Also remove the following from onItemClick
in MainActivity.java:
// Log the item's position and contents
// to the console in Debug
Log.d("omg android", position + ": " + mNameList.get(position));
You don’t need any of that simple stuff now that you’ve got your own souped-up Adapter
!
Now, to start using the your adapter class, replace this line at the beginning of MainActivity.java:
ArrayAdapter mArrayAdapter;
With this:
JSONAdapter mJSONAdapter;
Next, add the following to the end of onCreate
:
// 10. Create a JSONAdapter for the ListView
mJSONAdapter = new JSONAdapter(this, getLayoutInflater());
// Set the ListView to use the ArrayAdapter
mainListView.setAdapter(mJSONAdapter);
Great! You just created an instance of your snazzy new JSONAdapter
, feeding it a Context
and a LayoutInflater
. Your Activity can be used as a Context parameter (via the this keyword) because Activity is a Subclass of Context. Now your adapter is hooked up and can provide your ListView
with the data it needs.
If you were to build and run, though, you would be rather underwhelmed by the results. Even after inputting a search String
, the ListView
remains empty. Why?
Because, if you recall, you created your Adapter
using its constructor, public JSONAdapter(Context context, LayoutInflater inflater)
. That method creates an empty JSONArray
as a placeholder.
An empty list is OK to start with, of course, but it sure would be great to update the list when your search is done! That’s not happening yet, so that’s next on your agenda.