Update note: This tutorial was updated for the latest version of Android Studio by Darryl
Bayliss. Original tutorial by Matt Luedke.
But when does the Android version come out?
The first blog comment after any iOS app gets announced.
Clearly theres a demand for Android app development, and its turning the platform with the
lovable green mascot into more and more of a strong first choice rather than just a secondary
option to iOS.
With over one billion devices activated, Android is an exciting space to make apps to help you
communicate, organize, educate, entertain or anything else youre passionate about.
If thats not enough, here are a few more reasons to learn Android:
Youll be plugged into the open source platform with (at the time of press) the largest
market share of smart devices worldwide.
Androids policies on device provisioning and app submission are more open than
Apples, meaning that once you complete your first appas youll do in this tutorial
you and your friends can enjoy it on your devices right away!
If you have experience developing for iOS, you can become well-versed in the ways that
the two platforms coincide and differ (well discuss a few in this tutorial) and what you
like about each. Then youll have more tools at your disposal for your next mobile
project.
Its not just the iPhone anymore. There are so many smartphones, tablets, glasses, and
watches out there, coming from so many manufacturers, and theyre all trying to jump
into the game. You dont have to be any sort of market analyst to know that there are a
few important platforms and Android is one of them.
So if youve been intent on, thinking about, or simply playing with the idea of learning
Android Make Your First Android App is the tutorial series for you!
There arent any prerequisites to start. Youll learn how to set up all the tools you need to become
an Android developer-in-training. Then youll put together your own full-fledged Android app
from scratch! This app will, when completed, help you get details about a book youre interested
in using online search sources.
By the end of Part Three of this series, your app will include useful features like:
Android, and several popular frameworks for it, make all of these features really simple to
implement. Why not start learning this easy and powerful platform for yourself, today?
Getting Started
How does Android development go down? First, the zoomed-out basics:
Youll write your programmingwhat you want your app to doin Java files and design
your layoutshow you want your app to lookin XML files.
Once your app is ready, youll use a build tool to compile all the project files and package
them together into a .apk file that you can run on Android devices and/or submit to
Google Play.
All of the files you used to put your app together are managed by an Integrated
Development Environment (IDE). The IDE is the program you will open to edit your
code files and to manage your projects.
The standard IDE for Android used to be Eclipse, but this is now being replaced by
Googles own Android Studio.
If you zoom in (metaphorically), youll find more in-depth processes going on behind the scenes
during all of the above steps. For example, advanced users will want to investigate the role of the
Dalvik Virtual Machine and its new replacement, ART.
But for now, lets move on. Heres what youre going to accomplish in this part of the tutorial:
1. Download and install Android Studio.
2. Set up testing on devices and emulators.
3. Create a simple Hello World! Android app that prints to your devices screen.
4. Make small edits to the app so it can congratulate you by name.
In the second part of this tutorial, youll make an app that records a message you type, adds it to
a list, saves it in memory and shares it with friends. Along the way, youll learn how to add and
Once you have the Terminal open, type in java -version. You should see some output that
mentions a version number, like below.
If you dont have the JDK installed, your Terminal will tell you command not found. If thats
the case, you should download the JDK from Oracle.
When ready, head over to the Android Studio page and click the big green button to download
the correct version for your Operating System.
Google is constantly updating this page, so the version you see may be newer than the screenshot
above. Once you have clicked the button youll be asked to agree to the Terms and Conditions:
After reading these carefully (as you always do) accept the Terms and Conditions and click the
blue button underneath titled Download Android Studio. Your download will finally begin. It
may take a few minutes but once finished you can install Android Studio similar to any other
program.
The download page will helpfully redirect to a webpage that contains installation instructions for
OSX, Windows and Linux Operating Systems. If for some reason the instructions dont appear
then you can also view them here.
Now that youve installed Studio, lets fire it up! Launch Android Studio.
Once Android Studio has finished loading, youll be greeted with the Setup Wizard the first time
Studio loads.
Click Next to move to the next screen. This screen will ask what type of setup you would like.
Make sure Standard is checked and click Next again to move to the next screen to accept some
licenses for components that will be installed on your computer.
Accept these and click Next one last time to begin downloading the extra components you will
need.
After a few minutes you will have everything you need to begin building fantastic Android Apps
and will move to the welcome screen.
Even though you just downloaded Android Studio, you might not actually have the latest version.
Right away, you need to check to see if any updates are available and if necessary, follow any
instructions to get the latest version. You can check whether any updates are available by
clicking check for updates at the bottom of the welcome screen.
If an update is available you will have a window like this appear:
When you see this screen. Always choose Update and Restart. In some cases, you may need to
download the full installer in that case you will see a Download button that will take you to
instructions for installing.
Great! As all programmers in the movies say when they are successfully greeted with a new
interface: Were in!
The menu will slide across and present a new menu with various options. The option you want is
the SDK Manager. This is the go to place if you need to download a specific version of Android
to develop or test your app on. It also contains other useful things such as API documentation,
version specific code samples and even previews of software kits that interact with Android such
as Android Wear and Google Glass.
For now lets focus on downloading a version of the Android SDK. Click the SDK Manager
option, a new window will present itself to you with checkboxes, folders and statuses across the
page.
Lets make some sense of this. Each folder is a directory of tools, software and documentation.
The majority of directories are specific to an SDK version of Android but there are some that
cater to multiple versions. The first folder for example, entitled Tools, is a folder that contains
components that are designed to assist in the development of Android and work across multiple
SDKs.
The Android 5.0 (API 21) directory on the other hand holds everything specific to that version of
the Android SDK. Including sample code for new features within the SDK, API documentation
and system images you can use to simulate a certain mobile architecture using an AVD.
If you dont understand what all this means then dont worry, just think of each folder as a place
where each version of Android and their tools live happily. From the latest preview version right
down to the original Android Beta (API 2).
You should already have the latest version of the Android SDK Tools, Android SDK Platformtools and Android SDK Build-tools downloaded from the tools directory. If an update for any of
these tools is available then the checkbox next to the tool will automatically be ticked, alongside
a polite message in the Status column of the window telling you what the latest version is.
For now lets download the previous version of Android, that being Android 4.4.2 (API 19).
Click the tick box next to the folder icon for Android 4.4.2, this will select everything within that
directory for download. You can always come back to the SDK Manager to delete anything you
dont use.
Important: Its alway worth opening the SDK Manager every time you start Android Studio to
see if there is any updates to any tools or SDKs you have installed. This ensures you get access
to the latest features in your alongside any fixes for nasty bugs.
Your SDK Manager window should look something like this:
Click Install x packages (x being the amount of packages checked for download) on the bottom
right of the SDK Manager. A new window will appear with a drop down list of the packages you
wish to install, simply click the root of the drop down list and then click the Accept License radio
button in the bottom right.
You may need to do this for multiple licenses depending on what packages you download.
Finally, click the install button at the very bottom right of the window to begin your download.
The window will disappear and the SDK Manager will begin to download and install your
selected items. Go grab a drink and take in what youve just done whilst the SDK Manager is
ticking away. This will become a regular task that you need to get accustomed to so you can
quickly acquire and update the various SDKs you want to work with.
Once the SDK Manager has finished downloading and installing your items you can move onto
creating your first Android App!
Note: If you currently have an Android Studio project open and the Welcome screen isnt
showing, select File\New Project from the menu to create a new project.
Studio will present you with your first project creation screen:
Enter OMG Android in Application name as shown above. Feel free to put your own name in the
Company Domain textfield. As you type, youll notice the Package Name will automatically
change to create a reverse domain style name based on your Application name and Company
Domain.
The Package Name is used to uniquely identify your app amongst other apps, that way any work
an Android device has to be perform on behalf of the app always knows its source and cant get
confused between two apps. iOS developers will recognize this concept as being similar to the
Bundle Identifier.
You can set the Project location to any location on your hard drive you do not need to follow
the screenshot for that one :]
Click Next at the bottom of the window to progress to the next part of the project setup.
The next screen is where you select device types and operating systems to target. Want an App to
focus on just Phone and Tablet? Not a problem! How about an App for TV and Google Glass?
Thats great too. For now though you just want an App that works on an Android Phone. This
option is selected as the default, alongside the default Minimum SDK.
The Minimum SDK drop down menu sets the minimum version of Android needed to run your
app. Selecting this value for your own projects is a matter of balancing the SDK capabilities you
want and the devices you want to support.
For your first app, youll use the default API 16, which is Android 4.1 (Jelly Bean). Every app
will have different requirements and you may want to choose something else, depending on the
situation.
If you really want to get into the details of what Minimum SDK version is best for your App then
Android Studio can help you out. As you change the Minimum SDK in the drop down menu, the
percentage in the text underneath reflects what percentage of devices currently run that version
of Android.
If you are more about features than numbers, Android Studio has got your back too. Click the
highlighted Help me choose underneath the drop down list to display a useful new window.
What you are looking at is a bar chart, split by the amounts of devices running a specific version
of Android. Click on any part of the bar and the text to the right will change, telling you the most
significant features of the Android Version that part of the bar represents. Useful if you need a
quick overview of what each version of Android provides for your App.
Take note of the Cumulative Distribution values on the right of the bar. This represents the
percentage of devices supporting that particular version of Android which includes backwards
compatibility. As you descend down the bar, the version of Android increases and you gain more
features that your App can make use of. The downside to this is the amount of devices that can
actually run your App is reduced.
Picking what Minimum SDK your App will require is a crucial choice. It will influence what
features you have available to you in your project while also influencing how many Android
users can run your App. Choose wisely!
For more information on API versions and their use, check out the Android Dashboards, which
are updated every few days.
Getting back to the new project window, click Next in the bottom right to pick more options for
your project.
This screen lets you choose a default Activity for your app. Think of an Activity as a window
within your App that displays content the user can interact with not unlike a View Controller in
iOS. An activity can take up the entire screen or it could be a simple pop-up.
Available activities on this template range from a blank activity with an Action Bar right up to an
Activity with a MapView embedded. You will be making a lot of activities, so its good to get
accustomed with them.
Select the Blank Activity option and click Next.
If you have made it this far then well done, you are at the final screen before you dig into some
actual coding. To speed this part up a little bit you will use the pre-populated default values, but
what is actually done with these values?
Activity Name. This will give your Activity a name to refer to in code. Once the project
setup is complete Android Studio will create a .java class and use the contents of this
textfield to give the class a name. This is the name you will use to refer to your Activity
inside your code.
Note: What its actually doing is making a subclass of Activity. Those familiar with
object-oriented programming will know what this is, but for newcomers, this basically
means that your MainActivity is going to be a customized version of Activity that acts
just like the default one, handling things like its lifecycle and the user interface display.
Layout Name. Youre going to define your Activity in Java, but the layout of everything
it will show to the user is defined in a special sort of Android XML. You will learn how
to read and edit those files shortly.
Click Finish. Android Studio takes this as its cue to go do a bunch of behind-the-scenes
operations and create your project. As it shoots out some descriptions of what its doing from
time to time, you may notice it say something like the following:
You see your project name, which is familiar. But then there is this Gradle word, and then a
mention of Maven in the URL. The benefit of having a modern IDE like Android Studio is that it
handles a lot for you. But as youre just beginning to learn how to use the software, its good to
know, in general, what its doing for you.
Gradle is a new build tool that is easy to use, but it also contains a lot of advanced options
if you investigate it further. It takes your Java code and XML layouts, and then uses the
latest Android build tools to create the app package file, known as an APK file. You can
customize your configurations to have development or production versions of the app that
behave differently, or add dependencies for third-party libraries.
Maven is another project build tool, and it can also refer to the Maven Central repository
of java libraries. It is absurdly easy to use Gradle and Maven Central in concert with
Android Studio to incorporate all sorts of functionality from the Android development
community. Those with an iOS background will know how cool this sort of thing can be
from using the CocoaPods tool. Youll learn more about Maven in Part Three of the
tutorial.
After a brief moment, Android Studio will finish building your project. The project is pretty
empty, of course, but it still has everything it needs already set up so that it can be launched on
an Android device or emulator. You will be dropped off in this spot:
Android Studio will contain three windows. To the left you have your project folder structure, the
middle contains a preview of your layout on a Nexus 5 device and finally the right shows your
layout hierarchy as well as attributes if you have a part of your layout hierarchy selected. Before
you get into any programming, lets talk about how youre going to get this app running. Its time
to say Hello world!
If you ran through the setup wizard earlier using the standard installation then you will already
have an emulator setup and ready for you to use. Making use of some useful software developed
by Intel to ensure your emulator runs quickly for your testing needs.
Up until recently, your computer would have to emulate everything an Android device would try
to do. Everything down to its hardware which runs using an ARM based processor. Most
computers these days make use of x86 based processors, which means your computer would be
doing computationally intense tasks that take a significant amount of time just to test your App.
You still have the option to do this if you want to create an emulator that is as close to an actual
device as you can, but be aware that the initial load times have put off many an Android
Developer using the emulator all together.
All of that being saidlets set up an emulator anyway, because youre going to need to know
how!
Click AVD Manager. Its the button in the toolbar that has an Android popping its head up next
to a device with a purple display.
As you will see. Android Studio has already created an AVD for you to use. Youll see a few
details about it, notably what type of emulator it is, what API it is using and what CPU
instruction set it uses.
Lets run through creating a new AVD. Click Create Virtual Device in the bottom left to begin
configuring a new virtual device.
The first decision you need to make is what type of device you want to emulate. The Category
list to the left shows all the types of device you can emulate. Clicking each option shows you
what type of devices are available to you in that category. For now you just want to emulate a
phone sized device but if you wanted to emulate an Android Wear watch or an Android TV then
you have options to do so here.
Select Nexus S in the list of devices available to you from the phone category and click Next.
Your next decision is to decide what version of Android you want your virtual device to run. You
will already have one or two available to you thanks to the setup wizard, so lets use one of them.
Select Lollipop and make sure the one selected has the value x86 in the ABI column. We want the
emulator to be running as fast as possible on our x86 computers. :)
Click Next once this is done to move to the final screen. The last screen is simply a confirmation
of your previous choices with the option to configure some other properties of your device such
as device name, startup orientation, and RAM size. For now leave these set as their defaults and
click Finish
Congratulations! With relative ease youve just created a fresh virtual device ready for use to test
out your new app.
Now, close the AVD Manager to go back to Android Studios main view. Now that youve
configured everything, theres but one step left
Click the Run button. It looks like a play icon.
A new window will appear, asking you to choose the device you wish to test your App on. You
currently have no devices running, so lets start the AVD you just created. Ensure the Launch
Emulator radio button is checked and your AVD is selected in the drop down menu, then click
OK.
Note: If you get an error that says This AVDs configuration is missing a kernel file!!, check to
make sure that you dont have the ANDROID_SDK_ROOT environment variable set from a
previous installation of the Android SDK. See this thread for more troubleshooting tips.
You may have to wait a while as the emulator loads, and you may even need to try it more than
once for the emulator to get it right, but once its ready, you should see something like this:
Processor speed
Bluetooth capabilities
If you want to make an App that will run on a hundred different devices, how can you tell if its
going to work? Well, there are six main strategies I can recommend:
1. Nail down your target
2. Filter your manifest
3. Check Androids best practices
4. Emulate
5. Pick at least one representative device
6. Fake other screens on your device
Lets go through them one by one.
Looking good. Youve just told your Android App what version of Android it is targeting and
what the minimum version a device needs to run it. Or have you? Mouse your cursor over your
newly added XML and you should receive the following message.
As the message politely tells you, your newly input values are being overridden by a Gradle
Build Script. Lets go see where this happens, double-click on the build.gradle (Module: App) file
in the Gradle Scripts folder and you will be presented with something like this.
apply plugin: 'com.android.application'
android {
compileSdkVersion 21
buildToolsVersion "21.1.2"
defaultConfig {
applicationId "com.darrylbayliss.omgandroid"
minSdkVersion 16
targetSdkVersion 21
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'),
'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:21.0.3'
}
There are quite a few things going on here, but for now you will focus on just one part the
defaultConfig element. Inside its curly brackets you will see some familiar looking values that
are affecting your App when it is being compiled. This was what the message in your Manifest
was warning you about. Fortunately, it isnt a concern, but it is something worth understanding.
Whenever you click Run in your main project window, any values within the defaultConfig
element of build.gradle are used to populate some additional information into your
AndroidManifest.xml file. The reason for this is to make use of the flexibility of the Gradle Build
Toolkit.
Before Android Studio, things such as trying to build multiple versions of an App from the same
project was something of a pipe dream. An App project only has one manifest file to declare its
features and rules so if you wanted to test different configurations of your App, it would be a
long and labored process because you would have to make your changes, compile the App and
then repeat this process until you were happy with the results.
The arrival of Gradle changes all that, allowing for multiple bespoke versions of your App to be
created using specific rules you can set yourself. Project dependencies can be linked to from
remote code repositories, ensuring every time you build your App you are using the latest version
of 3rd party libraries. You can even set rules up to test your APK using a variety of source files
during the build process.
Gradle makes use of Groovy syntax, a Java like language, so if you are intent on learning Java
then you will easily pick Groovy up as well.
You can learn more about Gradle and its treasure chest of features right here on the Android
Developers website.
When putting together layouts, use relative widths and heights instead of absolute values.
Putting in several versions of the same image file, tailored to different screen densities,
can also be a good idea.
The tutorial will discuss these in more detail in Part Two, when you lay out views in XML.
4. Emulate
When you set up your Emulator earlier, you saw how many options you have to work with
different screen sizes. If you used the stock emulator then you also saw how slow it can be. As
mentioned earlier, there are ways to improve it and alternative emulators all together you can
make use of to try your App on a huge variety of configurations.
If the stock emulator isnt for you, try them out and see what works best.
You can use the following command at the Terminal prompt to see if adb is available in your
system PATH:
adb version
Note: If the adb version command results in an error message, your Android SDK folder is
probably not in your PATH. Then youd need to find the exact location of the Android SDK and
change to the correct folder (as in the first steps in the screenshot above) and prefix any adb
commands with ./. Alternatively, you can add the adb path to your PATH.
The adb version command is just an example to show you how to call adb from the command
line. It can run all sorts of commands and you can get a list of available commands by typing adb
help.
If not already set up, you can optionally add the Android SDK platform tools folder to your
$PATH so that you can run adb from anywhere on your system. You should only do this if you
are familiar with UNIX and feel comfortable doing so.
With your device plugged in and with its screen turned off, type the following:
adb shell wm size 640x480
This represents a screen size of 640480 pixels with 160 pixels per inch pixel density.
Note: In Android versions earlier than 4.3 Jelly Bean, these commands are slightly different, as
documented here.
When you turn the device screen back on, you should see that the resolution has changed to
match the new values you entered!
Feel free to turn the screen off, try another set of dimensions and turn it back on again.
To return your device to its normal settings, type:
adb shell wm size reset
So you could get a full-size tablet (like a Nexus 10, perhaps) and then easily simulate all sorts of
smaller devices, without having to use the emulator! If you are trying this with a device that has
a relatively small screen then its not really worth going beyond the dimensions of your screen as
it will begin to display elements offscreen.
I hope that information helps you navigate the Gingerbreads, KitKats, Jelly Beans, and all the
other varieties of Android candy. Now, back to the app at hand
Browse around for a few minutes without any explicit instructions, expanding and collapsing
folders and double-clicking on files to see their contents in the main window. If you notice any
trends, great. If it all still looks cryptic, not to worry!
this tutorial, but if you want to learn or refresh your Java knowledge, here is a nice online
interactive tutorial.
It will be worth your time to learn more and more Java as you explore Android development.
Your team is relying on the professional.
Drawable folders that hold images just the default launch icon for now.
The layout folder with XML that represents the screen designs.
The menu folder with XML of the items that will appear on the Action Bar. More on that
later.
The values folder with XML containing dimensions, strings, and styles.
several Apps to perform the job (implicit), or very specifically to follow a certain path (explicit).
Youll see an example of each type if Intent later in this tutorial.
For an immediate example, your App already has an Activity called MainActivity. Your
manifest has it labeled with an intent filter that causes the MainActivity to launch when the user
selects the App icon from their home screen. You could potentially move that filter to another
Activity and then that activity would launch instead of MainActivity. Basically, the App does
whatever the boss says.
If you dont fully grasp everything about Intents right away, dont worry. Just keep the concept
in mind as you see Intents throughout the code, and eventually you will start to get an idea of
their potential.
So remember: When you launch the App, youre essentially doing the same thing as sending a
launch Intent to the manifest. As the boss, the manifest takes a look at the Intent and decides
it has the perfect fit for the job: MyActivity. The Java does the heavy lifting of opening the
screen, but for what to display it goes and asks the artist, eventually leading to strings.xml.
Click Run. When the App launches again, youll see your personalized message!
Congratulations! If you have a device, you can go around showing off your new app to your
friends or take a screenshot from the emulator and send it to them.
Youve entered the world of Android. Youve set up your development environment (no easy
task!), created your first app, run it on an Emulator or device, and changed the App so that it
specifically addresses you. Great job!
Update note: This tutorial was updated for the latest version of Android Studio by Darryl
Bayliss. Original tutorial by Matt Luedke.
This tutorial is the second of three parts. If youre looking to start from scratch, Part One is the
tutorial for you!
The first part of this series covered a lot of zoomed-out Android concepts, as well as the general
structure of Android projects. In this part of the tutorial, youll learn more on the ground
Android: how a layout file works and the specifics of Android layouts and views.
By the time youre done with this section, youll have an app with:
An option to share your message through Facebook, Twitter, SMS or email; and
A greeting that saves and retrieves your name each time you open the app.
You should already be at the point where you have a Hello World app running on your
emulator or device. At the end of the last section, you changed the text so that it greets you
personally, like mine:
Its great that youve come this far but now its time to take it to the next level! Theres a lot
to do, so lets get to it!
Getting Started
Looking ahead, the first thing you should do is check that youre making your app as simple as
possible. You dont want to introduce additional complexity unless its needed since extra
complexity in how something is implemented means that it takes more time and requires more
work if you were to later modify the bits with the extra complexity.
First, open app/res/layout/activity_main.xml. After opening the file, you may have to switch the
editor to Text mode if you cant see the raw XML. Click the appropriate tab at the bottom of the
editor pane as shown below.
Note: From now on, this tutorial will always interact with XML files in text mode.
The only thing you need to remove here are the padding attributes that Android Studio
automatically generates in your .xml layout. They look something like this (your values may
vary slightly):
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
Delete these lines from your layout, your activity_main.xml file should now look like this:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:text="@string/hello_world"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
Now, double-click on MainActivity.java on the left pane of Android Studio to take a look at your
first piece of Android code.
Pro Tip: A quick way to navigate to any file in your project using OSX is to type Shift +
Command + O. This is the same shortcut used in Xcode!
The only lines you need to remove here are the following:
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
Be careful not to remove the extra curly brace at the bottom that closes the end of your class.
Now that youve finished that bit of cleaning up, youre all set to begin. Its time to bring your
Activity to life!
wrap_content: This
match_parent: This
Explicit values: You could set the dimension to a specific number of pixels (Ex: 5px), but
it is usually wiser to use density independent pixels (Ex: 5dp). A dp is a pixel on a
medium-density (mdpi) device, and the number of actual pixels automatically scales for
devices designated as low-density (ldpi), high-density (hdpi), extra-high-density
(xhdpi), etc.
constant value specifies that the view will be just large enough to fit
whatever is inside it, whether thats an image, text or child view.
constant sets the view to be as big as its parent.
In other words, using straight-up pixels would result in your views being all sorts of crazy sizes,
depending on whether a device has 160 pixels per inch or 300 pixels per inch, or what have you.
Who knows! Let the Android system take care of the scaling and just use dp.
Note: Designations like mdpi and hdpi are only general categories. Actual pixel densities are
even more variable, but they are all given the same scaling factor regardless. So dp scaling, while
convenient, is not an exact science.
iOS Developers should be familiar with a similar practice of density independence, using
points instead of pixels in their layouts to account for early iPhone screens not having Retina
displays.
The final attribute of the TextView is simply text, in which you specify the text to be displayed.
This attribute is a good example of how different views respond to different attributes. Adding a
text attribute to a RelativeLayout or a Space wouldnt accomplish anything because, unlike
the TextView, they wouldnt know what to do with it.
But the value of the attribute, @string/hello_world, isnt whats displaying, is it? What you
specify in your layout file is not the actual string to be displayed but rather a string resource ID
identifying the actual text. That way, all your apps copy can be in one place
res/values/strings.xml.
You can command click on the resource ID to be brought directly to the definition in your
resource file.
Now lets look at the parent node in the XML: RelativeLayout. Whats going on there?
Relative Layouts
The layouts in iOS apps used to be in purely absolute terms, like: Place View X at pixels (x,y),
but now iOS developers have AutoLayout. Android developers have always needed to keep
device screen sizes in mind. Layout files are very well-suited for this consideration.
The default project Studio created for you sets you up with a useful layout: a RelativeLayout.
It is currently the parent layout and the TextView element is its child.
A RelativeLayout is an intuitive and powerful thing. It holds a bunch of child views and
positions them in relation to each other. Here are three examples of what you can easily do with a
RelativeLayout:
Example 1: Use layout_alignParentBottom and the similar attributes for top, left and right to
line up a views edge with the corresponding edge of the RelativeLayout, which may or may
not also be the edge of the screen.
Example 2: You can use layout_toRightOf and the analogous attributes for left, above and
below to position one View relative to another.
Example 3: You can use layout_alignRight and the analogous attributes to align a side of one
View with another.
You can see how that could be useful! For now, though, lets move on to the layout type youll be
using for this tutorial.
Linear Layouts
A LinearLayout needs to have an orientation specified, either horizontal or vertical. Then it
lines up its children in that orientation, in the order in which they are specified in your XML.
The children of LinearLayouts dont respond to attributes like layout_toRightOf, but they do
respond to two other attributes: layout_weight and layout_gravity.
Specifying a layout_weight expands the view to a proportion of its parent so that the parent
weight is the sum of all child view weights.
The layout_weight of View X
The sum of all weights of View X and its siblings
Confused? Perhaps the following image might help explain it better.
Notice how the full height of the parent view is split up between the child views based on the
layout weight assigned to each child view.
Assigning a layout_gravity to a view sets its horizontal and vertical positions within its parent
LinearLayout. For example, a view might have a layout_gravity attribute with a value like
left, right, and center_vertical. The previous values can also be combined, like this: top|
center_horizontal.
Then, theres gravity, not to be confused with layout_gravity. While layout_gravity is
about where you place the view itself, the gravity attribute defines how you place the content of
a view within itself. If you want your text to be left or center justified, use gravity.
The following example shows how layout_gravity and gravity work in a vertical
LinearLayout. Note that the top three have a layout_width of wrap_content while for the
bottom three its set to match_parent:
One handy trick, which youll see in just a bit, is that you can nest layouts inside each other. Cue
the theme music from Inception!
You dont want your layouts to be as multi-layered as a Christopher Nolan movie, though. So, if
you start to see your nested LinearLayouts scheme getting out of hand, consider switching to a
RelativeLayout.
Note: There are also performance reasons for considering a simple layout design. Layout weights
require the layout inflater the class which turns the XML into real Views to do a tiny
calculation to put the view together.
Now, modern devices wont have any trouble rendering your basic layouts. But if you were to
make an indefinitely long ListView for instance, one full of cells making copious use of
nested LinearLayouts and layout_weights all those extra tiny calculations could add up.
Before you move on to the next section, open res/layout/activity_main.xml and change the root
node from a RelativeLayout what Android Studio gave you as a default to a
LinearLayout.
To do that, you should replace these lines:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
With This:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
And this:
</LinearLayout>
Notice the addition of the id attribute. Using this tag (or attribute, if you prefer) allows you to
access that specific View from within your code, so you can thereafter manipulate the View via
code.
Theres also a change to make in the text tag. The name of the string resource hello_world is a
bit outdated now, dont you think? Right-click on the @string/hello_world part of the line and
then choose Refactor > Rename.
This not only changes the name of the resource ID in your layout file, it also changes the original
resource ID in your strings.xml file. It also renames the resource ID wherever else it might be
used in the project. This is a useful trick to remember when renaming something that appears all
over your project!
Now open MainActivity.java and add the following line above the onCreate method but below
the MainActivity class declaration:
TextView mainTextView;
Android Studio will throw an error at you when you leave your cursor on this line that will look
like this:
The TextView class hasnt been imported into MainActivity.java yet so it doesnt know what a
TextView is. Android Studio can quickly fix that for you. Just tap Alt-Enter on your keyboard
while this error popup is present to automatically import TextView.
Note: It can get tiresome very quickly having to manually import every single component of an
Android App you want to use in a class. Fortunately you can automate this in the IDE settings
window, which you can access by going to Android Studio > Preferences (for Mac) or File >
Settings (for Windows & Linux) and then clicking Editor > Auto Import and ticking Optimize
imports on the fly and Add unambiguous imports on the fly.
Next, add the following code to onCreate after the two existing lines of code:
// 1. Access the TextView defined in layout XML
// and then set its text
mainTextView = (TextView) findViewById(R.id.main_textview);
mainTextView.setText("Set in Java!");
TextView mainTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is
present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
The text set via Java code now appears on screen. What are the steps you just took to make that
happen?
1. You added an id attribute to the View in XML.
2. You used the id to access the View via your code.
3. You called a method on the View to change its text value.
Note: You added the code to access and set the text of your TextView in the onCreate method of
your Activity, meaning that the app runs all the code in that block right away when it first
creates the Activity. Activities have strict lifecycles they must follow and its thanks to this that
you can write code that runs at specific points of an Activities life. You can read more about the
Activity lifecycle here.
Notice theres an XML comment above the Button, a reminder of how to trigger results.
The layout_margin attributes simply add 20 density-independent pixels of space above and to
the left of the Button to keep your layout from looking cramped. Remember that the value of 20
will be scaled by the screen density of the device to get an actual pixel value.
Youve probably noticed @string/button under the button text property appears in red. If you
hover over it, youll see that the symbol cannot be resolved and thats because you havent yet
defined it.
Open strings.xml and add the following line to the bottom to resolve this:
<string name="button">Update The TextView</string>
Next, open MainActivity.java and add the following right below the previous line you added
to include a TextView variable:
Button mainButton;
Now add the following code to the end of onCreate, after the code you added earlier:
// 2. Access the Button defined in layout XML
// and listen for it here
mainButton = (Button) findViewById(R.id.main_button);
mainButton.setOnClickListener(this);
Again, you see the same three steps as when you added code to access the TextView:
1. You add an id to the View in XML. Or, in this case, you add a view with an id attribute.
2. You access that View in code by using the id.
Simply click OK on the next dialog, which lets you know which method(s) Studio will
automatically create for you.
Studio then generates the code necessary to make your MainActivity qualify as a unioncertified OnClickListener.
First, it added a bit to the class declaration indicating that the Activity implements a specific
interface:
public class MainActivity extends ActionBarActivity implements
View.OnClickListener
Second, Studio added a stub for a method you need to implement in order to get your
OnClickListener license (other interfaces may require more than one method to be
implemented): onClick. This method fires when your Button gets pressed.
@Override
public void onClick(View v) {
}
The method currently does nothing. So add the following code to onClick to make it do
something:
// Test the Button
mainTextView.setText("Button pressed!");
Can you tell from the code what should happen? Run your app and see if youre right
The app now changes the text in the TextView when you press the Button. Cool! Youll be
putting this Button to even better use later to submit input.
You can see a folder with multiple copies of the same image within res. Notice that the file
names have brackets at the end that look like they contain screen density abbreviations.
Indeed, those abbreviations correspond to the pixel density buckets used to classify Android
devices in dots per inch (dpi):
mdpi:
medium
hdpi:
high
xhdpi:
xxhdpi:
extra high
extra extra high
You can even create your own xxxhdpi folder where you can place images for devices with even
higher pixel densities. Personally, I think they should use Roman numerals for the next level up
and call it xlhdpi, but on second thought that would probably be a terribly confusing way to
go
Look inside the drawable directories. Youll see a file named ic_launcher.png. This is simply the
default launch image youre given, at several different sizes for different screens. The Android
system will pick the right one for the device.
Now head back to activity_main.xml and replace the following section:
<!-- Set OnClickListener to trigger results when pressed -->
<Button
android:id="@+id/main_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_marginLeft="20dp"
android:text="@string/button" />
With this:
<!-- This nested layout contains views of its own -->
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<!-- Set OnClickListener to trigger results when pressed -->
<Button
android:id="@+id/main_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_marginLeft="20dp"
android:text="@string/button" />
<!-- Shows an image from your drawable resources -->
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_marginLeft="20dp"
android:src="@drawable/ic_launcher" />
<!-- Closing tag for the horizontal nested layout -->
</LinearLayout>
You just added a new LinearLayout inside the existing root LinearLayout layout, directly
underneath the TextView as its new sibling. You also moved the existing Button into the nested
layout and added a new ImageView, as well.
By wrapping your Button in a second, horizontal LinearLayout, you are able to place a Button
and an ImageView side-by-side horizontally, even as the root layout has a vertical orientation.
As for the ImageView itself, the important attribute is src, to which you give your drawable
image resource. Note the format you use to reference the drawable image. You need to prefix the
file name of your image (minus the file type) with @drawable/.
Run the app, and youll see the new image right beside the button!
android:layout_marginLeft="20dp"
android:hint="@string/hint" />
Notice the special attribute, hint. Youre using this text as a placeholder in the input field. The
app will overwrite it once the user starts typing.
As usual, you need to define the string resource for your hint in res/values/strings.xml:
<string name="hint">A Name</string>
Now open MainActivity.java and add a new variable for the EditText (below the other two
existing variables):
EditText mainEditText;
The above code, similar to the previous code, simply gets a reference to the EditText control
and saves it in the assigned variable.
Now that you have a reference to the EditText control, you need to do something with user
input. Replace the current contents of onClick with the following:
// Take what was typed into the EditText
// and use in TextView
mainTextView.setText(mainEditText.getText().toString()
+ " is learning Android development!");
When mainButton is clicked, the mainTextView will now be set to display a string including the
contents of mainEditText concatenated with is learning Android Development!.
Run your app, and test this out!
Now you receive user input with an EditText, submit it with a Button, and display it in a
TextView. Very nice! But how about visualizing more than one piece of data at a time?
The ListView
The ListView is a useful control that visualizes a list of items. Its analogous to a UITableView
in iOS.
You define a ListView just as you would any other view in your XML. Add one to
activity_main.xml as a sibling to the TextView, the horizontal LinearLayout, and the EditText
by adding the following lines after the lines for the EditText control:
<!-- List whose dataset is defined in code with an adapter -->
<ListView
android:id="@+id/main_listview"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:layout_marginTop="20dp"/>
Wait what? How in the world could setting layout_height to 0dp be a good idea? After all,
no matter what screen youre on, 0 is always going to scale to 0.
Well, take a look at what directly follows: a layout_weight. Since you havent given anything
else in your layout a weight yet, the ListView is going to expand to fill as much space as
possible, no matter what value you give the layout_height.
The general practice, then, is to use a value of 0 so the layout inflater has one fewer dimension to
think about and can get the job done a bit quicker.
Now open MainActivity.java and, add the following variables below the ones youve already
added above the onCreate method:
ListView mainListView;
ArrayAdapter mArrayAdapter;
ArrayList mNameList = new ArrayList();
The one for the ListView makes sense. But what about the others? The others are for supplying
the ListView with data to display. All will be explained in a bit :]
But first, add the following code to the end of onCreate:
// 4. Access the ListView
mainListView = (ListView) findViewById(R.id.main_listview);
// 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);
Some of that looks familiar by now: finding the ListView using its id. But what else is going
on?
is an example of an adapter, which is basically a go-between so your ListView
can get the data it needs.
mArrayAdapter
Note: I think of a ListView as being a picky sort, as far as Objects go. Its great at what it does
but doesnt want to get its hands dirty with any real data. Its all got to be prepared for it or else
itll throw a fit.
The Adapter, then, is the enterprising Object that is able to code-switch between the rough
language of the datasource and the refined dialect of the ListView.
When you create mArrayAdapter, you have to specify the Context, the target XML view for the
data (simple_list_item_1), and the datasource (mNameList).
But hang on, you didnt write anything with an id of simple_list_item_1! So where is that
coming from? Also what exactly is a Context?
Notice the android.R.layout part before simple_list_item_1. There are several important
concepts here, but lets look at the R bit first. R (or, R.java, if you prefer) is a dynamically
created class which gives you access to the resources in your project. If interested, you can read
more about accessing resources via the R class, here.
As the linked article above explains, you can use the R class to get a resource ID by specifying a
resource type and a resource name. The resource type would be something like string,
drawable, or layout matching the various resource types you see in your project. And thus,
the layout part in android.R.layout.simple_list_item_1 simply specifies that you are
referring to a layout resource.
But what about the android prefix? Why is it there? It is an indicator that you didnt create the
view; its already part of the Android platform. It represents a simple TextView that a default list
cell can use.
The Context is an object that represents the current state of your App. Do you need to access a
specific service for your App to use? Context is your guy. Do you need your App to show a
specific View or Activity? Context is the mastermind behind it.
In this instance, Context is used to create the Views used within your ListView. Remember that
layout resource you are referring to? This is the layout the context takes and converts into a view,
the adapter then populates each view with a value from its datasource.
The datasource in this case is mNameList, which is simply a list of Strings. Its initialized, but
empty. So the next step is to add some data that the ListView can display.
Add the following code to the end of onClick:
// Also add that value to the list shown in the ListView
mNameList.add(mainEditText.getText().toString());
mArrayAdapter.notifyDataSetChanged();
You simply add whatever the user typed into the EditText to the list of names and then shoot a
signal to the adapter to update whats shown in the ListView.
Now run your app.
You should be able to type a name into the EditText, then see the name used in the TextView
and added to a new row in the ListView when you press the Button. Cool!
The above code sets MainActivity as the listener for any item clicks on mainListView.
Now replace the onItemClick Method that was automatically generated for you with the
following:
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long
id) {
// Log the item's position and contents
// to the console in Debug
Log.d("omg android", position + ": " + mNameList.get(position));
}
You may need to use your old friend Alt-Enter to import and use Log.
Well done! Your MainActivity Class has implemented onItemClick and can live up to the title
of being a card-carrying OnItemClickListener.
But whats happening inside onItemClick? Theres a weird Log.d in there, and then something
with a get(position)
Take a look at what youre passing along to onItemClick. In particular, look at int position,
which is an integer equal to the index of the item the user pressed on the list (counting up from
0).
You take that position, as well as the item at that index in your list of names, and log them.
Logging is a very basic, but very useful debugging technique.
Run your app, enter a few values and add them to the list, just as before. Then select an item.
Theres no visible effect for the moment.
With the app still running, look at the bottom section of Android Studio:
The bottom left section of the window contains information about your Device or Emulator, what
processes are running and logs of what is currently happening within those processes. These logs
appear in a console called logcat. It will read off tons of stuff from your emulator or device, the
majority of which is not of much interest to you at this point. The log statements you generated
with your selections are here, but theres too much noise to see them.
Here are some useful ways to filter out the noise and see only what you want:
Notice the option for Log level in a dropdown at the top of the console. When you put your Log
command into code, it specifically was the Log.d command. The d is for debug level. The
levels are:
v: Verbose
d:
Debug
i:
Info
w: Warning
e:
Error
When you select a log level for logcat, it will only show messages at that level or higher. And
the levels start at verbose and go up to error, in the same order as listed above. So, if you select
the log level as Warning, then youll see all warnings and errors but nothing else.
Meanwhile, you can use the text box to the right of the log level drop-down to apply a filter and
show only those messages that contain the text you typed in.
Now that you know this, set the log level to Debug and type omg android into the filter text box.
Great! You now have a clean feed of log statements and you can detect when a certain item gets
selected in your list. The ability to log will come in handy as you create more complicated apps
and want to stay informed of the inner workings of your code.
The Action Bar provides a familiar base for your users. Since its present across apps, making
good use of the Action Bar means a significant part of your apps functionality will be
immediately intuitive to an Android user. Conversely, neglecting the Action Bar would confuse
all your users who expect it to work and that would be weird!
The Action Bar is already in your app it just has no options attached to it yet. That will be
your first order of business next!
Sharing
Soon youll have a chance to show off the fact that youre learning Android, from within your
own app! Youll do this using an intersection of the Android concept of the Intent and the
Action Bar, known as a ShareActionProvider.
One of the advantages of an Intent is that you can construct it in either a specific (explicit) or
generic (implicit) manner. You used an example of the explicit type when you specifically
defined the Intent that launches your app, which the manifest then identifies as MainActivity.
Now youll see an example of the implicit type.
A generic Intention really helps in this case. After all, some of us prefer to share things with
the entire world, and others with just a few friends. Rather than wondering what a potential
users favorite social network might be and integrating them one by one, you can politely tell the
Android device that youd very much like to share a bit of content (thus expressing an Intent),
and Android will graciously take it from there!
Navigate to res/menu/menu_main.xml and open it.
Youll note that theres some auto-generated XML in there, but you dont need it. Replace the
whole thing with this:
<!-- Defines the menu item that will appear on the Action Bar in MainActivity
-->
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:omgandroid="http://schemas.android.com/apk/res-auto">
<!-- Share item -->
<item
android:id="@+id/menu_item_share"
android:title="Share"
omgandroid:showAsAction="ifRoom"
omgandroid:actionProviderClass=
"android.support.v7.widget.ShareActionProvider" />
</menu>
Because your App is running on versions of Android lower than Lollipop, its often the case that
you need to use features that dont exist on previous versions of Android. This means you ether
build your own functionality to ensure users have a seamless experience across multiple versions
or you provide it using 3rd party libraries.
Google provide various App Compatibility libraries to try and reduce this fragmentation issue. In
the XML above, you can see you are making use of the android.support.v7 libraries in your
XML. Making use of the support library now means the code you are about to implement will
work all the way down to Android v7. You can read more about the support libraries here.
Now head over to MainActivity.java and add the following variable underneath the rest of your
variables:
ShareActionProvider mShareActionProvider;
Note: Studio may be confused about which ShareActionProvider you mean, so if it asks, use
android.support.v7.widget.ShareActionProvider. If you have enabled auto imports then
return true;
From there, you can access the menu item you defined in XML by its id, menu_item_share, and
then you can access its action provider. Previously, you specified that this items action provider
was a ShareActionProvider. So, you can safely cast to that type in your code and hang onto a
reference to it via the mShareActionProvider variable.
Then, you call setShareIntent. This method creates an Intent, but not just any Intent. It
creates an Intent whose action youve set to ACTION_SEND. Its truly as generic as it looks:
youre going to tell Android you want to take the action of sending something.
From there, you set the Intents content type, subject used by email programs and the like,
as the subject header of the message , and text. The text matches whatever is currently in your
TextView. After youve packed up everything the Intent needs to know, you pair it with
mShareActionProvider.
This code will work, but only kind of. As-is, you only call setShareIntent once, at the
creation of the menu. It would be much better to update the Intent whenever the TextView
changes otherwise youre stuck with the initial message forever!
Add the following code to the end of onClick:
// 6. The text you'd like to share has changed,
// and you need to update
setShareIntent();
Here, you simply make sure that the share intent is always up-to-date.
Run the app, and try out the new sharing feature tapping the share icon on the Action Bar
should reveal a number of choices, depending on what is installed on your emulator or device.
The ShareActionProvider automatically puts together an array of possible avenues for sharing
content based on the apps you have installed on a given device. This array of options will differ
from device to device. The emulator will most likely have far fewer options for sharing, whereas
you may have apps like Twitter and Facebook on an actual device and could share through those
networks, too.
The above sets PREF and PREF_NAME at the top of the class. Youll use PREF as a filename to keep
your SharedPreferences in a single location. Youll use PREF_NAME as the key for storing your
name in shared preferences.
Note: Its a good practice to use variables for Strings that youll need/refer to multiple times.
That way, if you need to change the string value later, you can do it in one place. Youll also
never have to worry about some weird spelling error creating bugs in your code.
The final line adds a variable named mSharedPreferences for storing a reference to the shared
preferences class. You only need to access it in a few places, but it will be useful to hang onto it.
Add the import into your Class if you havent already.
Next, add the following lines to the end of onCreate:
// 7. Greet the user, or ask for their name if new
displayWelcome();
The new code calls a new method, displayWelcome. So implement that by adding the following
method at the end of the class:
public void displayWelcome() {
// Access the device's key-value storage
mSharedPreferences = getSharedPreferences(PREFS, MODE_PRIVATE);
// Read the user's name,
// or an empty string if nothing found
String name = mSharedPreferences.getString(PREF_NAME, "");
if (name.length() > 0) {
// If the name is valid, display a Toast welcoming them
Toast.makeText(this, "Welcome back, " + name + "!",
Toast.LENGTH_LONG).show();
}
}
Note: While you could conceivably place all the code from displayWelcome directly into
onCreate and it would still work, many people, your humble author included, prefer to keep
Finally, you check to see if the retrieved String actually has any content, and display a message
if so. Your message takes the form of a Toast, which is a short-lived pop-up message that
appears for a bit and then fades away. Give the Toast a message it should display, specify one of
its built-in lengths to remain on the screen and then simply tell it to show. Easy!
alert.show();
The app will reach this else condition when there is no valid name saved using the PREF_NAME
key. You use an AlertDialog.Builder to give your AlertDialog a title, a message, and an
EditText in the center for the user to type in their name.
Then, you add two buttons to the AlertDialog: a positive and a negative button. The first thing
you define for each is the text displayed on the button OK and Cancel are pretty standard
choices. The second thing you define for each button is an OnClickListener.
This time your OnClickListeners are specifically DialogInterface.OnClickListeners, and
you are defining them right away. Notice how the parameters for onClick are slightly different.
For the positive buttons listener, onClick does quite a bit. First, it reads the name that the user
typed into the dialogs EditText.
It then saves that name into SharedPreferences using a helper called a
SharedPreferences.Editor. You simply tell the editor what to save and where, tell it to
commit the changes, and thats it!
Finally, it displays a Toast identical to the other welcoming one.
The negative buttons listener is far simpler: it does nothing! Nothing!
Run your app and check out your Dialog.
Type in your name, press OK and see the Toast greeting. From now on, your app will remember
your name and greet you each time you launch it!
Setting up the EditText to expect names as its input so that it capitalizes first letters.
Making the Done button on the keyboard do the same thing as the Update TextView
button.
Hopefully you have found this helpful! If you have any comments or questions, feel free to leave
them below. And of course, dont miss Part Three, in which you set up your app to interact with
data online!
The Android robot is reproduced or modified from work created and shared by Google and used
according to terms described in the Creative Commons 3.0 Attribution License.
Update note: This tutorial was updated for the latest version of Android Studio by Darryl
Bayliss. Original tutorial by Matt Luedke.
This tutorial is the third and final part of the series devoted to helping you make your first
Android app! Check out Part One to get started with Android and Part Two to learn more about
Android UI and project structure.
In this final part of the tutorial, youll learn how to leverage the powerful capabilities of the web
to search and display data and images. More specifically, youll make an app that searches the
Open Library API a database of over 20 million books , displays information and cover
images of the books it finds, and allows you to recommend books to friends!
When youre done, youll know how to:
These are very useful and transferable skills for all sorts of Android apps youll want to make in
the future.
To begin this part of the tutorial, you should be at the point where you have an app that takes user
input, lists names, and shares messages through social networks. Your app should also ask for
your name when you first open the app and greet you by name thereafter. The final version of the
source from Part 2 is also available on GitHub or as a .zip.
Getting Started
Its time to do a bit of rebranding. No longer is this just a little demo app it is a book search
and recommendation engine!
The default Android icon can only take you so far. Download the following files and drag them
onto the src/main/res/drawable-hdpi directory to add them to your project:
ic_books.png
img_books_large.png
img_books_loading.png
Note: When importing your images you may receive an error saying Refactoring cannot be
performed when importing images. If that is the case try to drag your images into the project
again whilst holding the Alt key to resolve it.
Only the hdpi assets are provided here. For future projects, it is a good idea to add assets for the
other dpi values as well.
If you have trouble finding the hdpi folder in the drawables folder. Studio may be defaulting to
its Android project view structure. Change this to Project by clicking on the large button to
the left just above the project hierarchy on the left. Once you have imported the images you can
change back if you prefer
Open AndroidManifest.xml. As you recall, this is the boss of your app. If you want to change
the apps icon, you need to talk to the boss.
Find the opening application tag and change the icon attribute line from:
android:icon="@drawable/ic_launcher"
To:
android:icon="@drawable/ic_books"
From now on, the app icon will be a stack of books instead of the default Android icon.
Depending on your Studio version (and/or SDK version) you may also see that the application
has an attribute for setting the application name. Youre going to update the application name as
well, but not in the manifest.
First, while youre still looking at the manifest, you need to let the manifest know that you plan
to start accessing the Internet. Between the uses-sdk and application tags, add the following:
<!-- NEED TO ADD TO BE ABLE TO GO ONLINE AND GET DATA -->
<uses-permission android:name="android.permission.INTERNET"/>
If you dont let the boss know your plans, it wont file the appropriate paperwork with Android
to make the web call happen. But now youre good to go.
Now its time to change your apps name. Open res/values/strings.xml and replace the strings for
everything except action_settings with the following new values:
<string
<string
<string
<string
name="app_name">Bookmaster General</string>
name="textview">Search For Books!</string>
name="button">Search</string>
name="hint">Title and/or Author</string>
Run your app, your device or emulators home screen should reflect the name and icon updates.
For example:
Now that youve rebranded your app, its time to start adding the web interactions!
Networking Considerations
There are a lot of things to keep in mind when adding networking capabilities to your app.
For example, you need to consider how to keep all the network interactions off the UI thread, so
that the user can continue to use your app without everything locking up until a download
completes. If you were using an app and it became completely unresponsive for lengths of time,
youd get pretty frustrated!
You will also find Android will ask your user if they would like to quit an unresponsive App,
which isnt great for your App if people are scrambling to leave it as quick as they can.
Thankfully, you can solve issues such as this by simply using third-party libraries. These have
been specially designed and supported by Android experts to facilitate networking. Some of the
most well-regarded include Retrofit, Volley, and Android Async Http. For the purposes of this
tutorial, youll use Android Async Http.
Image downloads are also a consideration since each image takes time to download. Also, in the
case of a list of books, if you have your list set to download images as needed, you might find
that you end up downloading the same image over and over as your user scrolls through the list
of items. You really dont want that type of behavior. Youll use another third-party library called
Picasso to manage image downloads for you.
Note: The old-fashioned way to incorporate third-party libraries into your code was to download
a zipped-up .jar file, copy it into your code and then include it in your projects build path. Its
not too difficult, but the larger inconvenience is what to do when the library updates, or if you
need to share your project with teammates.
A way to easily manage your projects dependencies would sure be great! That leads us to
Gradle.
A Glance at Gradle
When you created your project in Android Studio, you may remember mentions of Gradle and
Maven. Refer to Part One if you need a reintroduction. Now youll see them in action.
Open build.gradle. Note that there are two build.gradle files in your project. You want the one
thats within the app folder not the one at the project root level:
Some of the stuff happening in here is beyond the scope of this tutorial, but if you become
serious about Android development, I recommend looking into Gradle a little more, starting of
course, with the Gradle website. For now, just notice that the Android plugin is being applied to
the project (apply plugin: 'com.android.application').
Scroll down to the area labeled dependencies. There may already be a support library listed
there, or it may be empty. Youre going to add two new libraries.
Add the libraries like this:
dependencies {
...
// there may or may not be a support library above these
compile 'com.loopj.android:android-async-http:1.4.4'
compile 'com.squareup.picasso:picasso:2.1.1'
}
Then find the Sync Project with Gradle Files button on the Studio toolbar and press it. It looks
like this:
Note: If you get an error that says requires compiling with JDK 7, you should upgrade your
SDK to JDK 7. You can download it here, then follow the prompts to set your SDK folder to
/Library/Java/JavaVirtualMachines/jdk1.7.0_71.jdk/Contents/Home.
Believe it or not, thats it! Just like that, youve included the Android Async Http and Picasso
libraries in your project and you can start using them whenever you like.
Its so easy because both of these libraries are available via the Maven Central Repository, to
which your project already contains a reference. You can see this in your root folders
build.gradle file, it looks like this.
repositories {
jcenter()
}
So, when you tell Gradle which libraries youd like to use, it simply grabs them from the source
and youre good to go.
Note: jcenter() is a Gradle method that connects to a repository which is a superset of the
Maven Central Repository. In early versions of Android Studio the default repository set by
Gradle was Maven Central, you still change it back by changing jcenter() to mavenCentral().
jcenter however is thought of as being faster and more responsive whilst still providing all the
libraries available on the Maven Central Repository. So lets leave this alone.
If you need to include any libraries which are not available on the Maven Central Repository,
then youd still have to go through the old school method of copying the source (or the library)
into your project. But most of the time, you wont have to go through all that pain since the
Maven Repository contains a lot of third-party libraries for you to use. So youll probably be
able to find an alternative to the library youre interested in. If interested, you can even browse
all the libraries available on the Maven Repository.
JSON Basics
Great! Now its time to meet your datasource: the Open Library API. Its 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 youre 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"]
are a collection of key-value pairs. For example, a very simple object describing a
book might be:
JSONObjects
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, its time to set up the code to go
make a sample query!
Add the following variable to your growing list 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 youll be calling. Its much better to hold this URL
as a static String, so you dont have to go searching through your code for it. Remember that
youre going to append the search string after the ?q=.
Next, add this new method anywhere in your MainActivity.java Class:
private void queryBooks(String searchString) {
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.
queryBooks
Calling new AsyncHttpClient() simply creates an instance of the HTTP client. Its 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
JsonHttpResponseHandler,
is simply the URL from which youd like to fetch data. This parameter will
be made up of base URL defined at the top of your class, plus the search string you will
add later.
which you define now, even though you dont 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());
}
In both cases, you simply present a Toast and log the results. Soon, though, the success case will
get a lot more exciting.
Fortunately, thats 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 users 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 havent 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.
This is already exciting and clearly has the potential to be something cool very soon! But its still
just a jumble of data. Your next challenge, then, is to display this data on the screen in a more
organized manner.
Right-click on the res/layout folder in the Studio left pane, and select New > Layout resource
file.
Name your file row_book.xml with a root element of RelativeLayout, then click OK.
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, youre going to add three views inside the RelativeLayout, and then youll 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 theres a little margin to the left of the picture.
This is stuff youve 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 youd like the image to display within the amount
of space its given. Especially given the unpredictable list of screen sizes youd have to support,
its often important to set this beforehand.
Using centerInside means that youll 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 its too short, or space on the sides if its too thin. If youre interested,
you can read up on the various ScaleType options.
Next, add a TextView for the books 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 whats 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 thats 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. Theres some space in between the two controls, as well.
Finally, add the TextView for the authors 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 authors 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 dont get
circular (two attributes that depend on each other), or else your XML wont inflate!
Then type in JSONAdapter as the new class name, make sure the Kind is set to Class and then hit
OK.
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 youll 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 thats 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.
Youll 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 objectoriented 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, youre 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 isnt
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.
When asked to select methods to implement, make sure all four methods are highlighted and
click OK.
Magically, Android Studio creates four methods for you and the red underlining disappears. This
means that youve satisfactorily extended BaseAdapter.
But all the methods are empty. Its 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();
}
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.
getCount
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.
This can be a very helpful method in some situations, but in this case, you dont 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 items ID number, this method would be helpful
for you.
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 theyre 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 books 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(h
older.thumbnailImageView);
} else {
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 items position in the list.
Next, you check to see if theres a cover ID for that book. Unfortunately, many books dont 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.
Note: An example URL from this operation: http://covers.openlibrary.org/b/id/6845816-S.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 doesnt 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!
You dont need any of that simple stuff now that youve 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;
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! Thats not happening yet, so thats next on your agenda.
This method accepts a JSONArray input, sets it as the adapters datasource, and calls
notifyDataSetChanged to refresh the list. The adapter is already set up to know what to do with
the data, so thats all you need!
Now go back to MainActivity.java. Youre going to use your new method to update the list when
the network call comes back. Find the following code in onSuccess, which is embedded within
queryBooks:
This is simply a call to updateData with the newly-returned query response. As soon as the data
comes back, you dont waste any time you send it straight to the adapter, which whips it into
shape for the ListView!
Its finally time run your app, and search away!
Now you can type a search string into your EditText, tap the Search button and let the
ListView (somewhat) magically populate from your web search. Not only that you can scroll
through all the results and look at book titles, author names, and thumbnails of the cover images.
This is already a pretty cool app!
Showing Progress
One nice feature you may notice missing is some kind of progress bar or spinner to let the user
know your app is thinking. Lets solve that now.
Add the following line to the list of variables at the top of MainActivity.java:
ProgressDialog mDialog;
is an Android class that provides a convienent solution for times when you
want to provide feedback that your App is performing some sort of intensive task. That could be
fetching large files and reading their contents, setting up basic values for the first time the App
run or fetching online content like your current App does.
ProgressDialog
Lets set some initial values, go to your onCreate Method and add these lines at the end:
mDialog = new ProgressDialog(this);
mDialog.setMessage("Searching for Book");
mDialog.setCancelable(false);
Good. Youve told your Activity you want a ProgressDialog to be created with a particular
message and that you dont want the user to have the ability to cancel it. Lets put this to work,
add this line to queryBooks, immediately after creating your AsyncHttpClient:
// Show ProgressDialog to inform user that a task in the background is
occurring
mDialog.show();
You want the Dialog to disappear when the request is over, which could actually be in one of two
spots onSuccess or onFailure. Add the following line at the very beginning of onSuccess:
// 11. Dismiss the ProgressDialog
mDialog.dismiss();
This is simply to provide a title for the activity. As mentioned before, its good to keep all the
strings in one file!
Next is the layout XML. It wont be complicated. Right-click on res/layout and select New >
Layout Resource File.
Thats almost it right there. All you need to do now is give the ImageView an id, a default image,
and a bit of margin space for good measure. Edit activity_detail.xml in Text mode to look like
this:
<?xml version="1.0" encoding="utf-8"?>
<ImageView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/img_cover"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="25dp"
android:src="@drawable/img_books_large"/>
Thats simple enough. This screen will now show a single ImageView with a 25dp margin all
around, and the default image is img_books_large.
Next, you need to make a new Activity. Right-click on the com.example.omgandroid package
(or the package name you set originally) and select New > Java Class, as before.
Name the class DetailActivity.
This creates a simple, empty class for you. Next, modify the class definition so that your new
class extends Activity, like this:
public class DetailActivity extends ActionBarActivity {
You already know you need to access the ImageView from your layout, so add the following
method to your activity:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Tell the activity which XML layout is right
setContentView(R.layout.activity_detail);
// Enable the "Up" button for more navigation options
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
// Access the imageview from XML
ImageView imageView = (ImageView) findViewById(R.id.img_cover);
}
The above code simply tells the Activity to use the simple XML layout you made earlier, and
then grabs the ImageView you need from it. But wait: what is that getSupportActionBar stuff
doing in there?
The other steps for enabling the Up button take place in your manifest. Open
AndroidManifest.xml and add the launchMode attribute to MainActivity so that it looks
something like this:
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:launchMode="singleTop">
Note: Depending on your Android Studio version, the package name you selected when you
created the project, and a few other factors, the above might not match what you see in your own
AndroidManifest.xml file exactly. The only thing you need to really worry about is adding the
new launchMode attribute as shown above. You can leave the rest as is.
So, do you recall from earlier in this tutorial about how the manifest is the boss who takes in
jobs in the form of Intents and checks if there is a team member right for the task? Normally,
the manifest would arrange for the system to start a brand-new instance of that Activity every
time.
But, by setting the launchMode attribute to singleTop, youre telling the manifest to use an
already-existing instance of that Activity, if possible. That way, when you use either the Back
or Up button to return to the main screen, your most recent search results will still be there and
you wont have to start from scratch!
Next, add a definition for the DetailActivity to the manifest, immediately after the one for
MainActivity:
<activity
android:name=".DetailActivity"
android:label="@string/activity_details"
android:parentActivityName=".MainActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".MainActivity"/>
</activity>
This looks pretty similar to the definition for MainActivity, except for the stuff about parent
activity. Setting the parent activity tells the manifest which activity should be displayed when
theres a request to go up from DetailActivity.
If you recall, the method that executes when a cell is selected from the ListView is
onItemClick in MainActivity.java. It used to log information originally but now is empty. Add
the following code to it:
// 12. Now that the user's chosen a book, grab the cover data
JSONObject jsonObject = (JSONObject) mJSONAdapter.getItem(position);
String coverID = jsonObject.optString("cover_i","");
// create an Intent to take you over to a new DetailActivity
Intent detailIntent = new Intent(this, DetailActivity.class);
// pack away the data about the cover
// into your Intent before you head out
detailIntent.putExtra("coverID", coverID);
// TODO: add any other data you'd like as Extras
// start the next Activity using your prepared Intent
startActivity(detailIntent);
Here, you create an Intent to take you from where you are now (this) to an instance of
DetailActivity. But before you fire off the command using startActivity, theres one more
thing to remember to pack away.
As youve previously seen when you created setShareIntent, you can pack extras into an
Intent in the form of key-value pairs. Since your DetailActivity needs to know the cover ID
to display, you extract that ID from the books JSON data and send it along.
Note: I put in a TODO reminder for an optional challenge to you. If you want your
DetailActivity to do anything more than show an image, you should send along additional
data here.
Build and run your app, and you will be able to click on a list item from your search results to
see your new DetailActivity! You can also navigate back to the main screen using either the
Up or Back button.
Right now, you only see the placeholder image, but you know where this is headed :]
Add the following variables at the beginning of DetailActivity.java (right after the class
definition line):
private static final String IMAGE_URL_BASE =
"http://covers.openlibrary.org/b/id/"; // 13
String mImageURL; // 13
This sets the base URL for cover images on the Open Library API. You also create mImageURL to
hang onto any specific URL so that different methods within your Activity can use it without
needing to create the image URL all over again each time.
Next, add the following code to the end of onCreate:
// 13. unpack the coverID from its trip inside your Intent
String coverID = this.getIntent().getExtras().getString("coverID");
// See if there is a valid coverID
if (coverID.length() > 0) {
// Use the ID to construct an image URL
mImageURL = IMAGE_URL_BASE + coverID + "-L.jpg";
// Use Picasso to load the image
Picasso.with(this).load(mImageURL).placeholder(R.drawable.img_books_loading).
into(imageView);
}
The above code digs into the Intent that brought you to this Activity and sees if it contains a
String with the name coverID. If so, the Picasso Library will download the image, just as you
did in all the row cells. You display a loading image until the desired image is ready.
Build and run, and youll see the actual cover for the book you chose from the list!
This is just another variable to hold a reference to the ShareActionProvider for your
Activity. Make sure you import the right ShareActionProvider. It should match the import in
your MainActivity.java class.
Next, add this new method to the class:
private void setShareIntent() {
// create an Intent with the contents of the TextView
Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.setType("text/plain");
shareIntent.putExtra(Intent.EXTRA_SUBJECT,
"Book Recommendation!");
shareIntent.putExtra(Intent.EXTRA_TEXT, mImageURL);
This should look very familiar, as it is nearly the same as the method added to MainActivity
earlier. The only difference is the use of mImageURL as the text to be shared.
Note: Some share service providers, like Facebook, will intelligently interpret the URL as an
image and display it to the users friends. Others, like e-mail, will simply include the link. Either
way, youre enabling your users to share dynamic content about a book they think others might
enjoy!
One final thing left to do adding the share button to the Action Bar. You can again reuse code
from MainActivity. Add this method to DetailActivity.java:
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu
// this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
// Access the Share Item defined in menu XML
MenuItem shareItem = menu.findItem(R.id.menu_item_share);
// Access the object responsible for
// putting together the sharing submenu
if (shareItem != null) {
mShareActionProvider
= (ShareActionProvider)
MenuItemCompat.getActionProvider(shareItem);
}
setShareIntent();
}
return true;
Here, you could potentially use different menu XML files to populate the Action Bar on different
screens/activities. But for your purposes, the same menu/menu_main.xml file will do.
Build and run your app, and youll have a pretty powerful app! Your app now takes in your
search query, returns a list of books,allows you to take a closer look at a book cover, and share
that cover image with friends!
Display more details about the book in the Detail View, like more information about the
book or even the first few lines of the book.
Investigate android:background and add background colors to your layouts and views.
Thank you for following this tutorial series. Please leave any comments or questions below!
The Android robot is reproduced or modified from work created and shared by Google and used
according to terms described in the Creative Commons 3.0 Attribution License.