Update note: This tutorial was updated for the latest version of Android Studio by Darryl Bayliss. Original tutorial by Matt Luedke.
This tutorial is the second of three parts. If you’re looking to start from scratch, Part One is the tutorial for you!
The first part of this series covered a lot of zoomed-out Android concepts, as well as the general structure of Android projects. In this part of the tutorial, you’ll learn more “on the ground” Android: how a layout file works and the specifics of Android layouts and views.
By the time you’re done with this section, you’ll have an app with:
- An image from a PNG file;
- An editable text field for writing messages;
- A button to submit your input;
- A text view that shows your most recent message;
- A list that displays all your messages;
- An option to share your message through Facebook, Twitter, SMS or email; and
- A greeting that saves and retrieves your name each time you open the app.
You should already be at the point where you have a “Hello World” app running on your emulator or device. At the end of the last section, you changed the text so that it greets you personally, like mine:
It’s great that you’ve come this far — but now it’s time to take it to the next level! There’s a lot to do, so let’s get to it!
Getting Started
Looking ahead, the first thing you should do is check that you’re making your app as simple as possible. You don’t want to introduce additional complexity unless it’s needed since extra complexity in how something is implemented means that it takes more time and requires more work if you were to later modify the bits with the extra complexity.
First, open app/src/main/res/layout/activity_my.xml. After opening the file, you may have to switch the editor to Text mode if you can’t see the raw XML. Click the appropriate tab at the bottom of the editor pane as shown below.
The only thing you need to remove here are the padding attributes that Android Studio automatically generates in your .xml layout. They look something like this (your values may vary slightly):
android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" android:paddingBottom="@dimen/activity_vertical_margin" |
Delete these lines from your layout, your activity_my.xml file should now look like this:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MyActivity"> <TextView android:text="@string/hello_world" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </RelativeLayout> |
Now, double-click on MyActivity.java on the left pane of Android Studio to take a look at your first piece of Android code.
The only lines you need to remove here are the following:
@Override public boolean onOptionsItemSelected(MenuItem item) { // Handle action bar item clicks here. The action bar will // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); if (id == R.id.action_settings) { return true; } return super.onOptionsItemSelected(item); } |
Be careful not to remove the extra curly brace at the bottom that closes the end of your class.
Now that you’ve finished that bit of cleaning up, you’re all set to begin. It’s time to bring your Activity
to life!
XML Layout Basics
Android layouts are in XML format, in the form of a tree with a single root and a hierarchy of views. The hierarchy is very strict and straightforward: each view in the tree is termed the parent of the views it contains and the child of the view that contains it.
Open app/src/main/res/layout/activity_my.xml. You can see the XML for your activity here. There is a parent RelativeLayout
and a child TextView
.
Look at the TextView
specifically. You can see that it contains three attributes. Of those, two are present in every view you’ll ever put into your Android layouts: layout_width
and layout_height
. The values for these attributes can take several forms:
wrap_content
: This constant value specifies that the view will be just large enough to fit whatever is inside it, whether that’s an image, text or child view.match_parent
: This constant sets the view to be as big as its parent.- Explicit values: You could set the dimension to a specific number of pixels (Ex:
5px
), but it is usually wiser to use density independent pixels (Ex:5dp
). Adp
is a pixel on a “medium-density” (mdpi
) device, and the number of actual pixels automatically scales for devices designated as low-density (ldpi
), high-density (hdpi
), extra-high-density (xhdpi
), etc.
In other words, using straight-up pixels would result in your views being all sorts of crazy sizes, depending on whether a device has 160 pixels per inch or 300 pixels per inch, or what have you. Who knows! Let the Android system take care of the scaling and just use dp
.
Note: Designations like mdpi
and hdpi
are only general categories. Actual pixel densities are even more variable, but they are all given the same scaling factor regardless. So dp
scaling, while convenient, is not an exact science.
iOS Developers should be familiar with a similar practice of density independence, using “points” instead of pixels in their layouts to account for early iPhone screens not having Retina displays.
The final attribute of the TextView
is simply text
, in which you specify the text to be displayed. This attribute is a good example of how different views respond to different attributes. Adding a text
attribute to a RelativeLayout
or a Space
wouldn’t accomplish anything because, unlike the TextView
, they wouldn’t know what to do with it.
But the value of the attribute, @string/hello_world
, isn’t what’s displaying, is it? What you specify in your layout file is not the actual string to be displayed but rather a string resource ID identifying the actual text. That way, all your app’s copy can be in one place – res/values/strings.xml.
Now let’s look at the parent node in the XML: RelativeLayout
. What’s going on there?
Relative Layouts
The layouts in iOS apps used to be in purely absolute terms, like: “Place View X at pixels (x,y)”, but now iOS developers have AutoLayout. Android developers have always needed to keep device screen sizes in mind. Layout files are very well-suited for this consideration.
The default project Studio created for you sets you up with a useful layout: a RelativeLayout
. It is currently the parent layout and the TextView
element is its child.
A RelativeLayout is an intuitive and powerful thing. It holds a bunch of child views and positions them in relation to each other. Here are three examples of what you can easily do with a RelativeLayout
:
Example 1: Use layout_alignParentBottom
and the similar attributes for top, left and right to line up a view’s edge with the corresponding edge of the RelativeLayout
, which may or may not also be the edge of the screen.
Example 2: You can use layout_toRightOf
and the analogous attributes for left, above and below to position one View
relative to another.
Example 3: You can use layout_alignRight
and the analogous attributes to align a side of one View
with another.
You can see how that could be useful! For now, though, let’s move on to the layout type you’ll be using for this tutorial.
Linear Layouts
A LinearLayout needs to have an orientation specified, either horizontal
or vertical
. Then it lines up its children in that orientation, in the order in which they are specified in your XML.
The children of LinearLayouts
don’t respond to attributes like layout_toRightOf
, but they do respond to two other attributes: layout_weight
and layout_gravity
.
Specifying a layout_weight
expands the view to a proportion of its parent so that the parent weight is the sum of all child view weights.
The layout_weight
of View
X
———————————————————————-
The sum of all weights of View
X and its siblings
Confused? Perhaps the following image might help explain it better.
Notice how the full height of the parent view is split up between the child views based on the layout weight assigned to each child view.
Assigning a layout_gravity
to a view sets its horizontal and vertical positions within its parent LinearLayout
. For example, a view might have a layout_gravity
attribute with a value like left
, right
, and center_vertical
. The previous values can also be combined, like this: top|center_horizontal
.
Then, there’s gravity
, not to be confused with layout_gravity
. While layout_gravity
is about where you place the view itself, the gravity
attribute defines how you place the content of a view within itself. If you want your text to be left or center justified, use gravity
.
The following example shows how layout_gravity
and gravity
work in a vertical LinearLayout
. Note that the top three have a layout_width
of wrap_content
while for the bottom three it’s set to match_parent
:
One handy trick, which you’ll see in just a bit, is that you can nest layouts inside each other. Cue the theme music from Inception!
You don’t want your layouts to be as multi-layered as a Christopher Nolan movie, though. So, if you start to see your nested LinearLayout
s scheme getting out of hand, consider switching to a RelativeLayout
.
Views
— to do a tiny calculation to put the view together.
Now, modern devices won’t have any trouble rendering your basic layouts. But if you were to make an indefinitely long ListView
— for instance, one full of cells making copious use of nested LinearLayout
s and layout_weight
s — all those extra tiny calculations could add up.
Before you move on to the next section, open res/layout/activity_my.xml and change the root node from a RelativeLayout
— what Android Studio gave you as a default — to a LinearLayout
.
To do that, you should replace these lines:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MyActivity"> |
And this one at the very end of the file:
</RelativeLayout> |
With This:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".MyActivity"> |
And this:
</LinearLayout> |
Accessing Views From Within Java
Layouts are primarily the domain of your XML. But there are plenty of visual elements you will want to create, destroy, change, and trigger from within your Java code!
So first, edit the TextView
in activity_my.xml to match the following:
<TextView android:id="@+id/main_textview" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="20dp" android:layout_marginTop="20dp" android:text="@string/hello_world"/> |
Notice the addition of the id
attribute. Using this tag (or attribute, if you prefer) allows you to access that specific View
from within your code, so you can thereafter manipulate the View
via code.
There’s also a change to make in the text
tag. The name of the string resource hello_world
is a bit outdated now, don’t you think? Right-click on the @string/hello_world part of the line and then choose Refactor > Rename.
Then, type in textview
and click Refactor.
This not only changes the name of the resource ID in your layout file, it also changes the original resource ID in your strings.xml
file. It also renames the resource ID wherever else it might be used in the project. This is a useful trick to remember when renaming something that appears all over your project!
Now open MyActivity.java and add the following line above the onCreate
method but below the MyActivity class declaration:
TextView mainTextView; |
Android Studio will throw an error at you when you leave your cursor on this line that will look like this:
The TextView
class hasn’t been imported into MainActivity.java yet so it doesn’t know what a TextView
is. Android Studio can quickly fix that for you. Just tap Alt-Enter on your keyboard while this error popup is present to automatically import TextView
.
Note: It can get tiresome very quickly having to manually import every single component of an Android App you want to use in a class. Fortunately you can automate this in the IDE settings window, which you can access by going to Android Studio > Preferences (for Mac) or File > Settings (for Windows & Linux) and then clicking Editor > Auto Import and ticking Optimize imports on the fly and Add unambiguous imports on the fly.
Next, add the following code to onCreate
after the two existing lines of code:
// 1. Access the TextView defined in layout XML // and then set its text mainTextView = (TextView) findViewById(R.id.main_textview); mainTextView.setText("Set in Java!"); |
Your MyActivity.java file should now look like this:
public class MyActivity extends Activity { TextView mainTextView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_my); // 1. Access the TextView defined in layout XML // and then set its text mainTextView = (TextView) findViewById(R.id.main_textview); mainTextView.setText("Set in Java!"); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.my, menu); return true; } } |
Finally, run your app and look at the results!
The text set via Java code now appears on screen. What are the steps you just took to make that happen?
- You added an
id
attribute to theView
in XML. - You used the
id
to access theView
via your code. - You called a method on the
View
to change its text value.
Note: You added the code to access and set the text of your TextView
in the onCreate
method of your Activity
, meaning that the app runs all the code in that block right away when it first creates the Activity
. Activities have strict lifecycles they must follow and it’s thanks to this that you can write code that runs at specific points of an Activities life. You can read more about the Activity
lifecycle here.
Buttons and Listeners
It’s time to build on your TextView
and get more interactive! Next up is a Button
.
Add a Button
to activity_my.xml, directly after your TextView
:
<!-- Set OnClickListener to trigger results when pressed --> <Button android:id="@+id/main_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="20dp" android:layout_marginLeft="20dp" android:text="@string/button" /> |
Notice there’s an XML comment above the Button
, a reminder of how to trigger results.
The layout_margin
attributes simply add 20 density-independent pixels of space above and to the left of the Button
to keep your layout from looking cramped. Remember that the value of 20 will be scaled by the screen density of the device to get an actual pixel value.
You’ve probably noticed @string/button
under the button text property appears in red. If you hover over it, you’ll see that the symbol cannot be resolved – and that’s because you haven’t yet defined it.
Open strings.xml and add the following line to the bottom to resolve this:
<string name="button">Update The TextView</string> |
Next, open MyActivity.java
and add the following right below the previous line you added to include a TextView
variable:
Button mainButton; |
Now add the following code to the end of onCreate
, after the code you added earlier:
// 2. Access the Button defined in layout XML // and listen for it here mainButton = (Button) findViewById(R.id.main_button); mainButton.setOnClickListener(this); |
Again, you see the same three steps as when you added code to access the TextView
:
- You add an
id
to theView
in XML. Or, in this case, you add a view with anid
attribute. - You access that
View
in code by using theid
. - You call methods on that
View
.
This time, the method you called on the Button
is setOnClickListener
. What you put in the parentheses of that method becomes the answer to this question: Which Object
is going to respond when this Button
gets pressed?
To answer that question with simply the word this
seems a little curt and unspecific, but Java knows that it means MyActivity
itself is your intended listener.
This means that MyActivity
has to implement the View.OnClickListener
interface. If that sentence doesn’t make much sense to you, I suggest finding an intro on what an interface is and how to create one, like this one.
If have an iOS/Objective-C background, an interface is comparable to a protocol. In fact, in object oriented programming the words protocol and interface are used interchangeably.
Android Studio is smart and can help you do the implementation. Simply single-click on this
, which is underlined in red, indicating an issue (in this case the fact that MainActivity
currently does not support the necessary interface). Then, when a red light bulb appears at the beginning of the line, click on it and select Make ‘MyActivity’ implement ‘android.view.View.OnClickListener’.
Simply click OK on the next dialog, which lets you know which method(s) Studio will automatically create for you.
Studio then generates the code necessary to make your MyActivity
qualify as a union-certified OnClickListener
.
First, it added a bit to the class declaration indicating that the Activity
implements a specific interface:
public class MyActivity extends Activity implements View.OnClickListener |
Second, Studio added a stub for a method you need to implement in order to get your OnClickListener
license (other interfaces may require more than one method to be implemented): onClick
. This method fires when your Button
gets pressed.
@Override public void onClick(View v) { } |
The method currently does nothing. So add the following code to onClick
to make it do something:
// Test the Button mainTextView.setText("Button pressed!"); |
Can you tell from the code what should happen? Run your app and see if you’re right…
The app now changes the text in the TextView
when you press the Button
. Cool! You’ll be putting this Button
to even better use later — to submit input.
Adding a Visual and Nested Layouts
It’s always fun to include images in your UI. So how about adding an ImageView
to show a little icon? Along the way, you’ll also get to see how a nested LinearLayout
works.
First off, what image will you show? Well, it’s easiest to start with the image you’re given by default. It’s already in your project and here’s where to find it.
Use the Project Navigator to expand the src/main/res
directory:
You can see several directories within res
, including values
, which you dealt with when you edited strings.xml. Notice that several are named drawable
, plus some letters that look like screen density abbreviations.
Indeed, those abbreviations correspond to the pixel density buckets used to classify Android devices in dots per inch (dpi):
mdpi:
mediumhdpi:
highxhdpi:
extra highxxhdpi:
extra extra high
You can even create your own xxxhdpi
folder where you can place images for devices with even higher pixel densities. Personally, I think they should use Roman numerals for the next level up and call it xlhdpi
, but on second thought that would probably be a terribly confusing way to go…
Look inside the drawable
directories. You’ll see a file named ic_launcher.png. This is simply the default launch image you’re given, at several different sizes for different screens. The Android system will pick the right one for the device.
Now head back to activity_my.xml and replace the following section:
<!-- Set OnClickListener to trigger results when pressed --> <Button android:id="@+id/main_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="20dp" android:layout_marginLeft="20dp" android:text="@string/button" /> |
With this:
<!-- This nested layout contains views of its own --> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal"> <!-- Set OnClickListener to trigger results when pressed --> <Button android:id="@+id/main_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="20dp" android:layout_marginLeft="20dp" android:text="@string/button" /> <!-- Shows an image from your drawable resources --> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="20dp" android:layout_marginLeft="20dp" android:src="@drawable/ic_launcher" /> <!-- Closing tag for the horizontal nested layout --> </LinearLayout> |
You just added a new LinearLayout
inside the existing root LinearLayout
layout, directly underneath the TextView
as its new sibling. You also moved the existing Button
into the nested layout and added a new ImageView
, as well.
By wrapping your Button
in a second, horizontal LinearLayout
, you are able to place a Button
and an ImageView
side-by-side horizontally, even as the root layout has a vertical orientation.
As for the ImageView
itself, the important attribute is src
, to which you give your drawable image resource. Note the format you use to reference the drawable image. You need to prefix the file name of your image (minus the file type) with @drawable/
.
Run the app, and you’ll see the new image right beside the button!
Involving the Keyboard
Now it’s time to get some user input… by introducing an EditText
. This is a special subclass of TextView
that opens the keyboard and displays what the user types as its content.
Add the EditText
XML to activity_my.xml as a sibling to the TextView
and the horizontal LinearLayout
. Be careful not to get it caught inside the nested layout! Instead, add it right after the closing for the embedded linear layout and just before the closing for the root linear layout.
<!-- Displays keyboard when touched --> <EditText android:id="@+id/main_edittext" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="20dp" android:layout_marginLeft="20dp" android:hint="@string/hint" /> |
Notice the special attribute, hint
. You’re using this text as a placeholder in the input field. The app will overwrite it once the user starts typing.
As usual, you need to define the string resource for your hint in res/values/strings.xml:
<string name="hint">A Name</string> |
Now open MyActivity.java and add a new variable for the EditText
(below the other two existing variables):
EditText mainEditText; |
Next, add the following code to the end of onCreate
:
// 3. Access the EditText defined in layout XML mainEditText = (EditText) findViewById(R.id.main_edittext); |
The above code, similar to the previous code, simply gets a reference to the EditText
control and saves it in the assigned variable.
Now that you have a reference to the EditText
control, you need to do something with user input. Replace the current contents of onClick
with the following:
// Take what was typed into the EditText // and use in TextView mainTextView.setText(mainEditText.getText().toString() + " is learning Android development!"); |
When mainButton
is clicked, the mainTextView
will now be set to display a string including the contents of mainEditText
concatenated with ” is learning Android Development!”.
Run your app, and test this out!
Now you receive user input with an EditText
, submit it with a Button
, and display it in a TextView
. Very nice! But how about visualizing more than one piece of data at a time?
The ListView
The ListView
is a useful control that visualizes a list of items. It’s analogous to a UITableView
in iOS.
You define a ListView
just as you would any other view in your XML. Add one to activity_my.xml as a sibling to the TextView
, the horizontal LinearLayout
, and the EditText
by adding the following lines after the lines for the EditText
control:
<!-- List whose dataset is defined in code with an adapter --> <ListView android:id="@+id/main_listview" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" android:layout_marginTop="20dp" /> |
Wait… what? How in the world could setting layout_height
to 0dp
be a good idea? After all, no matter what screen you’re on, 0
is always going to scale to 0
.
Well, take a look at what directly follows: a layout_weight
. Since you haven’t given anything else in your layout a weight yet, the ListView
is going to expand to fill as much space as possible, no matter what value you give the layout_height
.
The general practice, then, is to use a value of 0
so the layout inflater has one fewer dimension to think about and can get the job done a bit quicker.
Now open MyActivity.java and, add the following variables below the ones you’ve already added above the onCreate
method:
The one for the ListView
makes sense. But what about the others? The others are for supplying the ListView
with data to display. All will be explained in a bit :]
But first, add the following code to the end of onCreate
:
// 4. Access the ListView mainListView = (ListView) findViewById(R.id.main_listview); // Create an ArrayAdapter for the ListView mArrayAdapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1, mNameList); // Set the ListView to use the ArrayAdapter mainListView.setAdapter(mArrayAdapter); |
Some of that looks familiar by now: finding the ListView
using its id
. But what else is going on?
mArrayAdapter
is an example of an adapter, which is basically a go-between so your ListView
can get the data it needs.
ListView
as being a picky sort, as far as Objects
go. It’s great at what it does but doesn’t want to get its hands dirty with any real data. It’s all got to be prepared for it or else it’ll throw a fit.
The Adapter
, then, is the enterprising Object
that is able to code-switch between the rough language of the datasource and the refined dialect of the ListView
.
When you create mArrayAdapter
, you have to specify the Context
, the target XML view for the data (simple_list_item_1
), and the datasource (mNameList
).
But hang on, you didn’t write anything with an id
of simple_list_item_1
! So where is that coming from? Also what exactly is a Context
?
Notice the android.R.layout
part before simple_list_item_1
. There are several important concepts here, but let’s look at the R
bit first. R
(or, R.java
, if you prefer) is a dynamically created class which gives you access to the resources in your project. If interested, you can read more about accessing resources via the R
class, here.
As the linked article above explains, you can use the R
class to get a resource ID by specifying a resource type and a resource name. The resource type would be something like string
, drawable
, or layout
– matching the various resource types you see in your project. And thus, the layout
part in android.R.layout.simple_list_item_1
simply specifies that you are referring to a layout resource.
But what about the android
prefix? Why is it there? It is an indicator that you didn’t create the view; it’s already part of the Android platform. It represents a simple TextView
that a default list cell can use.
The Context is an object that represents the current state of your App. Do you need to access a specific service for your App to use? Context is your guy. Do you need your App to show a specific View
or Activity
? Context is the mastermind behind it.
In this instance, Context is used to create the Views used within your ListView
. Remember that layout resource you are referring to? This is the layout the context takes and converts into a view, the adapter then populates each view with a value from its datasource.
The datasource in this case is mNameList
, which is simply a list of String
s. It’s initialized, but empty. So the next step is to add some data that the ListView
can display.
Add the following code to the end of onClick
:
// Also add that value to the list shown in the ListView mNameList.add(mainEditText.getText().toString()); mArrayAdapter.notifyDataSetChanged(); |
You simply add whatever the user typed into the EditText
to the list of names and then shoot a signal to the adapter to update what’s shown in the ListView
.
Now run your app.
You should be able to type a name into the EditText
, then see the name used in the TextView
and added to a new row in the ListView
when you press the Button
. Cool!
Detecting List Selections
Looking at the items in a list is cool, but this is an app, and interactivity is even better! So next you’re going to set up a way to detect user selections from the list.
First, modify the class definition in MyActivity.java to add support for another interface. To do this, modify this line:
public class MyActivity extends Activity implements View.OnClickListener { |
To look like this:
public class MyActivity extends Activity implements View.OnClickListener, AdapterView.OnItemClickListener { |
All you’ve really done is add support for a new interface – AdapterView.OnItemClickListener
, which, as the name suggests, listens for item selections from a ListView
. MainActivity
is really stacking up the credentials!
You should also have a red line highlighting the line where you’ve just added your new interface, Android Studio is letting you know that you haven’t actually implemented the interface yet. You can fix this easily by clicking the highlighted line, pressing Alt-Enter,clicking Implement Methods and finally clicking Ok.
Next, add the following code to the end of onCreate
:
// 5. Set this activity to react to list items being pressed mainListView.setOnItemClickListener(this); |
The above code sets MyActivity
as the listener for any item clicks on mainListView
.
Now replace the onItemClick
Method that was automatically generated for you with the following:
@Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { // Log the item's position and contents // to the console in Debug Log.d("omg android", position + ": " + mNameList.get(position)); } |
You may need to use your old friend Alt-Enter to import and use Log
.
Well done! Your MyActivity
onItemClick and can live up to the title of being a card-carrying OnItemClickListener
.
But what’s happening inside onItemClick
? There’s a weird Log.d
in there, and then something with a get(position)
…
Take a look at what you’re passing along to onItemClick
. In particular, look at int position
, which is an integer equal to the index of the item the user pressed on the list (counting up from 0).
You take that position, as well as the item at that index in your list of names, and log them. Logging is a very basic, but very useful debugging technique.
Run your app, enter a few values and add them to the list, just as before. Then select an item. There’s no visible effect for the moment.
With the app still running, look at the bottom section of Android Studio:
The bottom left section of the window contains information about your Device or Emulator, what processes are running and logs of what is currently happening within those processes. These logs appear in a console called logcat
. It will read off tons of stuff from your emulator or device, the majority of which is not of much interest to you at this point. The log statements you generated with your selections are here, but there’s too much noise to see them.
Here are some useful ways to filter out the noise and see only what you want:
Notice the option for Log level in a dropdown at the top of the console. When you put your Log
command into code, it specifically was the Log.d
command. The d
is for “debug” level. The levels are:
v
: Verbosed
: Debugi
: Infow
: Warninge
: Error
When you select a log level for logcat
, it will only show messages at that level or higher. And the levels start at verbose and go up to error, in the same order as listed above. So, if you select the log level as Warning, then you’ll see all warnings and errors but nothing else.
Meanwhile, you can use the text box to the right of the log level drop-down to apply a filter and show only those messages that contain the text you typed in.
Now that you know this, set the log level to Debug and type omg android into the filter text box.
Great! You now have a clean feed of log statements and you can detect when a certain item gets selected in your list. The ability to log will come in handy as you create more complicated apps and want to stay informed of the inner workings of your code.
The Action Bar
Your app has several different views now, and it’s time to think about other ways to add functionality. Older Android devices used to have a Menu device button that would display a bunch of options depending on the situation, but since Honeycomb in early 2011, Android has used the Action Bar to display any options for the current view.
The Action Bar provides a familiar base for your users. Since it’s present across apps, making good use of the Action Bar means a significant part of your app’s functionality will be immediately intuitive to an Android user. Conversely, neglecting the Action Bar would confuse all your users who expect it to work — and that would be weird!
The Action Bar is already in your app — it just has no options attached to it yet. That will be your first order of business next!
Sharing
Soon you’ll have a chance to show off the fact that you’re learning Android, from within your own app! You’ll do this using an intersection of the Android concept of the Intent
and the Action Bar, known as a ShareActionProvider
.
One of the advantages of an Intent
is that you can construct it in either a specific (explicit) or generic (implicit) manner. You used an example of the explicit type when you specifically defined the Intent
that launches your app, which the manifest then identifies as MainActivity
. Now you’ll see an example of the implicit type.
A generic Intention
really helps in this case. After all, some of us prefer to share things with the entire world, and others with just a few friends. Rather than wondering what a potential user’s favorite social network might be and integrating them one by one, you can politely tell the Android device that you’d very much like to share a bit of content (thus expressing an Intent
), and Android will graciously take it from there!
Navigate to src/main/res/menu/my.xml and open it.
You’ll note that there’s some auto-generated XML in there, but you don’t need it. Replace the whole thing with this:
<!-- Defines the menu item that will appear on the Action Bar in MainActivity --> <menu xmlns:android="http://schemas.android.com/apk/res/android"> <!-- Share item --> <item android:id="@+id/menu_item_share" android:showAsAction="ifRoom" android:title="Share" android:actionProviderClass= "android.widget.ShareActionProvider" /> </menu> |
The ShareActionProvider
is built-in (hence the android.widget
prefix) and ready-to-use. So, when a user selects your menu item, the necessary functionality is already in place for you to make use of.
Now head over to MyActivity.java and add the following variable underneath the rest of your variables:
ShareActionProvider mShareActionProvider; |
ShareActionProvider
you mean, so if it asks, use android.widget.ShareActionProvider
. If you have enabled auto imports then double check it has imported the right one.Next, you need to add the following two methods to the class – the first one, onCreateOptionsMenu
, might already be implemented in the class. If it is, simply replace the existing implementation with the new one.
@Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu. // Adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.my, menu); // Access the Share Item defined in menu XML MenuItem shareItem = menu.findItem(R.id.menu_item_share); // Access the object responsible for // putting together the sharing submenu if (shareItem != null) { mShareActionProvider = (ShareActionProvider) shareItem.getActionProvider(); } // Create an Intent to share your content setShareIntent(); return true; } private void setShareIntent() { if (mShareActionProvider != null) { // create an Intent with the contents of the TextView Intent shareIntent = new Intent(Intent.ACTION_SEND); shareIntent.setType("text/plain"); shareIntent.putExtra(Intent.EXTRA_SUBJECT, "Android Development"); shareIntent.putExtra(Intent.EXTRA_TEXT, mainTextView.getText()); // Make sure the provider knows // it should work with that Intent mShareActionProvider.setShareIntent(shareIntent); } } |
onOptionsItemSelected
in the class, remove it.Add your imports so they are recognised by your Activity
.
onCreateOptionsMenu
gets called once, when the activity first starts. Similar to how you specified which layout XML file you wanted to use for the activity in onCreate
, you now direct the menu inflater to look at my.xml for the menu items that go on the Action Bar.
From there, you can access the menu item you defined in XML by its id
, menu_item_share
, and then you can access its action provider. Previously, you specified that this item’s action provider was a ShareActionProvider
. So, you can safely cast to that type in your code and hang onto a reference to it via the mShareActionProvider
variable.
Then, you call setShareIntent
. This method creates an Intent
, but not just any Intent
. It creates an Intent
whose action you’ve set to ACTION_SEND
. It’s truly as generic as it looks: you’re going to tell Android you want to take the action of sending something.
From there, you set the Intent
‘s content type, subject — used by email programs and the like, as the subject header of the message —, and text. The text matches whatever is currently in your TextView
. After you’ve packed up everything the Intent
needs to know, you pair it with mShareActionProvider
.
This code will work, but only “kind of.” As-is, you only call setShareIntent
once, at the creation of the menu. It would be much better to update the Intent
whenever the TextView
changes — otherwise you’re stuck with the initial message forever!
Add the following code to the end of onClick
:
// 6. The text you'd like to share has changed, // and you need to update setShareIntent(); |
Here, you simply make sure that the share intent is always up-to-date.
Run the app, and try out the new sharing feature – tapping the share icon on the Action Bar should reveal a number of choices, depending on what is installed on your emulator or device.
The ShareActionProvider
automatically puts together an array of possible avenues for sharing content based on the apps you have installed on a given device. This array of options will differ from device to device. The emulator will most likely have far fewer options for sharing, whereas you may have apps like Twitter and Facebook on an actual device and could share through those networks, too.
Remembering Your Name
Everything you’ve done so far with regard to user input only persists while the app is running. But what about between sessions? Let’s see some data persistence in action, with a new feature that will record and remember your name each time you open the app.
There are a few good options on Android to persist data, and the simplest one is SharedPreferences
.
SharedPreferences
stores data in key-value pairs, meaning that you specify a name (the key) for a piece of data (the value) when you save it, and you can retrieve it later by using the original key.
Let’s see how it works in action, shall we?
First, add the following constants and variable to MainActivity.java (to the same place as the previous variables):
private static final String PREFS = "prefs"; private static final String PREF_NAME = "name"; SharedPreferences mSharedPreferences; |
The above sets PREF
and PREF_NAME
at the top of the class. You’ll use PREF
as a filename to keep your SharedPreferences
in a single location. You’ll use PREF_NAME
as the key for storing your name in shared preferences.
Strings
that you’ll need/refer to multiple times. That way, if you need to change the string value later, you can do it in one place. You’ll also never have to worry about some weird spelling error creating bugs in your code.The final line adds a variable named mSharedPreferences
for storing a reference to the shared preferences class. You only need to access it in a few places, but it will be useful to hang onto it. Add the import into your Class if you haven’t already.
Next, add the following lines to the end of onCreate
:
// 7. Greet the user, or ask for their name if new displayWelcome(); |
The new code calls a new method, displayWelcome
. So implement that by adding the following method at the end of the class:
public void displayWelcome() { // Access the device's key-value storage mSharedPreferences = getSharedPreferences(PREFS, MODE_PRIVATE); // Read the user's name, // or an empty string if nothing found String name = mSharedPreferences.getString(PREF_NAME, ""); if (name.length() > 0) { // If the name is valid, display a Toast welcoming them Toast.makeText(this, "Welcome back, " + name + "!", Toast.LENGTH_LONG).show(); } } |
displayWelcome
directly into onCreate
and it would still work, many people, your humble author included, prefer to keep method lengths reasonable by calling separate methods for a specific task.
This also means that if the same task needs to be performed from elsewhere in code later on, you already have a handy method in place for it :]
In the new method, the first thing you do is access SharedPreferences
, with MODE_PRIVATE
meaning that only your OMGAndroid app can access the data stored here. This means that your saved data will not get overwritten by another application which might have used the same key as you.
Then you simply ask the preferences object for whatever value is stored using the key PREF_NAME
. The second parameter for the method can be used to set a default value to be returned in case there is no value stored using the key you provide. So, you use an empty String
as the default value here.
Finally, you check to see if the retrieved String
actually has any content, and display a message if so. Your message takes the form of a Toast
, which is a short-lived pop-up message that appears for a bit and then fades away. Give the Toast
a message it should display, specify one of its built-in lengths to remain on the screen and then simply tell it to show
. Easy!
Displaying the Name Dialog
What you’ve set up so far will show your name if the application can retrieve it out of the preferences. But obviously, that’s no use to you yet since you have no mechanism in place to save your name in the first place!
You’ll use a Dialog
to achieve that. Dialogs
are small windows that alert the user. They may contain ways for the user to provide input or make choices. You’re going to use an AlertDialog
, specifically.
Add the following code to the end of displayWelcome
, creating an else
branch for the if
condition that’s already there:
} else { // otherwise, show a dialog to ask for their name AlertDialog.Builder alert = new AlertDialog.Builder(this); alert.setTitle("Hello!"); alert.setMessage("What is your name?"); // Create EditText for entry final EditText input = new EditText(this); alert.setView(input); // Make an "OK" button to save the name alert.setPositiveButton("OK", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { // Grab the EditText's input String inputName = input.getText().toString(); // Put it into memory (don't forget to commit!) SharedPreferences.Editor e = mSharedPreferences.edit(); e.putString(PREF_NAME, inputName); e.commit(); // Welcome the new user Toast.makeText(getApplicationContext(), "Welcome, " + inputName + "!", Toast.LENGTH_LONG).show(); } }); // Make a "Cancel" button // that simply dismisses the alert alert.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) {} }); alert.show(); } |
The app will reach this else
condition when there is no valid name saved using the PREF_NAME
key. You use an AlertDialog.Builder
to give your AlertDialog
a title, a message, and an EditText
in the center for the user to type in their name.
Then, you add two buttons to the AlertDialog
: a positive and a negative button. The first thing you define for each is the text displayed on the button – “OK” and “Cancel” are pretty standard choices. The second thing you define for each button is an OnClickListener
.
This time your OnClickListener
s are specifically DialogInterface.OnClickListener
s, and you are defining them right away. Notice how the parameters for onClick
are slightly different.
For the positive button’s listener, onClick
does quite a bit. First, it reads the name that the user typed into the dialog’s EditText
.
It then saves that name into SharedPreferences
using a helper called a SharedPreferences.Editor
. You simply tell the editor what to save and where, tell it to commit the changes, and that’s it!
Finally, it displays a Toast
identical to the other welcoming one.
The negative button’s listener is far simpler: it does nothing! Nothing!
Run your app and check out your Dialog
.
Type in your name, press OK and see the Toast
greeting. From now on, your app will remember your name and greet you each time you launch it!
Where to Go From Here?
You covered a lot of UI concepts in this part of the tutorial! Take a few minutes to play around with your app a little, maybe sharing something with a friend!
You can get the full source code for this part of the tutorial on GitHub or as a .zip.
Those looking for a challenge should try:
- Setting up the
EditText
to expect names as its input so that it capitalizes first letters. - Dismissing the keyboard associated with the
EditText
. - Making the Done button on the keyboard do the same thing as the Update TextView button.
- Modifying font sizes on the various views.
Hopefully you have found this helpful! If you have any comments or questions, feel free to leave them below. And of course, don’t miss Part Three, in which you set up your app to interact with data online!
The Android robot is reproduced or modified from work created and shared by Google and used according to terms described in the Creative Commons 3.0 Attribution License.
Android Tutorial for Beginners: Part 2 is a post from: Ray Wenderlich
The post Android Tutorial for Beginners: Part 2 appeared first on Ray Wenderlich.