Quantcast
Channel: Kodeco | High quality programming tutorials: iOS, Android, Swift, Kotlin, Unity, and more
Viewing all articles
Browse latest Browse all 4370

Android Tutorial for Beginners: Part 2

$
0
0

android_tutorial_title_image

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:

personalized_hello_world

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.

switch_to_text

Note: From now on, this tutorial will always interact with XML files in text mode.

The only thing you need to remove here are the padding attributes that Android Studio automatically generates in your .xml layout. They look something like this (your values may vary slightly):

android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"

Delete these lines from your layout, your activity_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.

Pro Tip: A quick way to navigate to any file in your project using OSX is to type Shift + Command + O. This is the same shortcut used in Xcode!

my_activity_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). A dp is a pixel on a “medium-density” (mdpi) device, and the number of actual pixels automatically scales for devices designated as low-density (ldpi), high-density (hdpi), extra-high-density (xhdpi), etc.

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.

You can command click on the resource ID to be brought directly to the definition in your resource file.

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.

layout_alignParentBottom

Example 2: You can use layout_toRightOf and the analogous attributes for left, above and below to position one View relative to another.

layout_toRightOf

Example 3: You can use layout_alignRight and the analogous attributes to align a side of one View with another.

layout_alignRight

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.

layout_weight

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:

layout_gravity

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 LinearLayouts scheme getting out of hand, consider switching to a RelativeLayout.

Note: There are also performance reasons for considering a simple layout design. Layout weights require the layout inflater — the class which turns the XML into real Views — to do a tiny calculation to put the view together.

Now, modern devices 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 LinearLayouts and layout_weights — 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.

refactor

Then, type in textview and click Refactor.

rename_textview

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:
import warning

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!

dynamic_text

The text set via Java code now appears on screen. What are the steps you just took to make that happen?

  1. You added an id attribute to the View in XML.
  2. You used the id to access the View via your code.
  3. You called a method on the View to change its text value.

Note: You added the code to access and set the text of your TextView in the onCreate method of your Activity, meaning that the app runs all the code in that block right away when it first creates the Activity. Activities have strict lifecycles they must follow and 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:

  1. You add an id to the View in XML. Or, in this case, you add a view with an id attribute.
  2. You access that View in code by using the id.
  3. 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.

Note: An interface is like part of a job description. If I’m a young, ambitious Java object, and I want to be able to put a particular certification on my resume, there are a few methods I have to be comfortable and capable of performing. The interface is like the checklist I need to pass, or “implement.”

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’.

implement_onclick

Simply click OK on the next dialog, which lets you know which method(s) Studio will automatically create for you.

auto_generate_method

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…

button_pressed

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:

ic_launcher

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: medium
  • hdpi: high
  • xhdpi: extra high
  • xxhdpi: 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!

nested_layout_imageview

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!

edittext_update_textview

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:

ListView mainListView;
ArrayAdapter mArrayAdapter;
ArrayList mNameList = new ArrayList();

The one for the ListView makes sense. But what about the others? The others are for supplying the ListView with data to display. All will be explained in a bit :]

But first, add the following code to the end of onCreate:

// 4. Access the ListView
mainListView = (ListView) findViewById(R.id.main_listview);
 
// Create an ArrayAdapter for the ListView
mArrayAdapter = new ArrayAdapter(this,
		android.R.layout.simple_list_item_1,
		mNameList);
 
// Set the ListView to use the ArrayAdapter
mainListView.setAdapter(mArrayAdapter);

Some of that looks familiar by now: finding the ListView using its id. But what else is going on?

mArrayAdapter is an example of an adapter, which is basically a go-between so your ListView can get the data it needs.

Note: I think of a 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 Strings. 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.

name_list

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.

activity_implement_methods

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 MyActivityonItemClick and can live up to the title of being a card-carrying OnItemClickListener.

activity_business_card

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:

log output

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: Verbose
  • d: Debug
  • i: Info
  • w: Warning
  • e: Error

When you select a log level for logcat, it will only show messages at that level or higher. And the levels start at verbose and go up to error, in the same order as listed above. So, if you select the log level as Warning, then 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.

logging_item_clicks_arrow

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.

action_bar

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!

Note: Since you are only targeting Ice Cream Sandwich and later for this tutorial, you don’t have to worry about bending over backwards to get the Action Bar into your app. If for some reason your project needs to be compatible with really old versions of Android, you’ve got two good options: a compatibility library from Google and a well-regarded third-party solution called Action Bar Sherlock.

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.

menu_main

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;
Note: Studio may be confused about which 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);
    }
}
Important: If there’s an implementation of 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.

share

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.

Note: It’s a good practice to use variables for 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();
    }
}
Note: While you could conceivably place all the code from displayWelcome directly into onCreate and it would still work, many people, your humble author included, prefer to keep 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 OnClickListeners are specifically DialogInterface.OnClickListeners, 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.

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.


Viewing all articles
Browse latest Browse all 4370

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>