31 Days of Android
31 Days of Android
Kicking things off in our 31 Days of Android series, we’ll talk about getting things set up for
development. When you want to start developing for Android, you have a decision to make. You can
use the Eclipse development environment which has integration with Android Development Tools or
you can use the command line to create, compile, and package your Android apps. If you’re set on
the IDE you want to use, and it isn’t Eclipse, you can do it, it’s just not as easy. For today we’ll just
assume you’re going to use Eclipse.
When you go to download Eclipse, you’ll want to choose Eclipse IDE for Java Developers. The
current version of Eclipse is 3.7.1 and is code named Indigo. If you already have an older version of
Eclipse installed and it’s relatively recent, you can probably get away with using it. However, I have
run into issues with one of the more advanced features not really working in older versions of Eclipse,
so unless you’re set on staying with an older version, grab Indigo. Once you’ve pulled down Indigo
(or opened your older version of Eclipse), it’s time to install the Android plugin for Eclipse.
The Eclipse plugin created by Google is called the Android Development Tools (ADT). The ADT
takes all of the tools and capabilities that the Android SDK gives you in the command line and makes
them easy to use from Eclipse’s GUI. Go ahead and fire up Eclipse. If this is the first time you
launch, you’re going to be asked for a workspace to store projects in. You can use the default or
enter a folder of your own choosing. Once you’re into Eclipse, go to the Help menu and then Install
New Software. Click Add in the top right, then enter whatever you want for the name and this for the
location: http://dl-ssl.google.com/android/eclipse. Note that the “Official” Google instructions say to
use a HTTPS URL. I’ve never actually had this work and have always had to switch to HTTP. After
clicking OK, you’ll go back to the previous window and you should see this:
Go ahead and check all of those boxes and choose Next. You’ll need to click next and accept some
terms and conditions and then ADT will be installed and you’ll be asked to restart Eclipse. When
Eclipse launches, you’ll be presented with a fantastic window. This window will ask you to either
install the Android Software Development Kit (SDK) or choose an existing SDKs location. Prior to
version 15 of ADT, you had to manually download the SDK and go into options in Eclipse to connect
ADT to your SDK installation. Unless you’re just reinstalling Eclipse and you already have an up-to-
date copy of the SDK ,I’d choose to install it:
If you only want to develop for the latest version of Android (4.0 / Ice Cream Sandwich at the time I
wrote this), you don’t need to check the second box. However, if you want to support the majority of
devices, make sure you check the 2.1 box as well. After proceeding, you’ll need to Accept some
terms and install the SDK.
Provided that went well, you should still be in Eclipse and you should be nearly ready to create an
emulator. From Eclipse choose the Window menu and then Android SDK Manager. This tool
enables you to easily update your SDK and install different versions of the SDK as well as support for
specific device types. While you don’t need to do this as you’ve already installed the latest SDK and
2.1 (if you followed the above), I would go ahead and check all the available boxes and install all
available packages:
If nothing else, make sure you have installed the Android 2.2 (API 8) folder. There is some
functionality we’ll play with later in the series that requires at least version 8 of the Google APIs. If
you choose to install everything, it’s going to take a little while.
That’s it for today. You’re just about ready to get started with developing for Android. Next, we’ll look
at creating Android Virtual Devices. You can read more about the install process here (just a
warning, as of when this was written, some steps weren’t updated for v15 of the ADT).
Continuing in our 31 Days of Android series, we’ll talk about Android Virtual Devices (AVDs). Strictly
speaking, you don’t need an Android phone or tablet to develop for Android. Instead, you can create
AVDs and run them in an emulator. Unfortunately, the emulator is well known for being slow,
displaying things differently than devices, and just being the bane of many developers. Prior to the
release of Ice Cream Sandwich and the latest version ofADT, running the emulator for a tablet was
nearly impossible. For most people out there (including myself) there just isn’t a way to get access to
all of the different device types. This mostly means different screen sizes and densities, however,
you also may want to test against hardware keyboards. I will say that if you can get access to
enough devices, avoid the emulator. Provided you have installed all of the different Google APIs and
SDK versions from yesterday’s entry in 31 Days of Android, you should be good to go with today.
If you’re not already running Eclipse, go ahead and launch it. After doing so, go to the Window menu
and chooseAVD Manager. The manager won’t list any AVDs yet (unless you created some in the
past) so now we’ll create some. Clicking New takes us to this window:
The name can be whatever you want. Since the platform and API level are visible in the AVD
manager, you may want to name your AVD for the resolution you select or any other special setup
you apply to the AVD. The vast majority of Android devices out there are still pre 3.0. You can view
the current distribution of device versions on the Android developer site. Knowing this, if you want to
put out an application that gets the most people possible, you’ll want to make it for and test it against
at least 2.2 and up. This currently covers 87% of devices on the market. So if we want to create a
2.2 device, we can choose either “Android 2.2 – API Level 8” or “Google APIs (Google Inc.) – API
Level 8”. The difference between the two is that the Google APIs version comes with a few
extras. The most common thing the APIs version has that people use is access to Google Maps. If
your app will use Google Maps, make sure you use an API version. I don’t know of a bad reason for
using an API version so you may just want to use that either way. So once you’ve selected “Google
APIs (Google Inc.) – API Level 8” you have the option of selecting size of the SD card. If you’re doing
something where size is a factor you may want to set this. In my general usage and testing, I never
have.
Snapshot is a relatively new feature that should allow you to boot your AVDs faster. Essentially what
Snapshot does is save the state of your AVD whenever you shut it down. Then when you reboot it, it
will start up much faster than the normal boot process. It does introduce some issues with restarting
the AVD that requires you to uncheck a “Launch from Snapshot” checkbox in the AVD’s launch
options though.
Next we have Skin. Skin allows us to quickly select a predefined screen size, such as the default
WVGA800, or pick out a specific resolution. Most of the time I’ve gotten by with the Built-In Skins but
if you need to test a specific device, you may need to branch out. In addition, you can install
additional built in Skins for use. Here is one walkthrough on installing skins for the Nexus S and
Nexus One (not the upcoming Galaxy Nexus). For my general testing I’ll make an AVD for
WVGA800 and HVGA. This covers a high definition device and a medium definition device.
Lastly, you’ll see the Hardware options. When you pick a Target SDK and a Skin, some options will
already be filled in (typically “Abstracted LCD density”, “Max VM application heap size”, and “Device
ram size”). This area gives you the ability to specify whether GPS should work, whether you can use
a camera, whether or not the device has a hardware QWERTY keyboard and more. Most of these
settings are defaulted to options that make it easy to test a device with the functionality. So if you
want to test how your app would run on a device that doesn’t have a hardware keyboard, camera, or
GPS, then that is when you’d want to go in and add these settings to the Hardware area.
Once you’ve filled that info out, you’re ready to hit Create AVD.
Upon doing so, you’ll be taken back to the Android Virtual Device
Manager. When you’re here, you can select your new AVD and
click details to get more information. When you’re ready,
click Start. You’ll next get the Launch Options screen. From
here you can control scaling options to decrease (or increase)
the size of the emulator. This doesn’t affect the DPI of the
emulator but how much space the emulator takes up on your
screen. If you’re developing on a smaller screen monitor or
laptop, you may need to scale down any HDPI emulators
because they won’t actually fit on the screen. Lastly, there are
options to Wipe User Data to set the emulator back to a “new”
state. There are two options for Snapshot here that have to do
with the Snapshot option we discussed when creating the
AVD. You should finally be ready so go ahead and
hit Launch. And wait. As I’ve said before, the emulator is pretty slow. Using the snapshot mode
should boost the speed of starting your emulators. Give it some time though, and you’ll have access
to the emulator.
One thing to note is that the emulator won’t have any accounts set up by default and doesn’t have the
Android Marketplace installed. This means that if you’re writing an application that needs to interact
with other Marketplace installed apps, you won’t really be able to test on the emulator, at least not in
an easy manner. It is possible to get the Android Marketplace onto an emulator image but it requires
a bit of work and isn’t perfect. Just to reiterate, if you have access to devices, you’ll probably have an
easier time testing on them.
If you haven’t already done so on your own, go ahead and create an AVD targeting Google APIs
(Google Inc.) – API Level 8 with a Built-in Skin of HVGA and Snapshot enabled. If you want to
have an easier time of following along later, name your AVD “hvga”.
31 Days of Android: Day 3 - A Java Refresher
Posted on: 11/23/2011 6:29:24 AM by Chris
<< Day 2: Creating AVDs
Day 4: Creating your First App >>
Before we start getting really in to Android development in our 31 Days of Android series,
we need to go back and take a quick run through of Java. This series is assuming you
have a decent understanding of the Java programming language. For the most part
things will be pretty readable so if you’re familiar with object oriented programming but not
specifically Java, you shouldn’t have a problem following along. If you’re already familiar
with Java, this article can probably be skipped.
A bit about Java
Java was created in the early and mid 90’s. Today it’s “owned” by Oracle (due to their purchase of
Sun). Java was designed as an object-oriented language that would be portable and usable on all
sorts of hardware. To this end, Java is not compiled down to native executable (like C or C++). Java,
instead, is compiled to an intermediate bytecode which is then interpreted by a virtual machine that is
built specific to the hardware on the device. This allows a program you create in Java to be run on
x86, ARM, PowerPC, and more. Furthermore, Java is a very structured and ceremonial language. If
you’ve stuck to primarily functional languages in the past, Java may be a bit to swallow. if you’ve
worked with C#, Java will feel like an old friend (though you’ll probably hate Eclipse).
Data Types
Java is a strongly statically typed language. This means that there are some restrictions in place, but
they are good, helpful restrictions. The STRONG aspect means that once you create a variable as
an integer, it’s an integer for the life of that variable. You CANNOT set it to a string or a
boolean. The STATIC part means that when your Java program is compiled, it’s going to type check
your code to make sure you’re not attempting to break the STRONG typing. Java has the same sort
of variables that you’re used to if you’ve developed in other languages. String, int, double, boolean,
arrays, etc. Going into every variable type is beyond the scope of this article, so we won’t do it. If
you’re unfamiliar with the basic data types I mentioned before, I would look into going through a full
programming tutorial. You can find all sorts of resources online to help learn Java programming from
the ground up. One note I will make is regarding the String class. When comparing strings, doing
stringA == stringB compares the reference (do they point to the same spot in memory). If you want to
compare two string values (i.e. “Chris” == “Chris”) then you want to do stringA.equals(stringB).
Operators
I don’t have a lot to say about Java operators. Things here function very similarly to the default
behavior in most other languages. One important note to remember is that operator overloading is
not possible in Java.
Classes
If you’re not familiar with classes and-object oriented design, this may be one of the biggest concepts
to make sure you understand. Classes are used very heavily in Java. In general, all of the code you
do will be encapsulated in some sort of class.
1: public class MyClass {
2: }
If the class is public then it must be contained in a file with the same name as the class (appended
with .java). Non-public classes can be stored in any .java file, however, for the sake of keeping an
understandable project structure, it’s best to create separate files for each class. Java offers class
inheritance but not multiple inheritance (similar to C#). In addition, it also allows implementation of
multiple interfaces. In Android programming, you’re going to end up using inheritance quite often so
being familiar with how it works would be a good idea. In general, understand that a class (A) that
inherits from a base class (B), has access to any of B’s public or protected member variables and
methods, but not anything private. Classes can be public, meaning they can be instantiated from
anywhere, final, meaning they can’t be extended or altered, or abstract, meaning they can’t be
instantiated but MUST be extended. Classes can also be protected and private but only if they are
defined as being part of another class.
Methods
Methods, also known as functions, are blocks of code that can be called from other sources. Let’s
say I’m going to add two numbers together in many different places in my application. Instead of
doing this everywhere I want two numbers added, I might create a method that can do that for me,
like so:
1: public int AddNumbers(int numberOne, int numberTwo) {
2: return numberOne + numberTwo;
3: }
Now, instead of doing the math, I can call AddNumbers(…) wherever I need the numbers
added. This is a very simplified explanation but should serve to help understand. All of your logic
and functionality will be done inside of methods. Using my A & B class examples from above, class A
can override methods that are a part of class B. When A does this it can call the same method in B by
calling super.methodName(). This is useful when you want to create something with similar
functionality as something else but alter it slightly. An example of this is theActivity class. You’ll
use Activity a lot in Android development, initially just for showing data. For this you’ll inherit from
the Android Activity base class. Within your class, you’ll override the onCreate method that is found
in Activity. Within that method you’ll call super.onCreate since the base Activity class needs to setup
some plumbing for you. Then after you call that, you’ll continue setting up your UI, initializing values,
etc.
Loops and Conditionals
Java features the standard loops and conditionals you’ll know from any other language. Here we
have a typical If Then, Else If, Else block.
1: if (this is true) {
2: //Do this
3: } else if (actually this is true) {
4: //Actually do this
5: } else {
6: //None of those things were true so do this
7: }
Furthermore, we have the standard switch statement:
1: switch (my variable) {
2: case "Test":
3: //Do some stuff
4: break;
5: case "For Realz":
6: // Do some real stuff
7: break;
8: default:
9: //This isn't a test or for realz
10: }
One important thing to know about the Java switch statement is that the case statements MUST use
final staticvariables. That’s the only way you can use variables as cases. We also have
our While loops:
1: do {
2: //Do some stuff
3: } while (true);
4:
5: while (true) {
6: //Do some stuff
7: }
Lastly we have the foreach loop:
1: String[] names = { "Chris", "Risner" };
2: for (String name : names) {
3: //Do something with name
4: }
Two last, but very important, concepts for loops are break and continue. These keywords can be
used within your loops to either break out of your loop or to stop the current iteration and go to the
next instance in the loop, respectively.
Memory Management
In Java, memory management is not as much of a concern as it is in a language like C++ where you
need to explicitly handle memory. Java has a garbage collector which handles repossessing blocks
of memory that are no longer in use by your program. Now, just because there is a garbage collector
does NOT mean that you can’t still run into memory issues. You are limited to how much memory
you can use and if your application just consumes and consumes memory, your app isn’t likely to
perform very well.
Do I have to develop in Java if I want to do Android?
Lastly, I’d like to touch on programming languages for Android. Java is by far the most well known
and popular approach to developing for Android. The Android SDK is Java based and is the primary
entry point for Android. However, Android also offers the Native Development Kit (NDK). The NDK
allows you to develop in C and C++ and doesn’t offer the same high level access that the SDK
does. You can write your application in Java as well as using the NDK and use the Java Native
Interface to connect the two. The NDK is good for CPU intensive processes and things you want to
have compiled to native code instead of bytecode. One important thing to consider is that when using
the NDK, you no longer have the guarantee that your app will run on any Android device with a Java
Virtual Machine. Your NDK code has to be compiled for the right processor architecture as well.
Hopefully this refresher has given you enough to go off of to start doing some real Android
development. Tomorrow we’ll take a look at our first application.
31 Days of Android: Day 4–Our First App
Posted on: 11/24/2011 11:07:00 PM by Chris
<< Day 3: A Java Refresher
Day 5: Multiple Activities and Intents >>
Now that you’ve installed our IDE and the Android SDK as well as
created an AVD, you’re ready to continue our 31 Days of Android by
creating our first Android application! In this application we’ll go through
the boiler plate project template in Eclipse and enhance it’s its
functionality a little bit.
If you haven’t done so already, start Eclipse. Once it has started, go
to File –> New –> Other (or type Ctrl + N / Command + N). From there
you can either find Android or type it in the filter text box at the top. You
should see a slew of different Android options. Choose Android
Project and click Next. Next, you’ll come to a Create Android
Project window. The only thing you need to specify here is the Project Name. For this, name
it DayFour.
You will then be asked to select a Build Target. If you installed all of the Android SDK versions as
detailed in our first post, you should see quite a few. If not, you should at least see the most recent,
4.0. Go ahead and chooseAPI Level 14 for Platform 4.0 with target name Google APIs. We’re not
going to do anything 4.0 specific; however, it’s a good idea to make your applications able to run on
the latest version of Android (we’ll get into this a bit more in a moment). Once selected, click Next.
Our last window for now is the Application Info wizard.Here you specify the Application
Name again, thePackage Name, if we want an initial Activity, and a Minimum SDK. Your package
name has to be unique from all other applications available on the Android Marketplace as well as
unique on the device the app is installed to. For now, name it com.dayfour. The Activity name
should already be prefilled with DayFourActivity. Lastly you have the Minimum SDK. This value is
going to be different from your Build Target in that it specifies the oldest version of the operating
system your app can be installed on. You don’t want to limit yourself to only devices running 4.0 so
instead your select the Google APIs version of 2.2 which means your app will be available to the
majority of devices. Setting your Minimum SDK low and your Build Target high means that you can
make use of features specific to higher versions of the OS but should make your app work with all the
versions in between. While we won’t do so now, if you want to make an application that is capable of
using advanced features but handling lack of access to those features, I would recommend reading
up on it. When all is done your window should look just like this:
The last part deals with creating a Test Project. Down the road you should (maybe we will) look at
adding tests to your Android project with a Test Project. For now we’ll skip it. Go ahead and
click Finish. Your application should now be created. Before we run the project, let’s start the
emulator. Go to Window –> AVD Manager. Pick the hvga emulator you created earlier and
click Start. From there you should just be able to click Launch. If this is the first time you’ve started
the emulator, it’s going to take a little while. Once the emulator is running, go back into
Eclipse. Right click on your project DayFour in the Package Explorer (it should be on the left side in
the default Eclipse orientation) and go to Run As –> Android Application. You should see some
messages coming up in the Console about uploading and installing DayFour and then starting an
activity. If you go back to your emulator you should now see the boiler plate app in action:
Let’s go back to Eclipse and take a look at some of the files that made this happen. First open up
theAndroidManifest.xml file in the root directory. The manifest file contains app specific settings
such as app version and minimum SDK version, the different permissions required, activities that can
be run, services, broadcast receivers and more. Upon opening you’ll be taken to a graphic editor for
the manifest. While it’s possible to do everything in the graphical view, click
the AndroidManifest.xml tab on the bottom right of our view of the manifest. This will bring up an
XML view of the file:
1: <?xml version="1.0" encoding="utf-8"?>
2: <manifest xmlns:android="http://schemas.android.com/apk/res/android"
3: package="com.dayfour"
4: android:versionCode="1"
5: android:versionName="1.0" >
6: <uses-sdk android:minSdkVersion="8" />
7: <application
8: android:icon="@drawable/ic_launcher"
9: android:label="@string/app_name" >
10: <activity
11: android:label="@string/app_name"
12: android:name=".DayFourActivity" >
13: <intent-filter >
14: <action android:name="android.intent.action.MAIN" />
15: <category android:name="android.intent.category.LAUNCHER"
/>
16: </intent-filter>
17: </activity>
18: </application>
19: </manifest>
At the top of our manifest you’re specifying your unique package name (com.dayfour), your app
version code and version name, and the minimum SDK Version. After that, you have your application
settings. At the top level you’re specifying our icon and label (don’t worry we’ll look at drawables and
strings soon). Then, you have your activity. As you may recall from above, when you created the
project, you told it to create an Activity as well and that is our DayFourActivity. The intent-filter stuff
essentially specifies that this activity is what should launch when the app runs.
Now let’s look at those @string things. Back in Package Explorer, expand the res folder and then
the valuesfolder. You should see a strings.xml file. Go ahead and open that. Again you’ll get a
graphical interface for editing the file. Go ahead and click the stings.xml tab in the bottom of the
editor.
1: <?xml version="1.0" encoding="utf-8"?>
2: <resources>
3: <string name="hello">Hello World, DayFourActivity!</string>
4: <string name="app_name">DayFour</string>
5: </resources>
You should see two strings that were already created. If you look up above at the manifest where it
shows@string/app_name, it’s referring to the app_name string seen here. This strings.xml file is
basically a resource file. These are heavily used in Android to make it easier to localize your
applications (and for other reasons you’ll see later). As an example, if you wanted to release your
app in the US and in France. The default strings.xml file would contain English text. Inside of
a res/values-fr/ folder we would have another strings.xml that would contain the French equivalent
text. So going forward, if you want to add text to your UI or use it in your code behind, make sure
you’re making use of these files.
Next expand the res/layout folder and open main.xml. Once again, you get a graphical view. I
usually flip between the graphical interface and the xml one, but for now, click on the main.xml tab in
the bottom of the editor.
1: <?xml version="1.0" encoding="utf-8"?>
2: <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
3: android:layout_width="fill_parent"
4: android:layout_height="fill_parent"
5: android:orientation="vertical" >
6: <TextView
7: android:layout_width="fill_parent"
8: android:layout_height="wrap_content"
9: android:text="@string/hello" />
10: </LinearLayout>
We won’t really dig into this very much today. Suffice to say, the LinearLayout is a way of containing
and organizing elements inside of it. The TextView is used to display a block of text. You’ll see
again that we’re using a string resource to set our TextView’s text property.
Return to the package explorer and expand src –> com.dayfour and open
the DayFourActivity.java file. Once there you should see this Java code:
1: public class DayFourActivity extends Activity {
2: /** Called when the activity is first created. */
3: @Override
4: public void onCreate(Bundle savedInstanceState) {
5: super.onCreate(savedInstanceState);
6: setContentView(R.layout.main);
7: }
8: }
You can see here that the DayFourActivity class is extending the Activity class (which is part of the
Android SDK). We’re only overriding one method from the Activity subclass,
the onCreate method. This method is called when the activity is created and also when the screen is
rotated. We call the super.onCreate method so theActivity subclass can handle it’s plumbing and
then we call setContentView. This method is passedR.layout.main. R is a way to refer to
the res folder and is how we programmatically access the items in that folder (and it’s sub
folders). Here we’re pulling the id for the main layout we looked at earlier.
The first change you’re going to make is to add a new string to our strings file and then set
your TextView to use that string in the onCreate method. Let’s first add a string
to strings.xml named test_one:
<string name="test_one">This was our DayFour test.</string>
Now let’s go back to our main.xml layout file. The problem with the layout as it is, is that there are no
ids in our views. Add an id to the TextView like so:
android:id="@+id/lblTextViewOne"
Now that your TextView has an id, we can now access it from the back end. Make sure you’ve
saved the file (Ctrl + S or Command + S). Go back to the DayFourActivity file. The first thing you
need to do is get a reference to the TextView. This is done by calling the
base Activity’s findViewById method. This method takes in an int which is the id of your layout
element, R.id.lblTextViewOne. This method returns a generic view so you need to cast it to
a TextView. When you’re done you should have something like this:
TextView lblTextViewOne = (TextView) findViewById(R.id.lblTextViewOne);
You’ll likely see a red squiggly line under the TextView lines. If you hover over any of them, you’ll
see a popup that says “TextView cannot be resolved to a type”. The reason for this is that you need
to import the library that contains TextView. While hovering over with the popup, you should see a
link that says Import ‗TextView‘ (android.widget). You can click that or conversely you can
hit Ctrl+Shift+O which will attempt to resolve all missing imports. Next we need to set
our TextView’s text property to be your new string.
lblTextViewOne.setText(R.string.test_one);
You should be ready to run the app again. Right click on DayFour and choose Run As –> Android
Applicationor click Ctrl + F11. We should now see our updated text:
Next you’ll add an EditText control to our view. You might also think of EditText as a textbox. Go
back to themain.xml and, if you’re still in XML view, click the Graphical Layout tab. On the left side
of the view is a folder structure for all the different View controls. You want to expand the Text
Fields folder. This contains an assortment of differently configured EditText controls. You’ll use the
top one with “abc” in it. Click on that and drag it over to the view and drop it underneath the text that
says “Hello World. DayFourActivity”:
Once you’ve done that, it should look like this:
If you wanted to, you could go in and change properties on the field but we’ll leave the defaults for
now. Let’s go back to your DayFourActivity.java. Below where you set the text
for lblTextViewOne let’s get the EditText and set it to the same string value:
1: EditText editText1 = (EditText) findViewById(R.id.editText1);
2: editText1.setText(R.string.test_one);
Here you’re getting and casting your view and then setting it’s its text property. Let’s go ahead and
run the application again (Ctrl + F11) and see what we get:
Now, let’s add another string, add a button, and set the text of that button in the layout. Go to
the strings.xml and add a new string named “click_me”:
<string name="click_me">Click Me</string>
Go back to our main.xml. Expand the Form Widgets folder on the left side so you can see the
buttons. Click theButton with the “Button” text and drag it below our EditText. Once you’ve done
that, the view should look like this:
Now, we want to change the text on the button from “Button” to our new string “click_me”. We can do
that in one of two ways. We could go to the xml view and change the text property from “Button”
to ―@string/click_me" or you can right click on the button in the graphical interface and go to Edit
Text. This will allow you to choose between the available strings and you can select click_me.
The last thing we’re going to do today is add a click handler to our button which will set
the TextView that came with the project to whatever is in the EditText. To do this, go back to
the DayFourActivity.java. Below where you pulled out your EditText and set it’s text, you’ll do the
same thing with the button and then you’ll assign it an anonymous click listener (i.e. one defined on
the fly in our method). When done you should have something like this:
1: Button button1 = (Button) findViewById(R.id.button1);
2: button1.setOnClickListener(new OnClickListener() {
3: public void onClick(View v) {
4: // TODO Auto-generated method stub
5: }
6: });
Now since you’re defining this OnClickListener inside of your DayFourActivity’s onCreate method
you can access any member variables of DayFourActivity. Unfortunately you declared
your TextView and EditTextinside the onCreate method. In order to fix this, you need to move the
declaration of editText1 andlblTextViewOne to up above the method. Your code should now look
like this:
1: public class DayFourActivity extends Activity {
2: private TextView lblTextViewOne;
3: private EditText editText1;
4:
5: /** Called when the activity is first created. */
6: @Override
7: public void onCreate(Bundle savedInstanceState) {
8: super.onCreate(savedInstanceState);
9: setContentView(R.layout.main);
10:
11: lblTextViewOne = (TextView) findViewById(R.id.lblTextViewOne);
12: lblTextViewOne.setText(R.string.test_one);//
13:
14: editText1 = (EditText) findViewById(R.id.editText1);
15: editText1.setText(R.string.test_one);
16:
17: Button button1 = (Button) findViewById(R.id.button1);
18: button1.setOnClickListener(new OnClickListener() {
19: public void onClick(View v) {
20: // TODO Auto-generated method stub
21: }
22: });
23: }
24: }
Now you can access lblTextViewOne and editText1 inside your OnClickListener. Your last bit of
code will be to set your TextView’s text property to the EditText‘s text property. Now
your OnClickListener will look like this:
1: button1.setOnClickListener(new OnClickListener() {
2: public void onClick(View v) {
3: lblTextViewOne.setText(editText1.getText());
4: }
5: });
Now when we run our app and enter text into our EditText and then click our button, it should alter
the TextViewat the top of the view:
Congratulations, you just finished your first event driven application. Plus you have a basic
understanding of what files are present in an Android application and how things are
structured. From here we’ll grow and expand this application when convenient and create new
projects when necessary. If you’d like to download the final code for this example, you can grab it
here.
31 Days of Android: Day 5–Adding Multiple Activities and
using Intents
Posted on: 11/25/2011 2:05:00 PM by Chris
<< Day 4: Your First App
Day 6: Options Menus and Base Activities >>
Picking up where we left off yesterday in our 31 Days of Android, let’s get
back to our application. We left off with an application that was only capable of handling some very
simple user input. Your application is currently comprised of only a single activity which means it’s
not capable of a lot. To be fair, there are certainly some applications that only use one activity and
accomplish quite a bit. However, most apps will use multiple activities. Today we’re going to talk
about how to accomplish that. For today, we’re going to work off of the same application code as last
time (though the package / activity names have been renamed DayFive instead of DayFour). You
can download our starter app code here.
In Eclipse, expand the src folder in Package Explorer and right click oncom.dayfive –> New –>
Class. You will then be presented with the New Java Class window. Some of the information
(the Source Folder and Package) will already be taken care of leaving
the Name and Superclass for us. The Name here is relatively arbitrary. The Superclass is not
however. If you remember ourDayFourActivity, it extended from Activity. Setting
our Superclass here will ensure that our new Activity also extends from Activity. If you’re not familiar
with Eclipse, you can click the Browse button on the right, it will help lookup classes in case you don’t
know their full path (i.e. if you know you want Activity but don’t know it’sandoird.app.Activity).
After clicking Finish, our new Activity will be added to our source code. Opening it will show us a
very empty new class:
public class ActivityTwo extends Activity {}
Like this, your Activity won’t do you any good. You need to override some base
functionality. Placing your cursor inside of the curly braces and hitting Ctrl+Space will bring up
different methods we can either override or implement for a class. The method you want to override
first is going to be onCreate so go ahead and select that method. Again you have some base code
that doesn’t do everything you need it to do:
1: @Override
2: protected void onCreate(Bundle savedInstanceState) {
3: // TODO Auto-generated method stub
4: super.onCreate(savedInstanceState);
5: }
The only thing you’re going to do with your second activity today is connect it to a Layout, however,
you need to create that Layout first. To do so, expand the res folder and right click on
the layout folder. From there go toNew –> Other and then select Android XML File. The Eclipse
Android tools are pretty smart and will know what type of XML file you want to create based off of the
folder you’ve right clicked on so it should already have Layoutselected in the Resource Type of the
next window. The default Root Element is LinearLayout so leave that as is. Enter a name (like
“second_layout”) in the File textbox and click Finish. If you click Next instead of Finish you can
enter OPTIONAL qualifiers for your layout. So if you wanted to make a layout specific to
the portraitorientation and a different one for landscape, this is where you would do so. We’ll cover
those qualifiers another day in more detail. After finishing, you’ll be taken to the graphical layout
editor. Again, you’re not going to get very fancy here so drag a TextView on to the layout from
the Palette on the left. By default your TextView’s id will be textView1 which is
fine. Your TextView on the other layout has a different id, however, it’s not an issue for different
layouts to have views with the same id. If, however, you have two views on the same layout with the
same id, you will run into issues trying to get references to those views in your code. Now that we’ve
created a second layout, let’s go back to our ActivityTwo class. After the call
to super.onCreate you need to set thecontentView to your new layout. This is done via the
following line of code:
setContentView(R.layout.second_layout);
Great, you’ve created a second activity and a layout and you’ve connected that layout to our Activity,
but how do you actually show that on the screen? To do this, you use Intents. Intents are
essentially messages that you can send between different parts of your application or to the Android
OS itself. You’re going to use it in a very simple way today though Intents are incredibly powerful
and will come up in several posts later in the series. To use our first intent, we must return to
the DayFiveActivity and the main.xml layout. Go to the layout and drag anotherbutton onto the
bottom beneath our ―Click Me‖ button. This should default to be id button2. With this done, you
can go to our code file, beneath where you get a reference for button1 and set
it’s onClickListener you need to do the same for button2:
1: Button button2 = (Button) findViewById(R.id.button2);
2: button2.setOnClickListener(new OnClickListener() {
3: public void onClick(View v) {
4: }
5: });
Now here’s where you get to the Intent. There are assorted ways to setup an Intent and make it
run. You’re going to do the simplest way here:
1: button2.setOnClickListener(new OnClickListener() {
2: public void onClick(View v) {
3: startActivity(new Intent(getApplicationContext(),
ActivityTwo.class));
4: }
5: });
Here you’re instantiating an Intent object on the fly using the Application Context and your
new Activity. That gets passed into startActivity which does exactly as named. You’re nearly
ready to run now. The last thing you need to do is make a small modification to
your AndroidManifest file. The manifest file needs to know about all the activities your application is
going to run so near the bottom, between the end of the first activity and the end of
the application you need to add an element for your new activity:
<activity android:name=".ActivityTwo" android:label="@string/app_name"/>
Now you can run your application. Right Click on the application in Package Explorer and go to Run
As –> Android Application or hit Ctrl+F11:
So this is your DayFive layout with the new button at the bottom. Tapping that button should cause
our newActivity to fire and give you this:
You’ve successfully created a second activity and layout and connected your first activity to it. This
basic functionality is actually pretty critical to many applications. Furthermore, the Intent you used
was very basic but incredibly powerful.
One thing I would like to highlight is what happens when you tap the Back button on the second
activity. Due to the way you’re starting ActivityTwo, if you tap Back, you will return to our
first Activity. You could think of yourActivities as a stack of cards. When your app starts, card 1 is
put down. When you start your second activity, card 2 is put down on top of card 1. When you hit the
back button, card 2 is taken off to reveal card 1. If you wanted to get rid of card 1 when you show
card 2, or prevent the user from backing into the first activity, you could call finish immediately after
starting your second activity like so:
1: public void onClick(View v) {
2: startActivity(new Intent(getApplicationContext(), ActivityTwo.class));
3: finish();
4: }
This will end your first activity and start your second activity. That’s all for today. You can download
the latest source code here.
31 Days of Android: Day 6–Options Menus and Base Activities
Posted on: 11/26/2011 11:27:00 PM by Chris
<< Day 5: Multiple Activities and Intents
Day 7: Sharing Data between Activites >>
Time for more of the 31 Days of Android! Today we’re going to pick up
where we left off with Day 5 and our multiple activity application. Today we’re going to cover a couple
things including one of the features that differentiates Android from it’s biggest competitor, iOS. This
feature is the Menu button. TheMenu button enables you to load an Options Menu which allows
users to access basic activity actions and navigation options. Let’s take a look at how you
accomplish this functionality. Just like last time we’re going to be working off of the code from
yesterday’s example. Everything that was DayFive will now be DaySix and that should be the only
change.
The first thing you have to do to add your Options Menu is create a new XML resource. This
resource will contain the content of our actual menu. To do this, expand the res folder of your project
in Eclipse. Then, right click the resfolder and go to New –> Folder. Name the folder menu and
click Finish. Once that is done, right click on the new menu folder and go to New –>
Other. Choose Android XML File and click Next. Menu should be preselected as the Resource
Type which means you just need to put in a Filename. For today name the file
"options_menu”. You’ll be taken to the graphic menu editor. Click Add and pickItem. This will
create a new menu item. We’re fine with the default id but we need to set the Title. To do so, click
the Browse button to the right of the Title textbox. We’re going to need new String resources so
click New String. In the String textbox, enter ―Activity One‖ and in the New R.string textbox
enter ―menu_one‖. When that is done, click Ok and then Ok again. You need to add one more item
so repeat that procedure and name the second item “Activity Two‖ and ―menu_two‖. Now we’re
ready to put that menu into an Activity. Open up yourDaySixActivity (or DayFiveActivity if you’re
working off yesterday’s code) from the src/com.DaySix folder.
Here, you need to override the onCreateOptoinsMenu method from the Activity class. To do this
easily, place your cursor at the closing curly brace at the end of the onCreate method. Hit Enter to
get to a new line and hitCtrl+Space. This should bring up intellisense and enable us to pick a
method to override. ChooseonCreateOptionsMenu and tap enter. You should now have the
following code in our class:
1: @Override
2: public boolean onCreateOptionsMenu(Menu menu) {
3: // TODO Auto-generated method stub
4: return super.onCreateOptionsMenu(menu);
5: }
Here you’re going to get rid of the call to super.onCreateOptionsMenu and inflate
the options_menu we just created. This is done by getting an instance of MenuInflacter from
our Activity and passing in the id of the menu like so:
1: @Override
2: public boolean onCreateOptionsMenu(Menu menu) {
3: MenuInflater inflater = getMenuInflater();
4: inflater.inflate(R.menu.options_menu, menu);
5: return true;
6: }
Now if you load your application (hit Ctrl+F11) and click the Menu button on the emulator (or a
device) you should see your options menu appear:
This is great but now you need to actually handle clicking these menu buttons. To do so, you need to
override another method in our DaySixActivity. This time you’ll add onOptionsItemSelected:
1: @Override
2: public boolean onOptionsItemSelected(MenuItem item) {
3: // TODO Auto-generated method stub
4: return super.onOptionsItemSelected(item);
5: }
The MenuItem that is sent into this method has an ItemId that will match one of your two menu
options so we’ll check to see if they are equal and do something different depending on which one it
is. In the example below, you’ll send an Intent if the user clicks "Activity Two” and do nothing if they
click “Activity One”:
1: @Override
2: public boolean onOptionsItemSelected(MenuItem item) {
3: switch (item.getItemId()) {
4: case R.id.item1:
5: return true;
6: case R.id.item2:
7: startActivity(new Intent(getApplicationContext(),
8: ActivityTwo.class));
9: return true;
10: default:
11: return super.onContextItemSelected(item);
12: }
13: }
This is a very simple options menu but should give you an example of the sort of things you can
do. Before I move on, I’d like to point out how to add icon’s to your options menu items. If you go
back to the options_menu file and select either item1 or item2, you should see a textbox for Icon 2
rows beneath the title. Oddly, there is noBrowse lookup for icons though it isn’t hard for you to
specify the icon. To do so, you need to know the name of an image file you have in one of
our drawable folders. You know that by default there is an image namedic_launcher.png in
your drawable folders so you’ll use that. In the Icon textbox enter “@drawable/ic_launcher” then
save your changes and rerun the application. Now when you tap Menu you should see (depending
on where you put the icon):
The problem you have here is that when we go from the first activity to Activity_Two and click
the Menu button, nothing appears. The code you added to show the options_menu was only
in DaySixActivity and not inActivityTwo. You could take the same code and put it
into ActivityTwo but what happens when you need to make a change or when you want to add a
third activity? Putting this code into several places isn’t very maintainable and isn’t a good idea in
general. What you can do to solve this is override the android.app.Activityclass, put your menu
code in that, and then extend your current activities from this base class. We’ll do that now. In
Eclipse right click on the com.DaySix package under the src folder and choose class. Just like
when you created ActivityTwo, you’ll enter android.app.Activity as our Superclass and
you’ll Name our new activity “BaseActivity”. Lastly, check the box for abstract and click Finish:
We’re making this class abstract because we’re not going to allow anyone to instantiate it, but will
use it as a base class. Now, you can go back to DaySixActivity and ActivityTwo and change the
type they extend from Activityto BaseActivity:
1: public class DaySixActivity extends BaseActivity {
2: ...
3: public class ActivityTwo extends BaseActivity {
4: ...
Now you can move all of your menu code out of DaySixActivity and into BaseActivity. I’ve added
code to the onClick for men item1 so that it will load DaySixActivity. I’ve also made the current
activity finish after triggering the load of a new activity:
1: public abstract class BaseActivity extends Activity {
2: @Override
3: public boolean onCreateOptionsMenu(Menu menu) {
4: MenuInflater inflater = getMenuInflater();
5: inflater.inflate(R.menu.options_menu, menu);
6: return true;
7: }
8: @Override
9: public boolean onOptionsItemSelected(MenuItem item) {
10: switch (item.getItemId()) {
11: case R.id.item1:
12: startActivity(new Intent(getApplicationContext(),
13: DaySixActivity.class));
14: //End the current activity
15: finish();
16: return true;
17: case R.id.item2:
18: startActivity(new Intent(getApplicationContext(),
19: ActivityTwo.class));
20: //End the current activity
21: finish();
22: return true;
23: default:
24: return super.onContextItemSelected(item);
25: }
26: }
27: }
Now when you run your application (Ctrl + F11) you can see the menu on both of your activities and
you can go back and forth between them. Note though that the back button will not take you
between the activities because you’re calling finish whenever you start a new activity. If you remove
the calls to finish then you could back your way between the activities. Implementing a base activity
of your own can help when you want to set a custom title, implement a shared menu, and many
more things. You can download the sample code from today’s application here.
31 Days of Android: Day 7–Sharing Data Between Activities
Posted on: 11/27/2011 9:08:00 PM by Chris
<< Day 6: Options Menus and Base Activities
Day 8: Project Structure >>
And now I can check if MyStaticString is empty, and if it isn’t, using it to set the text property of
a TextView in our second activity:
1: public class ActivityTwo extends BaseActivity {
2: @Override
3: protected void onCreate(Bundle savedInstanceState) {
4: // TODO Auto-generated method stub
5: super.onCreate(savedInstanceState);
6: setContentView(R.layout.second_layout);
7:
8: TextView textView1 = (TextView) findViewById(R.id.textView1);
9: if (!DaySevenActivity.MyStaticString.equals(""))
10: textView1.setText(DaySevenActivity.MyStaticString);
11: else
12: textView1.setText("MyStaticString is empty");
13: }
14: }
Now this is pretty poor object oriented design. Anything has access to change
the MyStaticString object. We could make a private static variable with a public static accessor
method, but we’re still not doing things the right way. ActivityTwo has no way of knowing that
the MyStaticString has been set or that DaySevenActivity was used before. So while possible, this
isn’t the best way to share data between activities.
Extending the Application Class like a Singleton
The next technique we’ll look at is extending the android.app.Application class for your project and
treating it like a singleton. In order to do this, you expand your src folder, right click on your package
(com.dayseven in this case) and go to New –> Class. Name your new class MyApplication and
select android.app.Application for it’sSuperclass. For today’s purpose, we’ll just add a String to
our class along with getters and setters:
1: public class MyApplication extends Application {
2: private String myApplicationString;
3:
4: public String getMyApplicationString() {
5: return myApplicationString;
6: }
7:
8: public void setMyApplicationString(String myApplicationString) {
9: this.myApplicationString = myApplicationString;
10: }
11: }
Now when the user taps the second button, you can get the application context and cast it back
to MyApplicationand access our new methods:
1: Button button2 = (Button) findViewById(R.id.button2);
2: button2.setOnClickListener(new OnClickListener() {
3: public void onClick(View v) {
4: startActivity(new Intent(getApplicationContext(),
5: ActivityTwo.class));
6: //Sets the Static String on the DaySevenActvity
7: MyStaticString = editText1.getText().toString();
8: //Get our Application Instance
9: MyApplication myApp = (MyApplication) getApplication();
10: //Set the app string on our app instance
11: myApp.setMyApplicationString(editText1.getText().toString());
12: }
13: });
Now in the onCreate method of ActivityTwo you can get access in a similar manner:
1: //Get our Application Instance
2: MyApplication myApp = (MyApplication) getApplication();
3: //Set the textview's text using the app's string
4: textView1.setText(myApp.getMyApplicationString());
Lastly, in order to run this and make it work, you have to tell Android that you’re overriding
the Application class. This is specified in your AndroidManifest.xml file. Open that up and add a
new attribute to the application node specifying the name of your application class:
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:name="MyApplication">
Now when you run your application, it will use your new MyApplication class. It’s important to note
that this isn’t strictly a singleton object as you could always create more instances
of MyApplication instead of casting theApplication Context into your MyApp class. The Android
OS will only create one instance for the lifetime of your project though. In other words Android won’t
screw up your singleton application class, but you could. Also important is that if we were just storing
a single String and passing between two activities in a bigger application, storing the String in an
application class like this might be a little bit overkill. However, if down the road we had aUser object
that stored a lot of information on the current application user and we used that object in all of our
activities, using this singleton application class technique makes sense.
Adding Extras to New Activity Intents
The most standard and “correct” way of sharing data between activities is to pass that data into the
new activity via the intent that is fired to open that activity. Let’s look at how we’re currently starting
our ActivityTwo when the user taps the second button on DaySevenActivity:
Button button2 = (Button) findViewById(R.id.button2);
button2.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
startActivity(new Intent(getApplicationContext(),
ActivityTwo.class));
...
}
});
If we wanted to pass a String into the second activity via the intent we need to change how we’re
doing this a little bit. Since we’re currently instantiating the new Intent in our call to startActivity, we
don’t have the opportunity to add any information to it. To start, you should change the code to the
following:
Button button2 = (Button) findViewById(R.id.button2);
button2.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
Intent myIntent = new Intent(getApplicationContext(), ActivityTwo.class);
startActivity(myIntent);
...
}
});
Now prior to calling startActivity you have access to the myIntent object. If you look at the available
methods on the Intent class, you’ll see one method, putExtra, that has been overridden to accept a
plethora of different second parameter types. This allows you to attach extended data to the
intent. So if you want to add your string data to the intent you could do that like so:
Button button2 = (Button) findViewById(R.id.button2);
button2.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
Intent myIntent = new Intent(getApplicationContext(), ActivityTwo.class);
myIntent.putExtra("MyString", editText1.getText().toString());
startActivity(myIntent);
...
}
});
Now in the onCreate method of ActivityTwo, you can get the intent that started the activity and pull
out the String extra:
Intent myIntent = getIntent();
textView1.setText(myIntent.getStringExtra("MyString"));
If you recall, we had set up our application to fire ActivityTwo from the options menu as well. If you
now go into the options menu to fire the second activity, it will load fine but there won’t be any
text. The reason for that is the intent that starts ActivityTwo from the options menu never adds a
String extra named “MyString”. This doesn’t cause the application to crash, it just returns null
from getStringExtra(―MyString‖) which means your TextView’s text property is set to nothing,
hence the empty screen. One important thing to note is that you probably don’t want to
use ―MyString‖in the code like this but may want to define a public static final String somewhere
that can be shared by both your activities.
You can use putExtra and getTYPEExtra for all manner of simple variable types (Strings, ints,
boolean, byte, double, etc). However, what if you want to pass a complex type via an
intent? Android handles this by usingParcelable. Parcelable is an interface that can be thought of
as similar to serializable. However, Parcelable has been optimized for Android which is why it is used
instead of the standard java serialization. To see this in action, add a new class to your project
named “PassableObject” and use the new class wizard to set the class to
implement Parcelable. You should end up with a class like this:
public class PassableObject implements Parcelable {
@Override
public int describeContents() {
// TODO Auto-generated method stub
return 0;
}
@Override
public void writeToParcel(Parcel arg0, int arg1) {
// TODO Auto-generated method stub
}
}
Now you’ll add a String to your class and implement the necessary methods to handle the
parcel. Specifically, you need to make a constructor that will generate our object with a parcel as the
input parameter and you’ll need to write what needs to be saved in the parcel. Once done your
PassableObject should look like this:
public class PassableObject implements Parcelable {
private String myStringValue;
public PassableObject() {}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel outParcel, int flags) {
outParcel.writeString(myStringValue);
}
Now in your onCreate method in ActivityTwo you can get the parcelable object and cast it back to
yourPassableObject class:
PassableObject passableObject = (PassableObject)
myIntent.getParcelableExtra("MyPassableObject");
textView1.setText(passableObject.getMyStringValue());
So now we have a way to pass simple variable types and complex objects to new activities
using Intents. This is going come in handy even more when we start using Intents to talk to other
applications.
Other Methods
There are certainly other methods that could be explored such as writing data to files in one activity
and reading those files in other activities. Outside of special circumstances, most of these methods
won’t deliver optimum results. Unless you find yourself in doing some very tricky stuff, your best bet
will be to use the putExtra andgetExtra methods to pass data between activities.
You can download the source from today here.
31 Days of Android: Day 8–The Android Project Structure
Posted on: 11/28/2011 12:33:00 PM by Chris
<< Day 7: Sharing Data between Activities
Day 9: Debugging Apps >>
Today we’ll look at the project structure and file organization for
an Android project. This is something that we could have covered earlier in the 31 Days of Android
series though it wasn’t entirely necessary. We’ll go folder by folder to explain what is in our most
recent DaySeven project.
src-This folder contains our actual source code. In this project we haven’t broken things down further
into different namespaces, though normally this would be recommended. You can see an example of
how Google has broken things down into different namespaces in their Google IO 2011 app here. In
this example they have a uinamespace which is then split into namespaces for different device types
(phone, tablet, etc).
gen-This contains files generated by Eclipse / Android. Specifically here we see R.java. If you recall
from when you were accessing the IDs of elements in our layout, you did so using R.id.name. This is
where those IDs are put into code so that you can access tehm.
Android 2.2-This folder actually contains our reference to the Android SDK.
assets-The assets folder is a location you can put raw files that you don’t need ID access to (so you
can’t access them via the R class mentioned above). This folder is useful for things you might not
need to localize or alter for different resolutions such as font files. You probably won’t end up using
this folder very much.
bin-This should be old hat for anyone that has done a lot of development. The bin folder contains the
compiled application files. In general, you can ignore this folder.
res-This folder is incredibly important. However, there aren’t any files actually in it. Instead it is split
up into sub-folders.
drawable-*-Contain graphical resources (jpegs, pngs, etc) for devices with the specific
resolution. For example, if your device is HDPI, the application will automatically use the HDPI
resources. You’ll find some applications that have many more drawable folders to specify landscape
vs. portrait or for non-image graphical resources. When I say “non-image graphical resources” I’m
referring to xml files that describe graphical resources such as an xml file that might describe the
appearance of a button. In addition, Android is smart enough to find a graphical resource if it isn’t in
the devices specific folder. For example, if you have a header.jpg file that’s only in the drawable-
mdpifolder, Android will use that file even if the device is HDPI or LDPI.
anim-While not present in this project, an anim folder will contain xml descriptions of animations.
layout-This is another folder that can actually be broken down into many other folders. On the
surface, layout contains xml descriptors of views. You’ve already worked with two different layouts so
you should have some idea what they are for. In addition to the plane layout folder, you can have
layout-land and layout-port for orientation specific layouts. Also, you can have size specific ones
such as layout-large-land or layout-xlarge-port. One thing worth pointing out is that as of Android 3.2,
there are new selectors for the layout folders. Now res/layout/main_activity.xml would correspond
to phones, res/layout-sw600dp/main_activity.xml would be used for tablets. You can read more
about the new selectors for layouts on the Android developer blog.
menu-The menu folder contains xml descriptors of menus. You’ve already created one as an options
menu.
values-This folder contains xml files with key value pairs essentially. You’ve already seen
the strings.xml file which contains a string keys and values. In addition to the strings file, you can
create colors, styles, integers, and more.
31 Days of Android: Day 9–Debugging your Applications
Posted on: 11/29/2011 12:18:00 PM by Chris
<< Day 8: Project Structure
Day 10: The Back Button >>
It’s relatively easy to tap anywhere in the camera frame to dismiss this tab, however, some users
might feel that tapping the back button should remove the tab but not close the camera. It’s quite
possible you might have something similar in your application. Instead of leaving your activity on the
back button tap, you could check to see if the tab was visible, and then hide it. Then on a second tap
of the back button, you could back out of the activity as a user would expect.
Can I Assume Android Devices will all have a Back Button?
Starting with the tablet devices that came out with Android 3.0 on them, manufacturers started
moving away from hardware buttons and instead had a button / status bar at the bottom of the
Android OS screen. It looks like Android and Google are continuing this with both phones and tablets
in Ice Cream Sandwich. So while future devices may not have a hardware back button like current
devices do, they will still feature a software implemented back button.
We didn’t do a lot of coding today but you can download the latest source here.
31 Days of Android: Day 11–Device Orientation
Posted on: 12/1/2011 2:06:00 PM by Chris
<< Day 10: The Back Button
Day 12: Linear Layouts >>
For today’s entry in 31 Days of Android we’re going to talk about device
orientation. In the easiest terms, orientation dictates if the device is
horizontal (landscape) or vertical (portrait). The orientation of a device
can affect many different things. Most commonly, you might have
different layouts depending on the orientation. Less commonly, you might
want to prevent a layout from displaying in a specific orientation or show
an animation specific to that orientation. We’re going to start our work
using the code from Day 10.
Detecting the Current Orientation
There are two different ways to detect the current device
orientation. There is a way you can use if you’re programming for devices
prior to 2.2 and a way you can use for 2.2 and above. The former method
is now deprecated so if you’re not supporting Android below 2.2, I
wouldn’t bother with it.
Open up your DayTenActivity and after setting your lblTextViewOne you can get an instance of
display and then use the getOrientation method. You can then check it’s orientation against
some Configuration constants. Unfortunately this method also requires us to compare the height
and the width of the screen. Even more unfortunate, you’ll see that the orientation reported
as ORIENTATION_PORTRAIT is actually landscape:
lblTextViewOne = (TextView) findViewById(R.id.lblTextViewOne);
lblTextViewOne.setText(R.string.test_one);
if (MyOrientationEventListener.canDetectOrientation()) {
MyOrientationEventListener.enable();
} else {
lblTextViewOne.setText("Can't DetectOrientation");
finish();
}
First, you instantiate the listener. Note that you’re passing
in SENSOR_DELAY_NORMAL here. You can set different constant values here which will dictate
how often the sensor will detect changes. If you’re creating a fast action game, you’ll probably want
something more intense than the normal setting. In the listener’sonOrientationChanged method,
you’re just setting the text to be the degrees of orientation (remember that 0 and 180 are portrait and
90 and 270 are landscape). This method isn’t only called when the orientation rotates so while you’ll
only see the matching orientation numbers with the emulator, if you run this on a real device, you will
see it go through the full range of degrees as you rotate the device. Note that at the bottom you’re
starting the listener if you can detect orientation and you’re finishing the activity if you can’t. You’d
probably want to handle things a little more gracefully if for some reason the sensor wouldn’t
work. One example of an app that might use this is the possibly infamous iBeer app that graphically
shows a beer pouring as you tilt the device.
Specifying Layouts for Orientations
Creating different layouts for an orientation is relatively easy and we already spoke about it
a little bit. To create an orientation for a specific layout, right click on your res folder and go to New –
> Other and then choose Android XML Layout File. Let’s create orientation specific layouts for
our second_layout. If you enter “second_layout” into the File field, the wizard will warn you that the
destination file already exists. You can ignore this for now and click Next. On the next screen you
can specify qualifiers to limit when the layout you’re creating is used. FindOrientation in the left list
and click the right arrow. A new drop down will appear on the right side. Chooselandscape from
that menu and you’ll see that the folder at the bottom will change to /res/layout-land. ClickFinish to
create the layout. Now repeat that but choose portrait to create a layout in /res/layout-port.
If you run your app now and tap the second button which fires off the second activity, the app will
crash. The reason it crashes is because in the ActivityTwo code sets the text value
of textView1. Since you haven’t added the TextView to your new layouts, this won’t work. To fix
this, copy the TextView from our originalsecond_layout.xml to the new ones:
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TextView" />
Now you can alter the two new second_layout’s to make them look better depending on the
orientation.
Preventing a Layout from Rotating
The last orientation specific thing we’ll talk about is how we prevent an activity from rotating. In order
to do so you can set a requested orientation in the onCreate method like so:
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
Adding this to DayTenActivity will prevent it from being shown in portrait. After adding this and
running your app, when you rotate, you’ll always see landscape. Conversely, if you pass
in SCREEN_ORIENTATION_PORTRAITyou’ll always get portrait. Another method of accomplishing
the same thing is to specify screenOrientation in your manifest file as an attribute on your activity. If
you wanted to lock DayTenActivity to landscape, you would do so like this:
<activity
android:label="@string/app_name"
android:screenOrientation="landscape"
android:name=".DayTenActivity" >
Remember that while you can lock screen orientation easily now, think about your users and what
they would expect your app to do. Most people expect that when they rotate their device, the view
will change to reflect having more room vertically or horizontally. If you lock the orientation, that
expectation is not met.
You can download today’s code here.
So far in our 31 Days of Android series, we really haven’t focused on the visual element of our
applications at all. We’ve created some layouts and we’ve added a few UI controls, but we haven’t
delved into it much. Starting today, we’re going to spend a few days talking about the different
layouts and interface elements. You can use thecode we left off with yesterday to start out with (I’ll
be referring to it with a DayTwelve instead of DayEleven today).
The first thing you should know is that all of the layout types (including the LinearLayout) as well as
all of the user interface elements (TextView, EditText) descend from the View class. Layouts
descends from a ViewGroupclass which in turn descends from View. Being a ViewGroup means
that it can contain other views. If you look at the main.xml file in the res/layout folder, you’ll see that
the layout we’ve been working with so far has several Views contained in a ViewGroup, in this case
a LinearLayout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<TextView
android:id="@+id/lblTextViewOne"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello" />
<EditText
android:id="@+id/editText1"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<requestFocus />
</EditText>
...
</LinearLayout>
Here there are only Views in the LinearLayout but there is nothing preventing you from putting
otherViewGroups into the root ViewGroup (so layouts within layouts). If you run the app you should
have it show up like this:
Looking at the XML and what your app shows, you should be able to glean that
a LinearLayout simply allows you to stack Views inside of it (in either
a vertical or horizontal orientation). Let’s start looking at the attributes of theLinearLayout:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
layout_width and layout_height will be in many of the views you’ll use in Android apps. Here you’re
saying that the root LinearLayout should fill up the whole screen by setting them both to
“fill_parent”. The alternative “present” values are “match_parent” and “wrap_content”. If you
change the layout_height value to bewrap_content and rerun, the app has changed but you won’t
be able to see it. Since the background is black by default, there is no way to differentiate the bottom
of the layout from the rest of the background. In order to demonstrate this in action, you’re going to
add a new xml values file and set a new attribute on your layout. Start by right clicking on
the res/values folder, go to New—>Other and choose Android XML File. In the name enter
“colors.xml” and click Finish. This generates a new resources file where we can specify different
colors. We want a nice bright green color so when you’re done your xml should look like this:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="Green">#59FF00</color>
</resources>
Now you can access this color in the same way you’ve accessed values in the strings.xml file
before. Going back to your main.xml layout file, make sure you’ve set the layout_height to be
“wrap_content” and then add thebackground attribute and specify your new color value
using “@color/Green”. When you’re done, the rootLinearLayout should look like this:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="@color/Green">
Now when you run your application, it will look a little bit different:
So what’s happened here is that you’ve set the background to green and said to only make the layout
big enough to wrap what’s inside of it. We have 4 Views in our layout so the layout ends immediately
after the last View, in this case the second Button. As I mentioned before, black is the default
background color in the Android emulator so since we’re not specifying anything besides
our LinearLayout the black default takes over. If you changelayout_height back to “fill_parent”,
you’ll see the green expand to the bottom of the screen.
For now, drop the background color so your app is a little easier to look at. The last attribute that was
already in your LinearLayout is orientation which is set to “vertical”. By default when you create a
new layout with it’s base ViewGroup being a LinearLayout, it will have a vertical orientation. If you
remove the orientation attribute from the layout, it will be horizontal by default. Go ahead and
remove the orientation attribute so your xml looks like this:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
When you run your application, you’ll be treated to an odd sight, only the “Rotation: 0” will be visible:
The reason you’re only seeing the first TextView is because it’s layout_width is set to fill_parent. If
you change it to wrap_content and run your app, now it should look like this:
This doesn’t look very good and we could “fix it” by putting some space between our two Views but in
this situation, we really want our root LinearLayout to use the vertical orientation so that we can use
the whole screen.
The next two attributes we’ll look at are gravity and layout_gravity. The difference between these
two can be slightly confusing but with enough experience, you should be able to pick it up (and
fortunately it’s easy to switch between the two if you can’t remember). layout_gravity controls the
gravity of the View it’s set on in it’s parent. So if you set the layout_gravity of the LinearLayout it’s
relative to the root. gravity controls the gravity of all of aView‘s children. So if you set the gravity on
the LinearLayout, it will affect the TextView, EditText, andButtons. Let’s actually try it out and see
what happens. First, try setting gravity of the LinearLayout to―center_horizontal‖ your app will
look like this:
Remember that gravity affects a View’s children and it will make sense that all of your
children Views are in the middle of the screen. Remember that the first TextView appears to not be
centered but that is actually because it’s layout_width is set to ―fill_parent‖. If that was changed to
“wrap_content” then it too would appear in the center. Conversely, if you don’t set gravity but
instead set layout_gravity to “center_horizontal”, and then run your app it will look the same as it
did before you added any gravity attributes. Similarly if you set layout_gravityto “right” it won’t look
any different. If gravity is set to “right” though, your app will look like this:
The last layout related thing we’ll cover today is adding a ViewGroup to
another ViewGroup. Specifically in the app we have right now, let’s say we want to have
a TextView to the left of our second button. There isn’t anyway we could just drop a TextView in
and make it work right now. First, we need to drop in another LinearLayout. Remember that if you
don’t specify an orientation, it will assumed to be horizontal. Once we wrap the button2 in this new
linear layout, we can add a new TextView in as well. Your main.xml layout should end up looking
like this:
<LinearLayout
android:id="@+id/linearLayour2"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/app_name"/>
<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button" />
</LinearLayout>
And when you run your app, it should now look like this:
Nested ViewGroups will be something you will end up using very frequently as you develop more
sophisticated Android apps though there are some concerns with doing so. Remember that when
you call setContentView in your Activity there is some overhead with inflating your view from the
xml. If you have tons of nested layouts, it’s going to take longer and use more memory. Thankfully
there are other types of layouts you can use to simplify things at times. We’ll take a deeper look at
this and other layouts in the next few days.
Also, it’s worth pointing out that as of Android 4.0, Google is recommending you use the new
GridLayout instead of LinearLayout.
You can grab today’s code here.
31 Days of Android: Day 13–Relative Layouts
Posted on: 12/3/2011 2:06:00 PM by Chris
<< Day 12: Linear Layouts
Day 14: Table and Frame Layout >>
The RelativeLayout is the second most common layout I’ve used after
theLinearLayout. Today as we continue our 31 Days of Android, we’ll
be focusing on the RelativeLayout. Remember that all of our layouts
are descendants of ViewGroups which descend
from Views. Furthermore,ViewGroups contain Views. Recalling all of that, we’re ready to dig
deeper into the RelativeLayout. We’re going to start with a stripped down version ofyesterday’s code
which you can grab here.
Where as the LinearLayout stacks it’s elements one after another vertically or horizontally,
the RelativeLayout requires each of it’s elements to specify it’s own position in regards to it’s fellow
elements and it’s parent. I say requiresbecause technically you don’t actually have to specify the
position. We’ll get this set up so you’ll see what I’m talking about. Today you’re going to make all of
our changes to main.xml. Themain.xml layout already has a few different Views including
some TextViews, an EditText, and a couple buttonsand will look like this:
As I said before, the RelativeLayout requires you to specify each child’s position in relation to it’s
parent and siblings. Since we didn’t specify anything for the RelativeLayout’s children, they all
defaulted to the top left of the parent. There are going to be some circumstances where it might
make sense to have things overlap, but in this case, we don’t want that. Let’s say we want to make
the view do the following:
Show the TextView that was at the top at the top right of the screen.
Show the EditText beneath that aligned on the left side of the screen.
Show the first Button in the middle of the screen.
Show the TextView and second Button at the bottom right of the screen
The RelativeLayout gives us several new attributes that we can set on each of it’s children. The first
thing you want to adjust is the layout_height of
the RelativeLayout from wrap_content to fill_parent. If you don’t layoutwill stay squished even
when you say to put the elements at the bottom of the screen. Your altered layout XML should look
like this:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
Now you’re ready to start making the above changes. Let’s start at the top with the
first TextView. It’s already going to be at the top of the RelativeLayout by default so you just need
to specify that it’s on the right. This is done using the layout_alignParentRight attribute:
<TextView
android:id="@+id/lblTextViewOne"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello"
android:layout_alignParentRight="true"/>
Interestingly, if you rerun your application now, you won’t see any difference. The reason for that is
that elements are drawn from top to bottom from the layout xml. Since the lblTextViewOne is at the
top of the XML it get’s drawn first and is overlapped by the other Views. Once you’ve finished
moving some more elements, you’ll see the TextView in it’s new position. Since you want
the EditText below the first TextView you’ll want to use thelayout_below attribute. This takes in an
ID for the element you want it to be below so we’ll use lblTextViewOne. The changed EditText will
look like this:
<EditText
android:id="@+id/editText1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/lblTextViewOne">
<requestFocus />
</EditText>
Now when you run your app, you’ll start to see something better:
Now you can see that your TextView is indeed at the top right of the layout and the EditText is right
beneath that. You’re half way done with the changes. Next, let’s put the first button in the middle of
the screen. To do this, you want to use the layout_centerInParent which will center our element
both vertically and horizontally in it’s parent. Now your Button xml will look like this:
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/click_me"
android:layout_centerInParent="true"/>
Now run your app and feast your eyes upon a centered button:
Now you’re ready for your last change. The LinearLayout need to be at the bottom right of the
screen. To do so, you’ll need to use the layout_alignParentRight attribute again as well
as layout_alignParentBottom. I’m sure you can guess what setting the layout to align to the
parent’s bottom. Here’s your altered XML:
<LinearLayout
android:id="@+id/linearLayour2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true">
And your screen finally looks the way you want it to:
You could make something that would look similar using a LinearLayout if you tried but it wouldn’t be
nearly as efficient as what you’ve done here. There are a few RelativeLayout specific attributes that
we didn’t use today so I’ll cover them quickly:
layout_toLeftOf - Aligns the element to the left of whatever element you pass in.
layout_toRightOf - Aligns the element to the right of whatever element you pass in.
layout_above - Aligns the element above of whatever element you pass in.
layout_below - Aligns the element below of whatever element you pass in.
layout_alignBaseline - Aligns the element with the middle of whatever element you pass in.
layout_alignLeft - Aligns the element with the left edge of whatever element you pass in.
layout_alignRight - Aligns the element with the right edge of whatever element you pass in.
layout_alignBottom - Aligns the element with the bottom edge of whatever element you pass in.
layout_alignParentLeft - Aligns the element with the left edge of it's parent.
layout_alignParentTop - Aligns the element with the top edge of it's parent.
layout_centerHorizontal - Horizontally centers the element in it's parent.
layout_centerVertical - Vertically centers the element in it's parent.
The last RelativeLayout attribute to talk about is layout_alignWithParentIfMissing. If this attribute
is set to true, then it will treat the element’s parent as it’s anchor if the element specified
in layout_toLeftOf, layout_toRightOf,layout_above, etc can’t be found. So for example, if you set
an element’s visibility to GONE but another element referred to it in one of
the RelativeLayout specific layout attributes, it will use the parent instead.
You’ve already got a LinearLayout inside of a RelativeLayout in this example. You could reverse
this and have a RelativeLayout inside of a LinearLayout or have several nested layouts. The point
is that with just these two different ViewGroups you can create some very complex views. There are
some other important common layout types that we’ll go over but understanding these two types will
get you far with developing your Android apps.
You can grab today’s final code here.
31 Days of Android: Day 14–Table Layout and Frame Layout
Posted on: 12/4/2011 2:09:00 PM by Chris
<< Day 13: Relative Layouts
Day 15: Toasts >>
There are a few things worth pointing out here. First, this doesn’t look that much different from when
the root was a LinearLayout. The one thing that really does look different is the Click
Me button. Where as before the button was only slightly larger than “Click Me” text, now it spans the
whole screen. Even though the layout_width of the button is still set to wrap_content. This is a
weird side effect of using a TableLayout. If you’re creating aTableLayout, you’re usually also going
to wrap it’s children in TableRows. Go ahead and wrap all of the children of the TableLayout in
those TableRows. When you’re done, your xml will look like this:
<?xml version="1.0" encoding="utf-8"?>
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TableRow >
<TextView
android:id="@+id/lblTextViewOne"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello" />
</TableRow>
<TableRow >
<EditText
android:id="@+id/editText1"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<requestFocus />
</EditText>
</TableRow>
<TableRow >
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/click_me" />
</TableRow>
<TableRow >
<LinearLayout
android:id="@+id/linearLayour2"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/app_name" />
<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button" />
</LinearLayout>
</TableRow>
</TableLayout>
When you run your app, it won’t look very much different (the EditText and first TextView won’t go all
the way to the end of the screen). For the sake of really understanding what’s going on, remove
the LinearLayout from the last TableRow so that it just contains a TextView and a Button. Now
your view will look like this:
What’s happened is that each element in a TableRow belongs to a separate column. Outside of the
last, eachTableRow only has one element. The first column ends up being the width of the largest
element (in this case theEditText and still oddly the first Button) and the second column ends up
comprising the only send element any of the rows have, the second Button. The last important thing
to talk about when it comes to TableLayouts is an attribute you put on the root layout
element: stretchColumns and shrinkColumns. These two attributes can be used to stretch or
shrink all or certain columns at run time. Since you may not know the width of certain elements at
design time, you may end up needing to use these attributes. The syntax of the value that you set
these attributes to is either a single column number, a comma separated list of column numbers, or
an asterisk to signify all columns. In your view currently, the “Button” text is being cut off on the right
side. Let’s look at some options we can use to fix this
using stretchColumns and shrinkColumns. The first thing you might think to try is to say stretch
column 1. Go ahead and do that and your xml should look like this:
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:stretchColumns="1">
Now run your app and you won’t get quite what you were probably hoping for. In fact, things won’t
look different at all. Column one is being told to stretch, but there isn’t any room for it to stretch. If
you change the stretch to a shrink and specify column zero, things will start looking better:
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:shrinkColumns="0">
Now run your app and you should see the button now fitting on the screen:
So here, you’ve told column one that it should shrink to allocate room for other columns. This gives
column one the room it needs to expand. It’s important to remember that you can stretch and shrink
columns as needed when you use a TableLayout though you may need to play around to get your
desired result.
The FrameLayou
The last layout we’ll take a very quick look at is the FrameLayout. The FrameLayout is probably the
simplest, and possibly least used, layout element you’ll run into. It’s intent is to be filled with a single
child element that you can swap in and out. You can have multiple elements in a FrameLayout and
the “later” children will be drawn over the “earlier” children. The most common example of this I’ve
seen is putting an ImageView in as the first element and then adding TextViews or Buttons
after. Let’s open up res/layout/second_layout.xml and change it to a FrameLayout. After you’ve
changed the layout, you can also get rid of the orientation attribute that yourLinearLayout had since
the FrameLayout doesn’t use it. Now you should be left with this:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TextView" />
</FrameLayout>
When you run your application, you should see your TextView in the top left corner of the screen. To
really see the FrameLayout in action, you need to add an ImageView as the first child of your
layout. Go ahead and add anImageView and set it to the only drawable we have in our
project, ic_launcher. As a side note, the ic_launcherfile is used as the app icon and is specified as
an attribute of the application in the manifest file. When you’re done, your XML should look like
this:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<ImageView
android:id="@+id/imageView1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/ic_launcher" />
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TextView" />
</FrameLayout>
If you run now, you’ll see the image but the text will still be at the top left. To fix this, add
a layout_gravityattribute to the TextView and set it to center. Now when you run your app, you’ll
have the text in the center of the image:
So by now you’ve played with the four big layout types: LinearLayout, RelativeLayout, TableLayout,
and FrameLayout. New in Android 4.0 is the GridLayout which may be a simpler option than using
some of the previously discussed layouts so if you’re going to develop for Ice Cream Sandwich,
it’s worth reading about GridLayout.
You can download the final code from today here.
31 Days of Android: Day 15–Toasts
Posted on: 12/5/2011 10:43:00 AM by Chris
<< Day 14: Table and Frame Layouts
Day 16: Notifications >>
Let’s look at each call to makeText. You’re passing in the Application Context for both of these
method calls. You could have passed in the Activity context if you had access to it. Since your calls
to makeText were in theonClickListener you didn’t have access to it (though you could have created
a member variable onDayFifteenActivity and set it to the current activity prior to
the onClickListener if you wanted to). As mentioned before, one of the methods is taking in a String
and the other takes in a Resource ID. The last parameter passed in is an integer for how long
the Toast should show up. You might think you could pass in any amount of time in milliseconds
here but that isn’t the case. You can only pass
in Toast.LENGTH_LONG orToast.LENGTH_SHORT. LENGTH_LONG corresponds to 3.5 seconds
and LENGTH_SHORT is 2 seconds. It might be possible to override Toast to show it for shorter or
longer periods of time, but I think the intention is to keep these sort of notification similar from app to
app. Lastly, since makeText returns a Toast object you callshow to make it appear on the screen.
It’s worth noting out that once you’ve called show on your Toast, it does not show up
immediately. The first call toshow appears first and then any other calls will appear in sequential
order after that. In this way, you’re prevented from showing multiple Toasts but only getting the last
one on screen.
The default location for a toast is, as demonstrated above, the bottom of the screen. You do have the
ability to alter this but it requires a little more work. Let’s change the second call to makeText to
handle this. First you need to remove the show call at the end and store the results
of makeText. When you do this, you’ll have aToast object to work with. Using the Toast object you
can alter the duration and the text that you specified in the constructor. More important is that you
can set a margin, alter the gravity, and change the view used. Both margin and gravity can be used
to alter the position. So if you wanted to put the toast at the top of the screen, you’d pass in
Gravity.TOP. If you wanted it to be in the center, you’d pass in Gravity.CENTER. Go ahead and
make these changes and your click listener should now look like this:
button1.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
lblTextViewOne.setText(editText1.getText());
Toast.makeText(getApplicationContext(), "This is a toast!",
Toast.LENGTH_LONG).show();
Toast myToast = Toast.makeText(getApplicationContext(),
R.string.app_name, Toast.LENGTH_SHORT);
myToast.setGravity(Gravity.CENTER, 0, 0);
myToast.show();
}
});
Now when you run your app and the second Toast appears, you should see it in the center:
If you wanted to make a slight adjustment to where the toast appears, the second and third
parameters tosetGravity will offset it for you.
So now you know how to make Toasts appear and how to change where they are located and how
long they show up. The last useful thing to know about them is that you can change their
appearance. The setView method on the Toast object is used here. To do this, you need to create
a new layout first. To do so, right click on theres/layout folder and go to new—
>Other. Choose Android XML Layout File. You can leave it as aLinearLayout and name the new
layout “toast_layout”. In this layout you could put just about whatever you want. Remember that
you can’t have any user interaction so putting Buttons wouldn’t make sense. For now, add
an ImageView and a TextView to your new layout:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="horizontal"
android:id="@+id/newToastLayout"
android:background="#FFAAAAAA">
<ImageView
android:id="@+id/imageView1"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:src="@drawable/ic_launcher" />
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:text="TextView" />
</LinearLayout>
You’ve already set the ImageView to be the ic_launcher icon but you still need to set the TextView’s
text and use it to set the Toast’s view. To do this, go back to your DayFifteenActivity. In order to
generate a view programmatically, you need to get an instance of LayoutInflater and inflate the
layout by resource ID. Then you can find views within it just like you do with a normal activity via
the findViewById method. When you’ve pulled out the TextView and set it’s text property, you can
then pass it in to the setView method. Your new toast code should look like this:
Toast.makeText(getApplicationContext(), "This is a toast!",
Toast.LENGTH_LONG).show();
Toast myToast = Toast.makeText(getApplicationContext(), R.string.app_name,
Toast.LENGTH_SHORT);
myToast.setGravity(Gravity.CENTER, 0, 0);
LayoutInflater inflater = getLayoutInflater();
View toastLayout = inflater.inflate(R.layout.toast_layout,
(ViewGroup) findViewById(R.id.newToastLayout));
TextView toastTextView = (TextView) toastLayout.findViewById(R.id.textView1);
toastTextView.setText("This is also a toast!");
myToast.setView(toastLayout);
myToast.show();
Now when you run your app, your second toast will look like this:
Toasts are a simple and great way to get information to users. Now you should have knowledge to
show them when you want, for how long you want, and make them appear how you want them
to. You can download the final code from today’s app here.
31 Days of Android: Day 16–Notifications
Posted on: 12/6/2011 10:06:00 AM by Chris
<< Day 15: Toasts
Day 17: Animating between Activities >>
As we continue the 31 Days of Android, we’ll talk about how you can add
things to the Notification bar. The notification bar is at the top of phone
devices and at the bottom for tablet devices and normally shows the
time, battery meter, WIFI / wireless signal strength indicator, and an
alarm indicator. These are all at the top right on a phone device and on
the bottom left on tablets. Individual applications can add notifications to
this bar which show up at the top left on phones and the bottom right on
tablets. When you receive emails or a calendar appointment shows up in
the top left, these are examples of notifications. Thankfully, your
applications can easily add notifications to this bar. Today we’ll go
through how to do this. We’ll start with some sample code that already has a few Views added and
you can grab that source code here.
Creating your first Notification
Just creating a notification isn’t very complicated. Let’s go ahead and create one now. Open up
theDaySixteenActivity and locate the onClickListener for button1. For now, you’ll just add code to
create the notification in this listener. Step one is to get an instance of NotificationManager:
String notificationService = Context.NOTIFICATION_SERVICE;
NotificationManager notificationManager =
(NotificationManager) getSystemService(notificationService);
Similar to when you got a WindowManager instance so you could detect the display orientation, here
you’re getting an instance of the NotificationManager. This isn’t something that you have to create
or set up in your applications but something that Android handles and you only need to
reference. Next you need to create a new instance ofNotification and specify an icon, text, and the
time to show in the notification:
Notification notification = new Notification(R.drawable.ic_launcher,
"Hello Notification!", System.currentTimeMillis());
Here you’ve passed in the ic_launcher icon ID to use as the icon of the Notification. If you try to
pass in zero, your Notification won’t show up. Next you are passing in a String that will show
up ONLY in the Notification bar. You could pass in any String you want but here you’re just doing
“Hello Notification!”. If you were to pass in a String that is too long and won’t fit, it will be broken into
several lines which will show up sequentially (hence the variable is named tickerText). Lastly you’ve
specified the current time. This means when the Notification shows up, it will have a timestamp of
right now. The reason you send a timestamp is so that if the user’s device isn’t active when
the Notification is sent, it will still show the time when you wanted it to. This means that you can set
your Notification for the past or the future. The past makes sense because your app could only be
triggered to send a Notification after the phone has been asleep, so it might want to notify the user
that a message was received in the past. Why you might want to specify a future time (knowing that
the Notification will still show up now) I’m not sure. Next you need to create an Intent for the activity
that will be called when the notification is tapped and use it to create a PendingIntent.
Intent notificationIntent = new Intent(getApplicationContext(),
DaySixteenActivity.class);
PendingIntent contentIntent = PendingIntent.getActivity(getApplicationContext(),
0,
notificationIntent, 0);
notification.setLatestEventInfo(getApplicationContext(), "Notification Title",
"Notification Text", contentIntent);
PendingIntents are used to specify an action that can be performed later on your application’s behalf
by another application. You can read more about Pending Intents here. Here you’re creating an
Intent for the currentApplication and Activity. Then the Notification you previously created has it’s
latest info set. Here you’ve passed in a title and text which is used when you the user pulls down on
the Notification bar. Last, you pass in the PendingIntent. Last you tell the NotificationManager to
show the Notification:
notificationManager.notify(1, notification);
Now when you fire your application up and click the button in the middle of the screen, a moment
later yourNotification will show up in the Notification bar:
A moment later the text will disappear and the time will come back to the top right corner. The icon
will stay behind. If you click the button again, you won’t see another notification right now. The
reason for this is the 1 you passed in to the notify method above. This is an identifier that ensures
you don’t show the same notification twice. If you were to change the ID and send the same
notification again, it would also show up and you would have two icons at the top left. Very important
to note is that if you changed the text values of the notification, then it would show up and replace the
one you had previously sent. Since you’re not changing any values here, nothing new shows
up. This is how the Gmail program’s notification always has the latest number of new emails you
have but there is only one visible Notification. The program keeps sending a Notification with same
ID. Go ahead and drag down on your Notification bar and you’ll see the title and text you used in
thesetLatestEventInfo method:
If you click the Notification the Notification bar will roll back up and it won’t look like anything is
happening. This isn’t true though. When you click the Notification the PendingIntent is used to call
the notificationIntent you created before. Since you passed in DaySixteenActivity.class, it’s firing
a new instance of DaySixteenActivity. You can test this by entering text into the EditText near the
top and then clicking the Notification again. TheEditText will be restored to what was showing
initially (“This was our DaySixteen test.” if you’re following along). If you hit back, that Activity will
dismiss and you’ll be back at the one where you edited the text. Also, theNotification never went
away like you think it would.
Clearing the Notification
For now, you could tap “clear” in the Notification pull down to remove notifications. However, you’re
probably use to having Notifications going away when you tap on them. Let’s look at how to
accomplish that. The easiest way to dismiss the Notification when the user taps them is to set a
property on the Notification object before you pass it to the notify method. The flags field is a bit
field that allows you to set several options. In this case, you just want to
add FLAG_AUTO_CANCEL to it like so:
notification.flags |= Notification.FLAG_AUTO_CANCEL;
Now when you call notify and then tap on your Notification, it will go away. There are a few other
flags you can use but we will discuss them later. Another way you can clear Notifications is by
calling theNotificationManager’s cancel and cancelAll methods. cancelAll will clear all
notifications from your app. cancel on the other hand, takes in an ID and will only remove
the Notification from your app that matches the ID. So if you add this code to
your DaySixteenActivity’s onCreate method, it will handle clearing the Notificationwithout having to
set the flags as described above:
String notificationService = Context.NOTIFICATION_SERVICE;
NotificationManager notificationManager = (NotificationManager)
getSystemService(notificationService);
notificationManager.cancel(1);
You’re calling the ID specific method here but you could always change this to cancelAll.
}
Today you’ll need to do a little bit more than just call the super implementation
of onBackPressed. Thankfully, you just need to call overridePendingTransition again. Now
your onBackPressed will look like this:
@Override
public void onBackPressed() {
super.onBackPressed();
overridePendingTransition(R.anim.slide_in_left, R.anim.slide_out_left);
}
Notice that you’re doing the same in and out animations before. This will cause the appearance to be
the same. Go ahead and run your app and when you back out of your second Activity it should slide
out to the left and the first Activity will slide in from the left as well.
Orientation Specific Animations
Just like creating a Layout for a specific orientation, you can create an Animation for a specific
orientation. If you wanted to slide up and down when the orientation was landscape and left and right
when you were in portrait, the naming scheme wouldn’t make much sense. You could create a
generic name that would be used for both landscape and portrait orientations. Or, you could put all
your animations in the anim folder and then choose which to use in the code. Let’s do that to see
how it works. First, add two new animations to your anim folder. The first should be
named slide_up and will have this XML:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/decelerate_interpolator"
android:shareInterpolator="true">
<translate android:fromYDelta="100%p" android:toYDelta="0%p"
android:duration="500" />
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:fromAlpha="0.0" android:toAlpha="1.0" android:duration="400"/>
</set>
Now add another named slide_down with the following XML:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/decelerate_interpolator"
android:shareInterpolator="true">
<translate android:fromYDelta="0%p" android:toYDelta="100%p"
android:duration="500" />
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:fromAlpha="1.0" android:toAlpha="0.0" android:duration="500"/>
</set>
Now, you’ve already seen how to figure out what the current orientation is. You need to get an
instance of Displayand use that to check it’s rotation (as noted in the linked article, if you’re using an
older version of Android, you’ll have to do something else). Your Button2 onClickListener should
be changed to check the rotation and then specify the in and out animations depending on that like
so:
Button button2 = (Button) findViewById(R.id.button2);
button2.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
startActivity(new Intent(getApplicationContext(), ActivityTwo.class));
Display display = ((WindowManager)
getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
if ((display.getRotation() == Surface.ROTATION_0) ||
(display.getRotation() == Surface.ROTATION_180)) {
overridePendingTransition(R.anim.slide_in_left,
R.anim.slide_out_left);
} else if ((display.getRotation() == Surface.ROTATION_90) ||
(display.getRotation() == Surface.ROTATION_270)) {
overridePendingTransition(R.anim.slide_up, R.anim.slide_down);
}
}
});
So here, you’re saying if the device is in a portrait orientation, slide out to the left and slide the
new Activity in from the left. If you’re in landscape, slide down and slide up the new Activity. In
the onBackPressed method of the second Activity you’ll use the exact same code to override the
pending transaction.
Now that you know how to override the transactions going in and out of activities, provided you can
figure out how to create a new animation, you should be able to get (nearly) any effect you want
between activities. Sliding in and out, left and right, up and down, are very common and relatively
easy to create. In the attached sort you’ll find a few more of the more common animation XMLs. You
can download the latest source code here.
31 Days of Android: Day 18–The Webview
Posted on: 12/8/2011 9:27:00 AM by Chris
<< Day 17: Animating between Activities
Day 19: Scrollviews >>
Today’s entry in 31 Days of Android will cover two important topics. Our
primary focus will be on the WebView and the second is something we
have to do to make make the WebView work,
permissions. The WebView can be used to either present a web page
within your application or to create a web application. In addition, you
can show online web sites or use the WebView to display static HTML
resources that you package as part of your app. Let’s dig in and
implement this in an application right now. You can grab the code we’ll
start with here.
Getting Permissions Added
We might as well get this part out of the way or our WebView won’t work down the road. One thing
we haven’t looked at in this Android series so far has been application permissions. Whenever you
install an application from the Marketplace, there is an area that says “this app requires these
permissions” and then lists those permissions:
These are things like ACCESS_FINE_LOCATION for checking the location using
GPS, CALL_PHONE for starting phone calls without going through the dialer, REBOOT to be able to
reboot the device, and many more. The one we’re interested in is INTERNET. All permissions are
defined in the manifest file. Open theAndroidManifest.xml file in the root of your
application. Permissions can be added anywhere within the manifestnode. For now we can just add
it in at the top:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.dayeighteen"
android:versionCode="1"
android:versionName="1.0" >
<uses-permission android:name="android.permission.INTERNET"/>
Now when users install your app, they would be able to see that your app requires internet
access. It’s important to note that this permission is also required to do any sort of backend HTTP
connections such as connected to a web service or doing a HTTP post.
Adding a WebView to your Layout
To use a WebView you need to add it to a layout. Open up the res/layout/second_layout.xml file
and remove the LinearLayout that is there right now. You could put the WebView in your layout with
other elements but for today, you’ll just use the WebView. For now, add a WebView to your XML
and set the layout_width andlayout_height to fill_parent. When you’re done, the layout XML
should look like this:
<?xml version="1.0" encoding="utf-8"?>
<WebView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/webView1"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
Now open src/com.dayeighteen/ActivityTwo.java. In the onCreate method after you’ve
called setContentViewand set it to the layout you just added the WebView to, you need to get a
reference to the WebView usingfindViewById. Then you can calll the loadUrl method on that and
pass in a web URL. Here’s the code for loading www.google.com:
WebView webView1 = (WebView) findViewById(R.id.webView1);
webView1.loadUrl("http://www.google.com");
Now when you run your app and tap the second Button it will launch ActivityTwo and after a
moment of loading, you should see Google appear:
That’s all you need to do to get a simple WebView in your app. There are a couple key things to
notice / remember about this. First, when the WebView first loads, there is no address bar or
navigation buttons. As soon as you click on a link or change the web page you’re on, you’ll notice
that your app title (DayEighteen in the image above) disappears. This is because the default
behavior of a WebView is to launch something that can handle URLs whenever you click a link or
change pages. In most cases this means the default browser will launch. If you back out of that
browser you’ll return to the WebView in your app. If you hit back from your WebView you’ll get back
to the calling Activity. You won’t notice it loading Google but if you were to change the URL that
you’re loading in your WebView to a page that uses JavaScript, you’d see that by default JavaScript
is not enabled. Let’s take a look at how to deal with some of these issues.
WebSettings for WebViews
WebViews have a set of settings attached to them called WebSettings. Once you’ve gotten a
reference to them, you can then alter those settings. Getting access is done via a call
to getSettings like this:
WebSettings settings = webView1.getSettings();
There are a mess of things you can configure using this variable including the following. What may
be the most important setting is enabling JavaScript which can be done via
the setJavaScriptEnabled method. Also important are setSaveFormData which enables you to
save user entered form data, setSavePassword which allows saving passwords users enter into web
sites, and setUserAgentString which allows you to pass a custom UserAgent over to any web sites
you visit. Go ahead and enable JavaScript for the WebView you created earlier. This is the code
you should end up with:
WebView webView1 = (WebView) findViewById(R.id.webView1);
WebSettings settings = webView1.getSettings();
settings.setJavaScriptEnabled(true);
webView1.loadUrl(http://www.google.com);
<script type="text/javascript">
function showToast(toast) {
Android.showToast(toast);
}
</script>
</body>
</html>
The input button is connected to the JavaScript showToast method via the onClick. Android is an
interface that the WebView has access to naturally. However, the showToast method that we’re
calling on the Android object is something we need to create. Go back to Package Explorer and
right click on the src/com.dayeighteen folder and choose New—>Class. Name your
class AppJavaScriptInterface and click Finish. Your class needs a constructor that will take in
an android.content.Context and sets a private variable so you will have access to that context
later. Now you just need to create a showToast method that matches up with the one we called
above from JavaScript. When you’re done, your class should resemble this:
public class AppJavaScriptInterface {
private Context context;
However, if you were to add a huge amount of text at the bottom, that might change. Open
yourres/values/strings.xml file and add a new String. Name it big_text and put a very huge text
String in there. Something like this:
<string name="big_text">Cow ... tongue</string>
In order to make your string suitably long, I’d recommend getting a few paragraphs from Bacon
Ipsum. Now open the res/layout/main.xml layout file. You need to add a new TextView beneath
the last button and set it to yourbig_text value. When you’re done you should have something like
this:
<TextView
android:id="@+id/lblTextViewBig"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/big_text" />
Now when you run your app, you’ll notice that the text goes off the screen and you don’t have anyway
to view it:
Here’s where you can use the ScrollView. Your first instinct may be to go back to your layout and
change theLinearLayout at the root to a ScrollView. If you do that, you’ll be greeted by this
unpleasant error:
Caused by: java.lang.IllegalStateException: ScrollView can host only one direct child
This means that you can’t have more than one View as a child of the ScrollView. Since your layout
has a coupleTextViews, an EditText, and a couple Buttons, you can’t just replace the
root LinearLayout like this. What you can do though is wrap the LinearLayout inside of
a ScrollView. This allows you to have a single child beneath the ScrollView but have multiple
children beneath that. When you’re done, your XML should look like this:
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:fillViewport="true" >
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<TextView
...
Now when you run your app, you’ll be able to tap on the screen and drag up:
Notice that when you scroll you’ll see a scrollbar on the right side of the screen. You’ll notice that you
can’t scroll right or left. This is because the ScrollView only supports vertical
scrolling. The HorizontalScrollView element will allow you to scroll horizontally. It is possible to put
a HorizontalScrollView in as the child of theVerticalScrollView which will allow you to scroll in both
directions, however, Google says this is a bad idea. Instead, they suggest you
override ScrollView and create your own control capable of both vertical and horizontal
scrolling. This is entirely possible and if you search the internet, you’ll find several examples of
people doing this and even the source code for their implementations. As I like to do, I’d remind you
to remember that just because you can do it, doesn’t mean you should. The experience when you
have to scroll in multiple directions on a small screen device isn’t a very good one (yes
the WebView and browser give you this capability and I’d argue whether I like that sometimes).
There are a number of different attributes you can use in a ScrollView including ones that let you
change how the scrollbars look, how long they’re visible, if they’re visible, etc. In addition, you can
control the scrolling of yourScrollView programmatically if you want to. For example, if you wanted
to add a Button that would trigger theScrollView to scroll to the top, this is possible by doing the
following:
ScrollView scrollViewOne = (ScrollView) findViewById(R.id.scrollViewOne);
scrollViewOne.scrollTo(0, 0);
Here you’re getting a reference to your ScrollView and telling it to scroll back to the top left
corner. Another important thing about the ScrollView is that it doesn’t have to be the root element in
your layout. If you wanted to have a text header at the top of the screen that your scroll view wouldn’t
encompass, you could just put theScrollView in as a child of the root LinearLayout. Here’s your
layout XML where the ScrollView is only wrapping the big_text TextView:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<TextView
android:id="@+id/lblTextViewOne"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello" />
...
<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button" />
<ScrollView
android:id="@+id/scrollViewOne"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<TextView
android:id="@+id/lblTextViewBig"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/big_text" />
</ScrollView>
</LinearLayout>
You should be ready to use ScrollViews in your own applications now. Tomorrow we’ll go over the
other method of displaying a lot of information in a single page, ListViews.
You can download the source code from today here.
31 Days of Android: Day 20–ListViews
Posted on: 12/10/2011 5:31:00 PM by Chris
<< Day 19: Scrollviews
Day 21: The Activity Lifecycle >>
If we did have URLs for each page, you could load it using a couple of different methods. You
could load up an Activity with a WebView in it like we did in Day 18. There is another method that
makes use of Intents that we will get into a few days from now.
Create your own row layout
So far you’ve used a built in layout, android.R.layout.simple_list_item_1, to display each row of
your ListView. There are a few different built in layouts available that you can use. For example, if
you changesimple_list_item_1 to test_list_item and rerun your application, you’ll see that the size
of the text and the rows has been decreased:
This is great and gives you a few options to easily work with. However, chances are good that you’re
going to want to have better control over how you display the information. To do this, right click on
the res/layout folder and choose New –> Other then select Android XML File. You can leave it with
a LinearLayout root element and name your new layout list_item. Let’s say you want to show an
icon on the left side of each row and then the text to the right of that. To do that, change
the LinearLayout to use a horizontal orientation and then add anImageView and a TextView. For
now, set the ImageView to the ic_launcher icon drawable and the TextView to use an ID
named text1. When you’re done, your XML should look like this:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:id="@+id/layout_item”
android:layout_height="match_parent">
<ImageView
android:id="@+id/imageView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_launcher" />
<TextView
android:id="@+id/text1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TextView" />
</LinearLayout>
Now go back to your DayTwentyActivity.java file where you’re creating your ArrayAdapter. Instead
of usingsimple_list_item_1 you need to use your new list_item layout. In addition, you need to
pass in the ID of theTextView that you want to use to display the text passed in the array. Your
new ArrayAdapter constructor and call to setListAdapter will look like this:
ArrayAdapter<String> listAdapter = new ArrayAdapter<String>(this,
R.layout.list_item, R.id.text1, values);
setListAdapter(listAdapter);
Now when you run your app, you’ll see the icon on the left of each row and the text right next to it:
With this knowledge you should be able to make some interesting looking ListViews.
Now when you run your app, you’ll have alternating rows:
Initially, all of the rows will be alternating like they were before. Now, when you scroll you’ll see rows
being the same color sometimes for multiple rows. The reason for this is that when you scroll the
view being passed in as the currentView is not necessarily going to be one that used the same
layout (since above you used different layouts instead of programmatically changing
colors). Unfortunately, there isn’t a way around this that I know of. The alternative would be to not
use the different layout resources and set colors in code. Let’s look at doing that now. Go back to
the getView method in your CustomArrayAdapter. You should leave the code in
forconvertView but take out the check on position and setting view to odd_list_item. Instead, after
you’ve checkedconvertView you should check the position and use that to set the layout’s
background color and text1’s text color. Your new getView will look like this:
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view;
if (convertView == null) {
view = inflater.inflate(R.layout.list_item, parent, false);
}
else
view = convertView;
TextView text1 = (TextView) view.findViewById(R.id.text1);
text1.setText(stringValues[position]);
LinearLayout layout_item = (LinearLayout)
view.findViewById(R.id.layout_item);
//Set the background and text color
if (position % 2 == 0) {
layout_item.setBackgroundColor(context.getResources().getColor(R.color.black));
text1.setTextColor(context.getResources().getColor(R.color.white));
} else {
layout_item.setBackgroundColor(context.getResources().getColor(R.color.white));
text1.setTextColor(context.getResources().getColor(R.color.black));
}
return view;
}
Now when you run your app you can scroll up and down all you want and the colors will be correct:
Each of the ovals (Activity starts, Activity is running, Activity is shut down, Process is killed)
represent important states that an activity can be in. More important, each of the rectangular boxes
(onCreate, onStart,onResume, onPause, onStop, onRecreate, onDestroy) represent methods
that are called on your application during the state flow. So far in this series, all of the logic we’ve
done for altering the layout or adding click listeners has been in the onCreate method. Looking at
these may be a little confusing so we’ll try to break it down:
onCreate-called once on activity creation.
onStart-called whenever the activity is started (partially visible to the user) and anytime the activity
is brought back into view after a call to onStop. Can be called multiple times.
onResume-called when the activity is in the forefront on top of all others. Can be called
multiple times.
onPause-called when the activity leaves the foreground but is still visible, or when the device
goes to sleep. Can be called multiple times.
onStop-called whenever the activity is hidden from view. Can be called multiple times.
onDestroy-the activity should release all of it’s resources and stop any background
proccessing. Only called once.
Hopefully this explains why most of the logic added and layout alteration is done in onCreate. That is
called once and the activity is kept in memory so you shouldn’t need to specify click listeners or
perform other onCreatecommon functionality elsewhere. In
comparison, onStart and onResume may be called multiple times. Furthermore, onResume will be
called the first time your activity becomes visible, even though that may not make sense considering
the name.
It’s important to know that after any of an activity’s onPause, onStop, or onDestroy methods are
called it is possible for the Dalvik Virtual Machine that runs all of the Android apps to kill your app
without calling into any of the other methods in the status flow. While it’s therefore important to
persist any necessary data in the onPausemethod, be warned that starting the next activity will wait
until the call to onPause is returned.
Another thing that may not make sense initially is that when the device is rotated, the Activity is killed
and recreated. This is really hard for some new Android devs to grasp so I’ll say it again. When the
device is rotated, the Activity is killed and recreated. If you think about the fact that different
layouts can be used for different orientation this starts to make more sense. The important take away
is that you need to make sure you test that changing orientation doesn’t screw up the state of
your Activity.
Saving and Restoring Activity State
Whenever an Activity is paused or stopped, it’s state is retained. This is done automatically and
something you don’t have to worry about. However, when an Activity is destroyed, things can be a
little different. Before onStopis called (and possible before onPause)
the Activitiy’s onSaveInstanceState method will be called. This method takes in a Bundle which
you can add data to. This Bundle is then sent in to the onCreate method. If, in
theonCreate method, the Bundle isn’t null then you know that the Activity is being
recreated. Android’s default implementation of onSaveInstanceState already handles a lot of the
persistence you might need. By default, it will call onSaveInstanceState on all of the views inside of
it. So, for example if you’ve typed something into anEditText and then rotate the device, that text will
appear after rotation as well. onSaveInstanceState saves the text value and then it gets restored
in onCreate. It’s important to note that in order for this to work your Views must have a unique ID.
If your Activity has member variables that you need to save when the Activity is destroyed, then you
would need to handle saving the state of these variables. This is one example of something that you
could handle inonSaveInstanceState. Let’s say your Activity has a member variable named
“Name” and you want to keep it’s value on recreation. You could add it to the Bundle like so:
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("MyString", this.Name);
}
Then in your onCreate method, you can check and see if you have a savedInstanceState and set
your variable if you do:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
this.Name = savedInstanceState.getString("MyString");
}
...
One final note about onSaveInstanceState is that it won’t be called if the Activity is killed as a result
of callingfinish() or the user hitting the back button. Since these are deliberate causes for
the Activity to be ended, there isn’t a reason to save the state. If you need to store any information
when this happens, you’ll have to do it manually.
Configuration changes
The reason that your Activity is recreated when the orientation changes is that it is a change in
theResources.Configuration class. Any changes to this class will trigger a recreation. Other
examples of things that change the Resources.Configuration class are sliding in or out a hardware
keyboard (changing the input device), changing mode (i.e. night mode, car mode, etc), and
more. The rotation and keyboard are likely to be the most common configuration changes you’ll need
to handle. It is possible to override these changes, per Activity, so that you aren’t restarted. This is
done by adding a configChanges to your Activity in the manifest file. For example, if you wanted to
prevent restart on rotation for an Activity named ActivityTwo, you’d do the following:
<activity android:name=".ActivityTwo"
android:configChanges="orientation"
android:label="@string/app_name"/>
Then in your Activity you would override the onConfigurationChanged method and just make it
specify the orientation you want to maintain:
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
So here you’re saying to keep a portrait orientation even when the orientation may have been
changed to landscape.
You should now have a good understanding of the Activity lifecycle now and when which event
method are called. One thing we didn’t cover is how to persist data long term so that you could load
an Activity after your application has been restarted and restore it’s state to the last time it was
used. We’ll talk more about how to do this sort of longer term persistence later in the series.
31 Days of Android: Day 22–Shared Preferences
Posted on: 12/12/2011 12:41:00 PM by Chris
<< Day 21: The Activity Lifecycle
Day 23: Writing and Reading Files >>
UPDATE: It looks like the code for today was accidentally labeled
DayTwentyOne instead of DayTwentyTwo. I'll go back and fix this as
soon as I have a chance.
Clearing SharedPreferences
If you want to give users the ability to clear any preferences you may have saved it’s easy to be
done. Once you get an instance of the Editor you just call clear() on it:
SharedPreferences settings = getSharedPreferences("MyPreferencesFileName", 0);
SharedPreferences.Editor preferencesEditor = settings.edit();
preferencesEditor.clear();
preferencesEditor.commit();
You still need to call commit() after you’ve cleared the preferences. Also, if you set any preferences
after you hitclear() but before you hit commit(), those will still be stored.
Removing a Preference
Instead of clearing all preferences, you also have the option to remove an individual one:
SharedPreferences settings = getSharedPreferences("MyPreferencesFileName", 0);
SharedPreferences.Editor preferencesEditor = settings.edit();
preferencesEditor.remove("OurText");
preferencesEditor.commit();
Note that again you need to call commit() after you’ve called remove.
A Couple Things to Note
There is no reason you can’t use multiple preference files in your application. In the examples above,
you passed in “MyPreferencesFileName” whenever getting a reference
to SharedPreferences. However, you could just as easily use different file names in different
activities or whatever you need. It might become complicated to use multiple preference file names
but it’s at your disposal.
When it comes to reading and writing preference files from other applications, you don’t need to do
anything differently. You just need to know the name of the preferences file that you need (and have)
access to.
One last and very important thing to know is that use’s have access to clear
the SharedPreferences even if you don’t give them access in your application. On your device, if
you go to Settings –> Applications –> Manage Applications –> Your App you’ll see a button to
“Clear Data”. Clicking this clears any data your app has saved, including SharedPreferences:
You can download the code we ended up with after today’s work here.
31 Days of Android: Day 23–Writing and Reading Files
Posted on: 12/13/2011 9:11:00 AM by Chris
<< Day 22: Shared Preferences
Day 24: SQLite Databases >>
UPDATE: It looks like the code for today was accidentally labeled DayTwentyTwo instead
DayTwentyThree. I'll go back and fix this as soon I have a chance.
It’s time to continue our sub-series on persistence as part of the 31 Days of Android. Yesterday we
went over Shared Preferences which is a method for persisting key value pairs. These can be only
readable by your application, or you can open them up to reading or writing by other apps. Today
we’re going to cover another method of persisting data, one that anyone that has programmed in the
past is probably familiar with: file storage. The Java language supports many different methods for
file access, and these can be used in Android as well. There are two methods of file storage we’ll
look at: internal storage and external storage. You can access our starter code here.
Once you’ve copied that file to your local computer and open it, you’ll see that whatever you had in
the EditTextwhen you tapped the button made it into the file.
You should now be capable of writing and reading files on your Android device in your
applications. Remember that users can see how much space your app is taking up in user
data. Furthermore they can clear it freely from the Manage Applications area of settings. Knowing
that, and that anything can access and change files on external storage, you should remember that
these files could be removed outside of your application.
Creating a Database
In order to create a database you need to override the SQLiteOpenHelper class and override
the onCreatemethod to handle the actual creation. Let’s start by adding a new class to your
project. Right click onsrc/com.daytwentyfour and go to New –> Class. Name your class
“DatabaseOpenHelper” and enterandroid.database.sqlite.SQLiteOpenHelper as the
superclass. When you create the class it will already have stubs for onCreate and onUpgrade for
you to implement. You will still need to add a constructor for your class though:
private final static String databaseName = "DayTwentyFourDb";
private final static int databaseVersion = 1;
The second option is slightly more complicated but important for something we’re going to do
later. Open up a command line window and navigate to your android SDK directory. For example,
mine is located at c:\Program files (x86)\Android\android-sdk. From there, you need to go into the
platform-tools folder so you can accessadb.exe. ADB can be used to connect to emulators and
devices from the command line. If you’re not sure what the device or emulator you want to connect to
is named, you can run adb devices. This should list out all currently connected devices and running
emulators. My emulator is showing up as emulator-5554. Next you need to run adb and connect to
the emulator with a shell like so:
$ adb –s emulator-5554 shell
After that, you’ll be running the command line ON the device. To check the same directory you did
above in File Explorer, execute this command:
# ls /data/data/com.daytwentyfour/databases
Doing so should yield a single file, DayTwentyFourDb. The reason I showed you the arguably more
complicated command line method is that later, after you’ve inserted data, you can open and check
the database via this command line where as with File Explorer, you can’t.
Dealing with CRUD
Now that you’ve checked and made sure your database is being created successfully, it’s time to look
at some CRUD (Create, Read, Update, Delete). Open the DatabaseAdapter class you created and
add the following:
public long createRecord(String text) {
ContentValues contentValue = new ContentValues();
contentValue.put("name", text);
return database.insert("DayTwentyFour", null, contentValue);
}
ContentValues implements Parcelable which you may have dealt with for sending data between
activities. Here you’re just putting a text value into the ContentValues that matches the name of the
only column you put in the database, outside the auto incremented ID column. After that, you use
the SQLiteDatabase class to do theinsert. Go back to the DayTwentyFourActivity class and
change your button1 onClickListener to do the following:
button1.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
lblTextViewOne.setText(editText1.getText());
DatabaseAdapter databaseAdapter = new
DatabaseAdapter(getApplicationContext());
databaseAdapter.open();
databaseAdapter.createRecord(editText1.getText().toString());
databaseAdapter.close();
}
});
The only thing different here from what you were doing before to create the database, is the call to
the adapter to create a record. Now go ahead and run your app. When you tap on the button, you
won’t see anything, but the data should insert behind the scenes. To check and see that data was
inserted, you need to return to the command line. Provided you are still “ADBed” into your emulator,
you can enter the following to get into the SQLite database:
sqlite3 /data/data/com.daytwentyfour/databases/DayTwentyFourDb
Now there are many different commands you can use here (run .help to have them
listed). Running .tables will give you a list of tables that are in your database. .dump
<tablename> will output the contents of the table. Go ahead and run:
.dump DayTwentyFour
You should get the following out (you might have fewer statements at the bottom depending on how
many times you tapped the button):
sqlite> .tables
.tables
DayTwentyFour android_metadata
sqlite> .dump DayTwentyFour
.dump DayTwentyFour
PRAGMA foreign_keys=OFF;
BEGIN TRANSACTION;
CREATE TABLE DayTwentyFour (_id integer primary key autoincrement, name text not
null);
INSERT INTO "DayTwentyFour" VALUES(1,'This was our DayTwentyFour test.');
INSERT INTO "DayTwentyFour" VALUES(2,'This was our DatyFour test.');
INSERT INTO "DayTwentyFour" VALUES(3,'This was our DatyFour test.');
INSERT INTO "DayTwentyFour" VALUES(4,'This was our DatyFour test.');
INSERT INTO "DayTwentyFour" VALUES(5,'This was our DatyFour test.');
INSERT INTO "DayTwentyFour" VALUES(6,'This was our DatyFour test.');
INSERT INTO "DayTwentyFour" VALUES(7,'This was our DatyFour test.');
INSERT INTO "DayTwentyFour" VALUES(8,'This was our DatyFour test.');
INSERT INTO "DayTwentyFour" VALUES(9,'This was our DatyFour test.');
INSERT INTO "DayTwentyFour" VALUES(10,'This was our DatyFour test.');
Now you know that you’re inserting successfully. Let’s take a look at how to pull data out.
public ArrayList<String> fetchAllRecords() {
Cursor cursor = database.query("DayTwentyFour", new String[] { "_id",
"name"},
null, null, null, null, null);
ArrayList<String> records = new ArrayList<String>();
cursor.moveToFirst();
for (int i = 0; i < cursor.getCount(); i++) {
records.add(cursor.getString(1));
cursor.moveToNext();
}
cursor.close();
return records;
}
Here you’re doing a database query and returning it’s results to a Cursor. The query takes in the
table name and the column names. There are additional parameters that you can pass in to specify
order, selection arguments, as well as a record number limit. Here you’re fetching all of the record
and putting them into an array which is returned. Go ahead and call this method from the second
button onClickListener in DayTwentyFourActivityand set the EditText and TextView to the first
row returned. You should end up with something like this:
button2.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
DatabaseAdapter databaseAdapter = new
DatabaseAdapter(getApplicationContext());
databaseAdapter.open();
ArrayList<String> records = databaseAdapter.fetchAllRecords();
if (records.size() > 0) {
editText1.setText(records.get(0));
lblTextViewOne.setText(records.get(0));
}
databaseAdapter.close();
}
});
You’re checking to make sure your database at least had a record before you use it, but that should
be it. Now if you run your app and tap the second button, the text of your two Views should be set.
Very quickly we’ll go over the other CRUD methods you might use.
Fetching a Specific Record
Fetching a specific record is very similar to the method above where you’re fetching all records. The
difference is that one of the nulls passed into the query method is replaced with selection criteria:
public String fetchRecord(long rowId) throws SQLException {
Cursor mCursor = database.query(true, "DayTwentyFour", new String[] { "_id",
"name" }, "_id ="+ rowId, null, null, null, null, null);
if (mCursor != null) {
mCursor.moveToFirst();
return (mCursor.getString(1));
}
return null;
}
Updating a Record
Updating a record is very similar to the insertion but, again, you’re specifying criteria:
public boolean updateRecord(long rowId, String text) {
ContentValues contentValue = new ContentValues();
contentValue.put("name", text);
return database.update("DayTwentyFour", contentValue, "_id =" + rowId, null)
> 0;
}
Deleting Records
Deleting records is very simple as well, just more criteria:
public boolean deleteRecord(long rowId) {
return database.delete("DayTwentyFour", "_id =" + rowId, null) > 0;
}
This should get you pretty far with using databases. An important reminder is that if the user goes
into Settings –> Manage Applications and pulls up your application and hits the Clear Data button,
you’re database WILL be deleted.
What about Upgrades
Earlier on I covered the onUpgrade method of your DatabaseOpenHelper class very quickly. This
method is actually very important if you’re working on an application that you anticipate making
database changes on. The way that Android handles database changes is that it expects you to do a
lot of the work. As you’ll recall, you created a databaseVersion variable in
your DatabaseOpenHelper. If you increase this number and release an updated version of your
app, the onUpgrade method will be called. Where previously you were just dropping the table and
calling on create, here you’d need to handle retrieving all of the data for the table, recreating the
table, and then reinserting the data. Something a lot like this:
@Override
public void onUpgrade(SQLiteDatabase sqLiteDB, int oldVersion, int newVersion) {
//Pull all of your data and keep it in a local variable
sqLiteDB.execSQL("DROP TABLE IF EXISTS DayTwentyFour");
onCreate(sqLiteDB);
//Insert that data you pulled out earlier into the new table
}
You can view an example of this implemented by a real app here.
Lastly, you can download the final source code from today here.
31 Days of Android: Day 25–Content Providers
Posted on: 12/15/2011 10:29:00 AM by Chris
<< Day 24: SQLite Databases
Day 26: Intents >>
When you’re done, tap Done to save the contact. Create at least two contacts before
proceeding. Note that after you hit Done, you may not see your contacts on the emulator. If you’re
not, tap Menu and choose Display Options. Then at the bottom under Choose contacts to
display, expand your account and ensure All Accountsis checked.
Getting at the Data
If you’re experienced with using a SQL database (like SQLite for Android) then accessing the data
through aContent Provider shouldn’t seem that weird. You have to execute a query against a
specific table, can specify which columns you want returned, which selection criteria (WHERE clause)
you want to use, and how you want the results ordered. In this case, instead of listing a table name,
you instead pass a URI. The built in providers define constants that you can use to specify these
URIs. These are some examples of the predefined constants:
android.provider.ContactsContract.Contacts.ContentURI
android.provider.MediaStore.Audio.EXTERNAL_CONTENT_URI
android.provider.CalendarContract.Events.CONTENT_URI
The first provider is for Contacts. The second to last is the URI for externally stored audio
media. The last one is new from Android 4.0 and is the URI for calendar events. Again, the URI
specifies the source of the information you’re accessing. Let’s take a look at how to pull out all of the
contacts you’ve saved in your app. Open
thesrc/com.daytwentyfive/DayTwentyFiveActivity.java class and
find button1’s onClickListener. Here you can add the code to query the contacts. For now you can
just pull all rows back and not pass any selection criteria. We’ll deal with those later. After you pull
the data, you should loop through it and join the names together:
button1.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
Uri contactsUri = ContactsContract.Contacts.CONTENT_URI;
Cursor cursor = managedQuery(contactsUri,
null,
null,
null,
null);
if (cursor != null && cursor.getCount() > 0) {
StringBuilder sb = new StringBuilder();
while (cursor.moveToNext()) {
sb.append("Name-");
sb.append(cursor.getString(cursor.getColumnIndex(
ContactsContract.Contacts.DISPLAY_NAME)));
sb.append("\n");
}
lblTextViewOne.setText(sb.toString());
}
else {
lblTextViewOne.setText("Couldn't find any contacts");
}
}
});
First you’re getting a reference the Contacts URI. Then you’re doing a managed query which just
passes in the URI. Since nothing else is specified, it will pull back all of the contacts and will use
whatever default order there is. After that, provided some contacts are found, you’re looping through
them and pulling out the DISPLAY_NAMEfor each. The managedQuery method you are using is
specific to the Activity class. Calling it means that theActivity that it is called from will handle the life
cycle of the Cursor (unloading and reloading when necessary). Now when you run your app and tap
the button, your app will crash. If you look in LogCat, you’ll see the following error:
java.lang.SecurityException: Permission Denial: reading com.android.
providers.contacts.ContactsProvider2 uri
content://com.android.contacts/contacts from pid=5529, uid=10055
requires android.permission.READ_CONTACTS
Getting access to contacts REQUIRES you to get permission. Open your
app’s AndroidManifest.xml file and add the READ_CONTACTS permission to it:
<uses-permission android:name="android.permission.READ_CONTACTS"/>
Now when you run your app and tap the button you should see the names of the contacts put into
the TextView at the top of the screen:
Uri.parse("geo:0,0?q=1600+Amphitheatre+Parkway,+Mountain+View,+CA+94043"));
startActivity(myIntent);
Intent.ACTION_VIEW is used again to open the maps. This time though, you’re passing in the
address as the Uri. This should demonstrate how specific apps can be with what Intents they are
capable of dealing with. Map programs are set to handle addresses and geo coordinates. Browsers
are used for URLs. Furthermore, an application can specify URLs that it can handle as well. As an
example, if you have the Yelp app installed and fire an intent to view a restaurant at the Yelp web
page, Android will ask if you want to view it in the browser or the Yelp app. This is another way that
Android makes it possible to interconnect apps without having to go back and change older
applications.
if (intent.getStringExtra("MyStringValue") != null)
textView1.setText(intent.getStringExtra("MyStringValue"));
else if (intent.getAction().equals(Intent.ACTION_SEND)) {
textView1.setText("Created by sharing! And text is " +
intent.getStringExtra(android.content.Intent.EXTRA_TEXT));
}
Now your ActivityTwo is capable of being created within your app and having MyStringValue sent
into it, or it can be created via another application sharing to it. If you do decide to create an Intent
Filter in your app, make sure you understand if other people are using it before you go and remove
it. If someone else designs an application that makes use of your app via intent, it’s poor etiquette to
go and break their functionality by altering or removing your filter.
PackageManager pm = getPackageManager();
List<ResolveInfo> list = pm.queryIntentActivities(marketIntent,
PackageManager.MATCH_DEFAULT_ONLY);
if (list.size() > 0)
startActivity(marketIntent);
else {
Toast.makeText(getApplicationContext(),
"Sorry the market is not installed",
Toast.LENGTH_LONG).show();
}
}
});
Now when you run your app on a device (with the Marketplace) it will still load the market like you
want (you’re still calling startActivity) but if you run it on an emulator, it will show a toast instead:
Now you should be able to use this to gracefully handle any app’s not being installed that you want to
launch from an Intent. Remember that you’ll never know for sure if the device running your
application is capable of handling an Intent so it’s always better to check than to assume.
I'm a little short for time today so we'll continue with, and wrap up, our talk about Intents, tomorrow.
31 Days of Android: Day 28–Intents Part 3: Service Intents
Posted on: 12/18/2011 1:30:00 PM by Chris
<< Day 27: Intents Part 2
Day 29: The Camera >>
Yesterday I was short on time and wasn’t able to complete the talk about
Intents as I had hoped which means that today we’ll wrap up talking
about them as we continue on with the 31 Days of Android. The last
thing we’re going to talk about is using Intents to start services and then
updating your UI when those services finish.
Using Services and Intents
The second topic I’d like to cover today that is related to Intents is
creating background services that are capable of updating your UI. You’ll
see that the service will be kicked off by sending an Intent. Your service
will update the UI via another messaging method called
a ResultReceiver. There are several steps you’ll have to go through to make this work so let’s get
started. First, right click on src/com.daytwentysevenand choose New –> Class. Name your class
“DayTwentySevenService” and set it’s superclass to “android.app.IntentService”. This will create a
new class with a onHandleIntent method for you to implement. In order for this to compile you’ll need
to at least add a constructor which should call the super constructor:
public class DayTwentySevenService extends IntentService {
public DayTwentySevenService() {
super("DayTwentySevenService");
}
@Override
protected void onHandleIntent(Intent arg0) {
// TODO Auto-generated method stub
}
}
Note that the super constructor expects a String argument to be passed in which is the name of the
service. It’s only used for debugging purposes but it may be something you want to define in a
constant for the class. There are a number of additional member variables you’ll want to add to your
class:
// Status Constants
public static final int STATUS_RUNNING = 0x1;
public static final int STATUS_FINISHED = 0x2;
public static final int STATUS_SUCCESS = 0x3;
public static final int STATUS_ERROR = 0x4;
// Command Constants
public static final int PERFORM_SERVICE_ACTIVITY = 0x5;
this.receiver.send(STATUS_RUNNING, Bundle.EMPTY);
switch (command) {
case PERFORM_SERVICE_ACTIVITY:
doServiceStuff(intent);
break;
default:
receiver.send(STATUS_FINISHED, Bundle.EMPTY);
}
this.stopSelf();
}
Here you’re setting your ResultReceiver to a value passed in via the intent object. Then you’re
getting thecommand from the same intent. Then you’re sending a message back to
the receiver telling it that your service is running. Finally, provided the correct command was sent in,
you’ll perform whatever operations the service is meant to do via the doServiceStuff method:
private void doServiceStuff(Intent intent) {
for (int i = 0; i < 100000; i++) {
String s = "This " + "is " + "a " + "test";
}
if (false) { // error
receiver.send(STATUS_ERROR, Bundle.EMPTY);
this.stopSelf();
receiver.send(STATUS_FINISHED, Bundle.EMPTY);
} else {
Bundle b = new Bundle();
b.putBoolean(SERVICE_WAS_SUCCESS_KEY, true);
receiver.send(STATUS_SUCCESS, b);
this.stopSelf();
receiver.send(STATUS_FINISHED, Bundle.EMPTY);
}
}
This is where you WOULD do whatever processing you needed to do. Instead, you’re just going
through a loop to kill some time so the response isn’t sent back right away. Examples of common
stuff you might do here is hit a web service, do extensive backend processing, or anything else that
you wouldn’t want tying up the UI thread. Once that is done, if everything was a success, you’ll send
a message to receiver saying you things were a success and then finished or if there was a failure,
you can send back that there was an error and that the service is finished. Now before you can start
making changes to your DayTwentySevenActivity to start the service and handle results, you’ll need
to create one more class. Right click on src/com.daytwentyseven and go to New –-> Class. Name
your class ServiceResultReceiver and set it’s subclass to android.os.ResultReceiver. This class is
going to be generic and could be used for any class that you want to receive service results. Let’s
look at the code:
public class ServiceResultReceiver extends ResultReceiver {
private Receiver mReceiver;
@Override
protected void onReceiveResult(int resultCode, Bundle resultBundle) {
if (mReceiver != null) {
mReceiver.onReceiveResult(resultCode, resultBundle);
}
}
}
As you can see, you haven’t put any code in this class that is specific to the service you created or
any activities in your project. Indeed, you could reuse this class in the exact same format across your
projects. Now open thesrc/com.daytwentyseven/DayTwentySevenActivity.java class and make it
implementServiceResultReceiver.Receiver:
public class DayTwentySevenActivity extends Activity implements
ServiceResultReceiver.Receiver {
In order to properly implement the ServiceResultReceiver, you’ll need to implement
the onReceiveResultmethod:
public void onReceiveResult(int resultCode, Bundle resultBundle) {
switch (resultCode) {
case DayTwentySevenService.STATUS_RUNNING:
// Don't do anything, the service is running
break;
case DayTwentySevenService.STATUS_SUCCESS:
boolean wasSuccess = resultBundle
.getBoolean(DayTwentySevenService.SERVICE_WAS_SUCCESS_KEY);
if (wasSuccess) {
Toast.makeText(getApplicationContext(),
"The service was a success", Toast.LENGTH_LONG).show();
} else {
// Show not success message
Toast.makeText(getApplicationContext(),
"The service was a failure", Toast.LENGTH_LONG).show();
}
break;
case DayTwentySevenService.STATUS_FINISHED:
Toast.makeText(getApplicationContext(), "The service was finished",
Toast.LENGTH_LONG).show();
break;
case DayTwentySevenService.STATUS_ERROR:
Toast.makeText(getApplicationContext(), "The service had an error",
Toast.LENGTH_LONG).show();
break;
}
}
Here you’re checking the result code sent from the service and depending on which constant in
theDayTwentySevenService class it matches, you’re showing a Toast depending on whether it’s
running, was successful, was a failure, had an error, or was finished. You could do any UI changes
you want here. For example, if your service had pulled data down from the internet, you could now
display it in your Activity. The last thing you need to address is starting the service. To start, add a
private variable to your class for yourServiceResultReceiver:
private ServiceResultReceiver receiver;
Then in the onCreate method you need to set your receiver variable:
// Set our receiver
receiver = new ServiceResultReceiver(new Handler());
receiver.setReceiver(this);
Next, you need to start the service. This is done by sending an intent. Let’s add this in for
the button2 onClickListener:
button2.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
final Intent serviceIntent = new Intent(Intent.ACTION_SYNC, null,
getApplicationContext(), DayTwentySevenService.class);
// put the specifics for the submission service commands
serviceIntent.putExtra(DayTwentySevenService.RECEIVER_KEY, receiver);
serviceIntent.putExtra(DayTwentySevenService.COMMAND_KEY,
DayTwentySevenService.PERFORM_SERVICE_ACTIVITY);
//Start the service
startService(serviceIntent);
}
});
Here you’re making more use of those constants you defined in the DayTwentySevenService class
to put the receiver and command keys in as extras sent through the intent. This isn’t all that different
from any of the other intents you’ve used. Now if you run your application and tap on the second
button you won’t see anything happen. If you check LogCat you’ll see a message that looks like this:
Unable to start service Intent { act=android.intent.action.SYNC
cmp=com.daytwentyseven/.DayTwentySevenService (has extras) }: not found
The problem here is that the Android OS hasn’t been told about your service. To do this, you need to
add the service into your manifest file:
<service android:name=".DayTwentySevenService"/>
Now when you run your app and tap on the second button, after a few seconds you should see
the Toast appear saying it’s been a success:
Again, this demo is very simplified but you could do anything you want here. Commonly you might
pull back data from a network resource in your service and then once it’s received you would use this
technique to update the UI with your data.
You can download the final working code from today (and yesterday) here.
31 Days of Android: Day 29–Using the Camera
Posted on: 12/19/2011 10:34:00 AM by Chris
<< Day 28: Service Intents
Day 30: Advertisements >>
Today we’re going to talk about using the camera in your applications as
we get close to wrapping up the 31 Days of Android. There are two
different ways you can use the camera in your applications: via
an Intent or directly using the camera hardware. If you use an Intent,
you’re relying on a different application to handle taking the picture or
video and “handing it back” to your app. Today we’ll look at how you can
get pictures from the camera with anIntent as well as how to get some
more information from the camera. You candownload the starter code
we’ll use today here.
Manifest Changes
Before you can use the camera, you need to add a permission to the manifest file. This will be listed
when users go to install your application so that they will know your app will use their camera.
<uses-permission android:name="android.permission.CAMERA" />
Technically this is the only change you need to make to your manifest. However, you can also
specify that the camera is a feature of your app by adding this to your manifest file:
<uses-feature android:name="android.hardware.camera" android:required="false" />
Note that at the end you’re saying that the camera isn’t required to install your app. If you set this to
true or remove it, then users wont’ be able to install your app if they don’t have a camera. Today I
think all phone and tablets that come out come with cameras though it’s entirely possible devices
without cameras could access theAndroid Marketplace (i.e. Google TV boxes) so if you’re building
an application that completely depends on having a camera, you may want to require it in your
manifest file. If you aren’t going to require a camera but want to check to see if a camera is available
you can use the PackageManager to find out:
PackageManager packageManager = getPackageManager();
boolean doesHaveCamera =
packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA);
If hasSystemFeature returns false than you know the device doesn’t have a camera
available. While not necessary for camera usage, you should also add the permission for external
storage to your manifest file:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
Later on you’ll write your images to external storage so it’s easier to take care of this now.
If you close down your application and run the gallery application, you’ll see that the images you took
for your app are there. This means that it’s storing images in the default location. If you want to, you
can specify a place to put the images you capture. This is done by putting an extra on your image
capture Intent before you send it:
Intent intent = new Intent(
MediaStore.ACTION_IMAGE_CAPTURE);
// Get our fileURI
Uri fileUri = getOutputMediaFile();
intent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri);
startActivityForResult(intent, 100);
The fileURI you are passing in is created from a File you create on the disk. You could write these
files to any location that you have access to. If you wanted to keep them private to your application,
you could pass in a URI for your app’s data directory. If you wanted to store them externally on the
SD card, you could do something like this:
private Uri getOutputMediaFile() throws IOException {
File mediaStorageDir = new File(
Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES),"DayTwentyNine");
// Create a media file name
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss")
.format(new Date());
File mediaFile;
mediaFile = new File(mediaStorageDir.getPath() + File.separator
+ "IMG_" + timeStamp + ".jpg");
if (mediaFile.exists() == false) {
mediaFile.getParentFile().mkdirs();
mediaFile.createNewFile();
}
return Uri.fromFile(mediaFile);
}
Here you’re creating a file in the SD card’s pictures directory, under a subfolder named
“DayTwentyNine”. The name of the file is “IMG_” followed by the timestamp. Now if you run your
app, it’s going to crash when it returns from taking the picture. The reason is that when you pass in
the EXTRA_OUTPUT extra, the camera doesn’t return the image data because it knows you know
where it’s going to write the file to. Once the camera returns, inonActivityResult you can check to
make sure it was a success and then do whatever you need with the picture. If you wanted to be able
to handle the picture being returned or not being returned, your onActivityResultmethod might look
like this:
@Override
protected void onActivityResult(int requestCode, int resultCode,
Intent intent) {
if (requestCode == 100) {
if (resultCode == RESULT_OK) {
if (intent == null) {
// The picture was taken but not returned
Toast.makeText(
getApplicationContext(),
"The picture was taken and is located here: "
+ fileUri.toString(), Toast.LENGTH_LONG)
.show();
} else {
// The picture was returned
Bundle extras = intent.getExtras();
ImageView imageView1 = (ImageView) findViewById(R.id.imageView1);
imageView1.setImageBitmap((Bitmap) extras.get("data"));
}
}
}
}
Here you’re still setting the ImageView if the data is returned, otherwise you’re just showing
a Toast with the file location. You could alternatively open that file and display it in the ImageView if
you wanted.
Checking Megapixels
One scenario I’ve found myself in at work that took some research to figure out was knowing how
many megapixels the camera was. It seems like there should be an easy to use built-in way to get a
rating on the camera on the device but, as of yet, I haven’t found anything. Thankfully it’s not very
difficult to calculate how many megapixels the camera supports. First you need to make a connection
to the camera hardware, and then you can use the getSupportedPictureSizes method to get a list of
supported picture sizes. This gives you a list of heights and widths which you can multiply to get
picture resolutions in pixels. You can use this number to find megapixels. This code will go through
these lists and round up or down appropriately to get megapixels:
ArrayList<Integer> supportedMegaPixels;
Camera cam = Camera.open();
camParameters = cam.getParameters();
pictureSizes = camParameters.getSupportedPictureSizes();
if (pictureSizes.size() > 0) {
supportedMegaPixels = new ArrayList<Integer>();
for (int i = 0; i < pictureSizes.size(); i++) {
supportedMegaPixels.add(((pictureSizes.get(i).height *
pictureSizes.get(i).width) + 1000000 / 2) / 1000000);
}
}
cam.release();
You can then check the supportedMegaPixels ArrayList to see the maximum number of megapixels
as well as to see if a specific resolution was supported. For example, here’s a method that will check
to see if the camera supports 5 megapixels:
public static boolean doesSupport5MP() {
return (supportedMegaPixels.contains(5));
}
I do have a few words of caution. First, if your device has a front facing camera as well, it will return
the supported picture sizes of that camera along with the picture sizes for the (typically) higher quality
rear camera. Secondly, if you’re using the camera and calling Camera.open() make sure you
call release on the camera object when you’re done. If you open the camera and then try to fire
the ACTION_IMAGE_CAPTURE intent, the camera won’t work.
If you need more control over the camera or want to make your own image capture program, you’ll
want to directly connect with the camera hardware. We won’t go over that today but you can read
more about it on the Android site (and maybe I’ll go over it in the future).
You can download the code we ended up with today here.
31 Days of Android: Day 30–Advertisements
Posted on: 12/20/2011 10:09:00 AM by Chris
<< Day 29: The Camera
Day 31: The Marketplace >>
We’re getting quite close to the end of the 31 Days of Android so it’s time
to talk about how to make some money with your apps. There are
several ways to make money with your applications. You could just
charge for the sale of your app. Many developers do this and put out a
free version with limited features to get users interested in paying for the
full version. Another way to make money is through in-app
purchases. This seems to be more common in games where you can
charge users to get special perks or abilities for their in-game
character. Another way to make money, and the focus of today’s article,
is in-app advertisements. One quick note is that you NEED your project
to target Android 3.2 or later. While previous projects targeted 2.2, the sample code today targets
4.0. You can download the starter code we’ll use today here.
request.addTestDevice(AdRequest.TEST_EMULATOR);
request.addTestDevice("015ED03511005018");
Different AD Sizes
In the AD you’ve put into your project, you’ve set the size to be BANNER. This Ad size is 320 x
50. In portrait mode, the Ad should stretch across the screen. However, if you rotate to landscape,
the Ad will be centered and only take up part of the screen:
There are other Ad sizes which are banner specific. IAB_MRECT is a medium rectangle and is sized
at 300 x 250. IAB_BANNER is a full size banner and is 468 x 60. IAB_LEADERBOARD is 728 x
90.
Sadly, we’ve come to the end of the 31 Days of Android. If you’ve been
paying attention so far, you should be capable of making some fairly
sophisticated applications. Now it’s finally time to take the roast out of
the oven and move your apps into the Android Marketplace. You
certainly don’t have to do this. Due to Android’s loose restrictions on
installing new app’s, you could distribute your app yourself or even use
one of the competing app stores. To my knowledge though, the Android
Marketplace is the best place to put your applications and the one app
store most highly used among Android owners. There isn’t any sample
code today as there isn’t anything you need to add on the code side once
your application is ready, though there are some things you may need to change.
After a moment the window should close and you should see your .apk file in the directory you
chose. Congrats, you’re done in Eclipse (until your next update).
Provided you’re happy, hit Save. Next, you’ll be able to upload assets and specify listing
details. Some of this is required and MUST be added before you can save. These required things
include: Screenshots, a High Resolution Application Icon, a Title, a Description, a Category, and a
Content Rating. If you’re just trying to get your app into the Marketplace and you don’t care about
getting featured or presenting your app as being professional, go ahead and just fill out the info so
you can get to the Save point. If you do desire to have your app be featured and you want people to
get to your app in the Marketplace, pay a lot of attention to what you use for images and make sure
they are in the correct format. Furthermore, don’t treat the optional images and promotional video as
optional. Google really looks at these things when deciding what applications to feature.
Congratulations, you’re finally ready to start putting your applications out for people to use. Good
night, and good luck.
31 Days of Android–Postmortem and What’s Next
Posted on: 12/28/2011 11:19:00 PM by Chris
<< Day 31: The Marketplace
It’s been just over a week since I wrapped up the last day of the 31 Days
of Android. This was a really fun series to work on but it took a lot out of
me. Today I’d like to talk about some of the lessons learned during and
after writing the series. These are tips that might be useful to you if
you’re planning a long running series and are certainly things I’d like to
remember going forward. Unlike the rest of the series, there won’t be
any coding in this article.
Don‘t Overcommit
Naming a blog series “31 Days of Android” and starting to post an article
every day, kind of implies that you’re going to post an article every day for 31 days straight. Not 7 for
the first 7 days and then 1 every other day for the next 48 days. It may not seem like it due to the
quality of some of the articles (I can admit that many of them could be better) but each one takes a
while to write. While some of them took a few hours, most of them took much longer. If you’re going
to try to get one out every day, make sure you’ve got the time to commit to it or plan ahead.
Plan Ahead
If you’re going to get an article out every day and you know, or even suspect, that it’s going to take a
while to write each one, write as many as you can ahead of time. I didn’t start the series until I had
written rough drafts for about 8 articles. However, shortly after I started posting, I lost my 8 article
head start. I think the big risk on not posting an article as soon as you’re done writing it is that it
becomes irrelevant. This could have happened with my first entry which detailed out to set up and
install your development environment. I’m sure it will only be a matter of time before Google releases
a new version of the ADK that slightly alters the installation and setup steps. However, I think the risk
is worth it to make sure you’re not putting too much work on your shoulders every night to get an
article out each day. If you don’t plan ahead, you’ll need to make a decision about how much time
you can devote to your writing.
In the End
While this series took quite a toll on me, it was totally worth writing. I feel like I’ve learned a lot about
both Android and blogging. I’m much more capable of explaining some of the concepts of Android
now and that’s already coming in handy at work. Furthermore, I think future articles I write will benefit
from this experience. I don’t know if I’ll write a “Days of” series again (and if it is, I don’t think it will be
31!), but I hope it will end up being a better experience having learned the lessons above.
What‘s Next
The series ended just over a week ago and I’ve taken a much needed break from writing anything
since then. I haven’t, however, taken a break from thinking about stuff I’d like to write about. At the
beginning of November, I wrote an article about some thoughts on Siri and how Google could
probably roll out a Siri like product. Shortly after that I had a quick Google Plus talk with a person in
Developer Relations at Google about how they’d like to control their music with their voice. This was
already somewhat possible using Voice Actions, but there were some pieces missing. I got motivated
and wrote an Android app to handle it. Over the next couple of months I’m planning on open sourcing
that program and writing some articles about how it works. Hopefully this will enable other
developers to more easily get voice control into their own apps. In addition to that, there were a few
articles I had to cut out of the 31 Days series (dialogs, maps, animations) that I would still like to write
about. Some of these are stubs I’ve already created and should be able to get out soon(ish). Others
will probably come later. Lastly, I’ve got a few more personal things to talk about (more beer
brewing and chromebook experiences). If you’re finishing up reading the 31 Days of Android and
have thoughts, feedback, or requests for other articles on Android development, let me know.