Monthly Archives: February 2014

Android notifications and PendingIntents

Notifications

On an Android phone, the bar at the top of the screen is the notification area or status bar. On a tablet, this area is at the bottom right of the screen. In either case, it is an area that is always visible and is used by apps to post notifications about various events that have occurred. The notification area can be opened to get a list containing more detailed information about these events.

To illustrate a few of the features of notifications, we’ll modify our earlier example using Fragments so that each time the user inputs some new text, a notification is received. In addition, if the user then closes the app and then opens the notification area and clicks on a given notification item, the app is restarted displaying the text that was entered to give that notification.

Implementing these additions requires only adding a bit to the MainActivity. Since we want to generate a notification when the user presses the enterText button we can modify the event handler for this button as follows:

	@Override
	public void enterTextButtonPressed() {
		EnterTextFragment enterTextFragment =
				(EnterTextFragment) getFragmentManager().
				findFragmentById(R.id.enter_text_fragment);
		GetTextFragment getTextFragment =
				(GetTextFragment) getFragmentManager().
				findFragmentById(R.id.get_text_fragment);
		String text = enterTextFragment.getEnteredText();
		getTextFragment.setText(text);
		restartNotifyText(text);
	}

The only change we’ve made is the addition of a call to restartNotifyText() at the end, so let’s look at this method:

	private void restartNotifyText(String text) {
		Notification.Builder notifBuilder = new Notification.Builder(this).
				setSmallIcon(android.R.drawable.btn_star_big_on).
				setContentTitle("Text sent").setContentText(text).
				setTicker("New text arrived");
		Intent mainIntent = new Intent(getApplicationContext(), MainActivity.class);
		mainIntent.putExtra(LOADED_TEXT, text);
		PendingIntent mainPendingIntent = PendingIntent.getActivity(
				getApplicationContext(), simpleNotification, mainIntent,
				PendingIntent.FLAG_UPDATE_CURRENT);
		notifBuilder.setContentIntent(mainPendingIntent);
		NotificationManager notifManager =
				(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
		notifManager.notify(simpleNotification++, notifBuilder.build());
	}

To create a notification, we can use the Notification.Builder class. Its constructor takes a context (the current Activity will do). Following this, we can add as much information as we want to the notification. However, there are three things we must add to any notification: a small icon, a title and the content text. The methods for adding features each return the Builder object that calls them, so they can be chained as seen here. We’ve used one of the built-in icons (it displays a yellow star) for the small icon and provided values for the title and text. In addition we’ve added a ticker which is a message that is displayed for a few seconds when the notification first appears. The ticker is optional.

For a bare bones notification, that’s all the information we need and if we didn’t want the notification to respond to clicks, we could just skip from here to line 12 to get the NotificationManager, then build the notification and send it to the device using notify().

However, to allow the notification to restart a closed app when clicked, we need to provide some more code. We can use an explicit Intent to restart the app in the way we did earlier, so we define mainIntent on line 6 to do this. Since we want the restarted app to display the text just entered, we need to save this text as an extra within the Intent (line 7). The parameter LOADED_TEXT is just a pre-defined string which serves as a key for the extra; it can be any string you like so long as it’s unique.

PendingIntents

Next, we define a PendingIntent. This is essentially a token object which contains a reference to a genuine Intent, and which can be sent to another process giving that process permission to run the enclosed Intent. PendingIntents can specify that various types of processes can be started, but here we’re interested in starting up an Activity, so we call the static PendingIntent.getActivity() method. Its arguments are

  • the usual context in which it is to run
  • an ID int which can be referred to later if we want to modify the PendingIntent. Here, we’ve defined an int called simpleNotification earlier and set its initial value to 1.
  • the Intent itself
  • a flag indicating what action should be taken with the PendingIntent. FLAG_UPDATE_CURRENT means that if this PendingIntent already exists, we should update it with the new data rather than create a new one.

Then we attach this PendingIntent to notifBuilder using setContentIntent(). Setting the content Intent in the builder provides an event handler for clicks on the notification item: clicking on the item will tell the PendingIntent to start up its Intent.

With that done, we can now use the NotificationManager to build and post the notification as before.

One final point should be mentioned. The notify() method takes two arguments. The first is an int ID that can be used to update the notification later. If you call notify() again with the same ID, it overwrites the existing notification. If you call it with a new ID that hasn’t been used before, it creates a new notification. In this example, we want a separate notification for each new bit of text that is entered, so we increment the ID each time a notification is posted.

We use the same ID for the PendingIntent and the notification. If the ID and Intent used to construct a PendingIntent are the same as those used earlier, then the old PendingIntent is overwritten rather than a new one being generated. Note that changing the text stored in the Intent’s extras bundle does not change the Intent as seen by the PendingIntent, so unless we change the ID of the PendingIntent on each call, the same PendingIntent would be attached to all the notifications. That is, each notification would display the correct entered text, but when the Activity is restarted, only the last entered text would be displayed in the app itself.

Finally, we can look at the onCreate() method:

	private static final String LOADED_TEXT = "LoadedText";
	int simpleNotification = 1;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		Intent notifyIntent = getIntent();
		Bundle extras = notifyIntent.getExtras();
		if (extras != null) {
			String loadedText = extras.getString(LOADED_TEXT);
			GetTextFragment getTextFragment =
					(GetTextFragment) getFragmentManager().
					findFragmentById(R.id.get_text_fragment);
			getTextFragment.setText(loadedText);
		}
	}

We need to retrieve the Intent that was used to start (or restart) the Activity. When the app is started by clicking its icon on device’s screen, it is the main system Intent that initializes the Activity. This Intent won’t have any extras. We can use the Activity’s getIntent() method to retrieve the Intent that started the Activity, and getExtras() to get its extras Bundle. If this isn’t null, we can then retrieve the LOADED_TEXT string and set the TextView in getTextFragment to this text.

Advertisements

Android ListViews and adapters

The ListView is an interface element that displays a list of items. The items in the list can have a custom layout consisting of other controls such as text or images. Managing a ListView is a bit more complex than you might think so we’ll walk through a relatively simple example here to see how all the parts fit together.

The app we’ll develop displays a Fragment on the left that contains an EditText into which the user can type some text, and a couple of buttons. The addItemButton adds an item to the ListView (displayed in a Fragment on the right) that contains the entered text along with the date and time at which the item was added. The clearListButton deletes all items from the list.

We’ve seen how to add Fragments to an Activity, so we’ll use the same technique here. First we create an XML file called enter_item.xml in the res–>layout folder and use Eclipse’s Graphical Layout editor to add the EditText and the two Buttons. Then we create a class to manage this layout, which looks like this:

package com.example.ex05listview;

import android.app.Activity;
import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;

public class AddItemFragment extends Fragment {
	AddItemListener activityCallback;

	public interface AddItemListener {
		public void onAddItemClick(ListItem item);
		public void onClearListClick();
	}

	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container,
			Bundle savedInstanceState) {
		final View view = inflater.inflate(R.layout.enter_item, container, false);
		Button addItemButton = (Button) view.findViewById(R.id.addItemButton);
		addItemButton.setOnClickListener(new OnClickListener() {

			@Override
			public void onClick(View arg0) {
				EditText itemText = (EditText) view.findViewById(R.id.itemText);
				ListItem newItem = new ListItem(itemText.getText().toString());
				activityCallback.onAddItemClick(newItem);
			}
		});

		Button clearListButton = (Button) view.findViewById(R.id.clearListButton);
		clearListButton.setOnClickListener(new OnClickListener() {

			@Override
			public void onClick(View v) {
				activityCallback.onClearListClick();
			}
		});
		return view;
	}

	@Override
	public void onAttach(Activity activity) {
		super.onAttach(activity);
		try {
			activityCallback = (AddItemListener) activity;
		} catch (ClassCastException e) {
			throw new ClassCastException(activity.toString() +
					" must implement AddItemListener");
		}
	}

}

This Fragment needs to send the new item to the ListView on the right and it does so via the MainActivity in order to keep the Fragments independent of each other. As before, we do this by defining an interface (line 16) which MainActivity must implement. In AddItemFragment’s onAttach() method, we test that the calling Activity implements the interface and store a reference to the Activity for future use.

In onCreateView() we inflate the layout and add event handlers to the two Buttons. The addItemButton handler retrieves the entered text from the EditText control and constructs a ListItem object. ListItem is as follows:

package com.example.ex05listview;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.TimeZone;

public class ListItem {
	private String dateAdded;
	private String message;

	public ListItem(String message)
	{
		this.setMessage(message);
		SimpleDateFormat dateFormat = new SimpleDateFormat("dd MMM yyyy HH:mm:ss");
		dateFormat.setTimeZone(TimeZone.getTimeZone("Europe/London"));
		Calendar cal = Calendar.getInstance();
		setDateAdded(dateFormat.format(cal.getTime()));
	}

	String getDateAdded() {
		return dateAdded;
	}

	void setDateAdded(String dateAdded) {
		this.dateAdded = dateAdded;
	}

	String getMessage() {
		return message;
	}

	void setMessage(String message) {
		this.message = message;
	}
}

The constructor uses Java’s SimpleDateFormat and Calendar classes to get the current date and time and format it into a String (lines 15 to 18).

Back in AddItemFragment, we then send newItem to MainActivity (line 32). MainActivity just passes the ListItem along to ItemListFragment. Here’s the code for MainActivity:

package com.example.ex05listview;

import com.example.ex05listview.AddItemFragment.AddItemListener;

import android.os.Bundle;
import android.app.Activity;

public class MainActivity extends Activity
implements AddItemListener {
	ItemListFragment itemListFragment;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		itemListFragment = (ItemListFragment) getFragmentManager().findFragmentById(R.id.itemListFragment);
	}

	@Override
	public void onAddItemClick(ListItem item) {
		itemListFragment.addItem(item);
	}

	@Override
	public void onClearListClick() {
		itemListFragment.clearList();
	}
}

We’ll get to ItemListFragment in a minute. First, we add the event handler for clearListButton in AddItemFragment (line 37) which calls onClearListClick() in MainActivity, which again just passes this along to ItemListFragment.

To build the ItemListFragment, we create a new XML file in res–>layout, but all we need to do is set the initial layout to a ListView, with an ID of “@+id/list”. Here’s the complete XML file:

<?xml version="1.0" encoding="utf-8"?>
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/list"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
</ListView>

All lines except the android:id should be created automatically in Eclipse. Here’s ItemListFragment, the Java class that manages the list fragment:

package com.example.ex05listview;

import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

public class ItemListFragment extends Fragment {
	ItemListAdapter adapter;

	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container,
			Bundle savedInstanceState) {
		adapter = new ItemListAdapter(getActivity());
		adapter.addItem(new ListItem("Test item"));
		View view = inflater.inflate(R.layout.list_fragment, container, false);
		ListView listView = (ListView) view.findViewById(R.id.list);
		listView.setAdapter(adapter);
		listView.setOnItemClickListener(new OnItemClickListener() {

			@Override
			public void onItemClick(AdapterView<?> parent, View item, int position,
					long id) {
				TextView itemText = (TextView) item.findViewById(R.id.messageItem);
				Toast.makeText(getActivity(), itemText.getText().toString(),
						Toast.LENGTH_SHORT).show();
			}
		});
		return view;
	}

	public void addItem(ListItem item)
	{
		adapter.addItem(item);
	}

	public void clearList()
	{
		adapter.clearItems();
	}
}

On line 20, we create an ItemListAdapter. A list adapter is a class that manages the data behind a list. There are several pre-defined adapter classes, but it’s easy enough to write a custom one, so here’s ItemListAdapter:

package com.example.ex05listview;

import java.util.ArrayList;
import java.util.List;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;

public class ItemListAdapter extends BaseAdapter {
	List<ListItem> itemList = new ArrayList<ListItem>();
	Context context;

	public ItemListAdapter(Context c) {
		context = c;
	}

	@Override
	public int getCount() {
		return itemList.size();
	}

	@Override
	public Object getItem(int position) {
		return itemList.get(position);
	}

	@Override
	public long getItemId(int position) {
		return position;
	}

	@Override
	public View getView(int position, View arg1, ViewGroup parent) {
		LayoutInflater inflater = (LayoutInflater) context.getSystemService(
				Context.LAYOUT_INFLATER_SERVICE);
		View itemView = inflater.inflate(R.layout.list_item, parent, false);
		TextView dateText = (TextView)itemView.findViewById(R.id.dateTimeAdded);
		TextView message = (TextView)itemView.findViewById(R.id.messageItem);
		dateText.setText(itemList.get(position).getDateAdded());
		message.setText(itemList.get(position).getMessage());
		return itemView;
	}

	public void addItem(ListItem item)
	{
		itemList.add(item);
		notifyDataSetChanged();
	}

	public void clearItems() {
		itemList.clear();
		notifyDataSetChanged();
	}
}

We’ve extended BaseAdapter, which is a built-in Android abstract class containing four methods we must implement which we’ll get to in a minute.

The adapter must manage the data that is displayed in the ListView as well as provide the View object for each individual element in the list. Here, we’ve stored the data in a generic List<ListItem> (line 14), though in a real world application, we’d use a more permanent location for the data. In this app, the data must be entered from scratch every time the app is run.

To add an item to the list, we use addItem() (line 48). After the item is added to the itemList object, we must call notifyDataSetChanged() to update the ListView display on screen. Similarly, to clear the list, we use clearItems() (line 54).

The three methods on lines 22 to 34 just implement methods defined in the abstract base class and should be self-explanatory. (getItemId() returns an ID number for a list item; it’s up to us how we define this ID, so we’ve just taken it to be the position of the item in the list.)

The main work in the adapter is done in creating the View (line 37). As usual, we use a LayoutInflater to inflate the layout from its XML file (which just defines two TextViews, one of the date and one for the message). We need a ‘context’ to get the inflater, so if you refer back to line 20 in the code for ItemListFragment above, you’ll see we pass in the Activity that contains the Fragment so it’s used as the context in the adapter.

Once we’ve created the View, we need to set the values of its two TextViews which we do by retrieving the data from itemList.

Finally, back in ItemListFragment (line 25) we add an event handler for a click on a list item. This pops up a Toast notification (a little black rectangle that appears for a couple of seconds on the display) containing the text of the item clicked.

Obviously there are a lot of other tweaks we could add to this program, but it should give you enough information to include a ListView in an app.

Android Fragments

One limitation of Android devices is that they can display only a single Activity at a time. This was due initially to the fact that Android was used for small screen devices such as mobile phones. With the advent of larger screen devices such as tablets, there is more space in which to display the interface. However, such devices still allow only a single Activity to be displayed at a time.

To get around this problem, starting with Android version 3 the Fragment was introduced. A Fragment is a sub-process that can be embedded within an Activity, and a single Activity can have several Fragments running at a time. A Fragment can (but need not) have a visual interface which is created in much the same way that an Activity’s interface is created.

To illustrate this, we’ll create an app that displays the two interfaces we used in the previous examples on a single screen. Start as usual by creating a new Android project with a single blank Activity in Eclipse. Eclipse doesn’t have an “Add fragment” function, but it’s still quite easy to create one. The layout of a Fragment, like that of an Activity, is defined in an XML file, so right click on the res–>layout folder in Package Explorer and select New–>Android XML file. We’ll start with the first screen that displays the Get text button we used earlier. Call this XML file get_text_fragment.xml and select RelativeLayout as its Root Element. After it’s created, open it in the Graphical Layout and add a Button and two TextViews, just as we did earlier.

Do the same for the other screen, calling the file enter_text_fragment.xml and adding a TextView, EditText and a Button.

Next we need to create Java classes to represent the Fragments and handle their events. In the src–>growe.ex04fragments (or whatever you’ve called your package) folder, right click on the folder and select New–>Class. To represent the get_text_fragment, create a class called GetTextFragment. In the Superclass box, type Fragment then Ctrl+(space), then select the Fragment class in the android.app package. (The other Fragment class in the android.support.v4.app can be used if you need your app to run on older (pre version 3) versions of Android.)

This will generate the skeleton code for your class. At this stage, we need to override only the onCreateView() method, which is most easily done in Eclipse by putting the cursor inside the class and pressing Ctrl+(space) to get a list of methods you can override. Select onCreateView() and you’ll get the code for it.

The creation of a view from an XML file requires inflating the view using a LayoutInflater. You’ll see that this is one of the arguments in onCreateView(). To create the view, delete the existing code inside this method and insert the following, so your code looks like this:

package growe.ex04fragments;

import android.os.Bundle;
import android.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

public class GetTextFragment extends Fragment {
	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container,
			Bundle savedInstanceState) {
		View view = inflater.inflate(R.layout.get_text_fragment, container, false);
		return view;
	}
}

The inflate method looks up the get_text_fragment.xml file and creates a Java version of the layout.

Do the same for enter_text_fragment.xml by creating a class called EnterTextFragment which looks like this:

package growe.ex04fragments;

import android.os.Bundle;
import android.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

public class EnterTextFragment extends Fragment {
	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container,
			Bundle savedInstanceState) {
		View view = inflater.inflate(R.layout.enter_text_fragment, container, false);
		Button sendTextButton = (Button) view.findViewById(R.id.sendTextButton);
		sendTextButton.setEnabled(false);
		return view;
	}
}

We’ve added a couple of lines to disable the Send text button when it is first displayed. The idea is that clicking on Get text enables this button and allows the user to send some text.

Now we need to embed these Fragments in the MainActivity and get them displayed on screen. First, we need to let the MainActivity’s layout know about the Fragments. This is done in activity_main.xml by adding in a couple of <fragment> tags. This can be done in Eclipse’s Graphical Layout.

Open activity_main.xml in Graphical Layout and open the Layouts menu in the Palette box on the left. Drag a Fragment onto the screen to where you want it. This will open a dialog in which you choose the Fragment class you want, so select GetTextFragment for the first Fragment. This will display a placeholder with instructions to pick the preview layout. To do this, right click on the placeholder and select Fragment Layout –>get_text_fragment (or open the Choose Layout dialog and choose it there). The layout components should then become visible.

Do the same to add enter_text_fragment to the layout. You should now be able to run the program in your emulator and see the combined layout.

Event handling in Fragments

Now the fun begins. We need to connect a button press in GetTextFragment to EnterTextFragment, and then send the entered text back to GetTextFragment when the Enter text button is pressed.  We could do this by simply relying on the IDs generated in the R.java file for the various view objects, but that is very bad design, for the following reason.

One of the advantages of using Fragments is that they encapsulate units of functionality, so they can be plugged in to apps wherever they are needed. If we wrote a Fragment so that it communicated directly with another Fragment, then both Fragments would always have to be present in any given app. To avoid this, we need to ensure that the only inter-Fragment communication occurs via the Activity that contains them. Doing this requires a fair bit of code, but it’s worth it to preserve modularity (or so I’m told :-)).

First, let’s see how to communicate the Get text button click event to MainActivity. Here’s the modified GetTextFragment class:

package growe.ex04fragments;

import android.os.Bundle;
import android.app.Activity;
import android.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;

public class GetTextFragment extends Fragment {

	GetTextListener activityCallback;
	View getTextView;

	public interface GetTextListener
	{
		public void onGetTextButtonPressed();
	}

	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container,
			Bundle savedInstanceState) {
		getTextView = inflater.inflate(R.layout.get_text_fragment, container, false);
		Button getTextButton = (Button) getTextView.findViewById(R.id.getTextButton);
		getTextButton.setOnClickListener(new View.OnClickListener() {

			@Override
			public void onClick(View v) {
				onClickGetText(v);
			}
		});
		return getTextView;
	}

	@Override
	public void onAttach(Activity activity) {
		super.onAttach(activity);
		try {
			activityCallback = (GetTextListener) activity;
		} catch (ClassCastException e) {
			throw new ClassCastException(activity.toString() +
					" must implement GetTextListener");
		}
	}

	public void onClickGetText(View view) {
		activityCallback.onGetTextButtonPressed();
	}

	public void setText(String text)
	{
		TextView displayed = (TextView) getTextView.findViewById(R.id.displayedText);
		displayed.setText(text);
	}
}

On lines 26 to 33 we’ve added an event handler for getTextButton clicks. We have to do it this way rather than by simply adding an ‘On Click’ handler to the button’s properties in the Eclipse Graphical Layout screen, since the latter adds the event handler to the containing Activity class rather than the Fragment class. If this code looks a bit complicated, don’t worry, as it’s always pretty much the same so you can just copy and paste it.

Ultimately, clicking getTextButton calls onClickGetText() on line 48. To see what this does, we have to back up a bit and see how the Activity communicates with Fragments it contains.

When the Activity starts up and loads the Fragment view from the XML file, it calls the onAttach() method of the Fragment class. We need to override onAttach() to provide the connection, if we want to call a method in the Activity when an event occurs in the Fragment.

We do this by defining an interface called GetTextListener on line 17. This interface specifies a method that the Activity must implement, so that it can be called in response to the button click event. So how does this all fit together?

When the Fragment is loaded, the Activity calls onAttach(), passing a reference to itself in as an argument. We attempt to save this reference on line 41 by casting it to GetTextListener. If this succeeds, this means that the Activity does implement this interface and all is well. If it fails, we throw an exception which stops the app.

Now that we have a reference to the parent Activity, we can use it on line 49 to call the onGetTextButtonPressed() method in that Activity. It is then the Activity’s responsibility to communicate with the EnterTextFragment to enable its Send text button.

To see how this works, here’s the modified MainActivity class:

package growe.ex04fragments;

import growe.ex04fragments.EnterTextFragment.EnterTextListener;
import growe.ex04fragments.GetTextFragment.GetTextListener;
import android.os.Bundle;
import android.app.Activity;

public class MainActivity extends Activity
implements GetTextListener, EnterTextListener
{

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
	}

	@Override
	public void onGetTextButtonPressed() {
		EnterTextFragment enterTextFragment =
				(EnterTextFragment) getFragmentManager().
				findFragmentById(R.id.enter_text_fragment);
		enterTextFragment.enableSendTextButton();
	}

	@Override
	public void enterTextButtonPressed() {
		EnterTextFragment enterTextFragment =
				(EnterTextFragment) getFragmentManager().
				findFragmentById(R.id.enter_text_fragment);
		GetTextFragment getTextFragment =
				(GetTextFragment) getFragmentManager().
				findFragmentById(R.id.get_text_fragment);
		getTextFragment.setText(enterTextFragment.getEnteredText());
	}

}

On line 9, we implement GetTextListener (and an interface for the other fragment, but we’ll get to that in a minute), and on line 19 we implement the onGetTextButtonPressed() method. Here, we retrieve the EnterTextFragment object and call a method (which we’ll see in a minute) in that Fragment to enable its Send text button.

Now for the revised version of EnterTextFragment:

package growe.ex04fragments;

import android.os.Bundle;
import android.app.Activity;
import android.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;

public class EnterTextFragment extends Fragment {
	View enterTextView;
	EnterTextListener activityCallback;

	public interface EnterTextListener {
		public void enterTextButtonPressed();
	}

	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container,
			Bundle savedInstanceState) {
		enterTextView = inflater.inflate(R.layout.enter_text_fragment, container, false);
		Button sendTextButton = (Button) enterTextView.findViewById(R.id.sendTextButton);
		sendTextButton.setEnabled(false);
		sendTextButton.setOnClickListener(new View.OnClickListener() {

			@Override
			public void onClick(View v) {
				sendTextButtonClicked(v);
			}
		});
		return enterTextView;
	}

	@Override
	public void onAttach(Activity activity) {
		super.onAttach(activity);
		try {
			activityCallback = (EnterTextListener) activity;
		} catch (ClassCastException e) {
			throw new ClassCastException(activity.toString() +
					" must implement EnterTextListener");
		}
	}

	public void enableSendTextButton()
	{
		Button sendTextButton = (Button) enterTextView.findViewById(R.id.sendTextButton);
		sendTextButton.setEnabled(true);
	}

	public void sendTextButtonClicked(View view)
	{
		Button sendTextButton = (Button) enterTextView.findViewById(R.id.sendTextButton);
		sendTextButton.setEnabled(false);
		activityCallback.enterTextButtonPressed();
	}

	public String getEnteredText()
	{
		EditText enteredText = (EditText) enterTextView.findViewById(R.id.enteredText);
		return enteredText.getText().toString();
	}
}

On line 47 you see the enableSendTextButton() method that is called from the Activity. Thus the event is generated in GetTextFragment which calls a method in MainActivity which in turn calls a method in EnterTextFragment. The two Fragments don’t communicate with each other directly.

To finish things off, the user enters some text in EnterTextFragment and then clicks the Send text button to send the text back to GetTextFragment. The process is much the same as the one we’ve just described, so we’ll go through it quickly.

In EnterTextFragment, we attach an event handler for the Send text button on lines 26 to 32. EnterTextFragment defines its own interface called EnterTextListener, which MainActivity must also implement. When EnterTextFragment is loaded, MainActivity is connected to it the same way (line 37), so when Send text is clicked, sendTextButtonClicked() on line 53 is called. We disable the Send text button, then call enterTextButtonPressed() in MainActivity.

If you go back to MainActivity’s code above, this method is on line 27. We need to retrieve the entered text from EnterTextFragment and send it to GetTextFragment, so we need references to both Fragments. On line 34 we call getEnteredText() from EnterTextFragment (line 60 in EnterTextFragment’s code) and pass the retrieved String to GetTextFragment’s setText() method (line 52 in GetTextFragment’s code above). Thus again, the two Fragments do not communicate directly.

This example, of course, is artificial; you’d never write an app that does something that simple in real life. But it does allow you to see how the mechanics of using Fragments work.

A more realistic example would change the layout depending on whether the device was viewed in portrait or landscape mode. We might display both fragments side by side in landscape, but only one Fragment at a time in portrait. More on that in another post.

Android: implicit intents

We’ve seen how to use an explicit intent to start a second Activity from within an existing Activity by giving the name of the second Activity’s class explicitly. It is also possible to start another Activity without naming it. The rationale behind this idea is that one or more activities might exist on a given device that can perform the required action. For example, if we want to view a web page, there could be several different browsers which can do this, and the user might like to select different ones for different pages. We can use an implicit intent to do this.

To use an implicit intent, an Activity that can handle a given type of request must contain an intent-filter that specifies the type(s) of actions that Activity can perform. The intent-filter can be specified in the Java code, but it’s more usual to include it in the AndroidManifest.xml file for the app. There are many predefined action types for an intent-filter, but to keep things local, we’ll show an example in which we define our own action type which is used by two alternative Activities within the same app.

The example app we’ll use is just a copy of the explicit intent app we used before with another Activity called OtherTextActivity added to it. The layout of OtherTextActivity is the same as that of GetTextActivity, so the user can enter some text and press a button to send the text back to StartActivity.

Before we look at the Java code for creating an implicit Intent, we need to define the intent-filters for  the Activities. Up to now, we’ve let Eclipse generate the AndroidManifest.xml file and haven’t altered it. To insert an intent-filter we do need to edit it. This can be done in Eclipse either by using the GUI in the Application tab or by directly writing the XML code. Let’s see how to use the Application editor to add it.

First, find the Activity (we’ll select GetTextActivity first) to which you want to add the intent-filter in the box at the lower left. Click on it and then click on Add… to get the dialog in which you can double-click on Intent filter. Now click on the new Intent filter, then click Add… again. We’ll need to add an action and a category to the intent-filter. Double-click on Action first, and you’ll see a Name field with a drop-down menu appear. If you open this menu, you’ll see a list of all the built-in action names you can give this intent-filter. However, we’re going to create our own custom action, so just enter GET_TEXT_ACTION in the Name box.

[As a side note, we cannot use a string resource to specify an action’s name. This is a bit of a pain, since it means we need to hard-code these strings both in the XML and in the Java code.]

Now add a category to the intent-filter and this time select android.intent.category.DEFAULT from the drop-down list. Have a look at the XML code file to see the code that has been added. You should see something like this:

        <activity
            android:name="com.example.ex03implicitintent.GetTextActivity"
            android:label="@string/title_activity_get_text" >
            <intent-filter>
                <action android:name="GET_TEXT_ACTION"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </activity>

Now repeat the process for OtherTextActivity. That completes the definition of the intent-filters. Now over to the Java code. We won’t need to change the code in either GetTextActivity or OtherTextActivity, but we do need to change how the Intent defined in StartActivity is created. We do this by changing the event handler for the getTextButton as follows:

	public void onClickGetText(View view) {
		Intent getTextIntent = new Intent();
		getTextIntent.setAction("GET_TEXT_ACTION");
		List<ResolveInfo> activities = getPackageManager().
				queryIntentActivities(getTextIntent, PackageManager.MATCH_DEFAULT_ONLY);
		boolean isIntentSafe = activities.size() > 0;
		if (isIntentSafe)
		{
			startActivityForResult(getTextIntent, GET_TEXT_ACTIVITY);
		}
	}

We create the Intent with an empty constructor. Then we set the action that the Activity to which this Intent is to be sent must support. Next, we retrieve a List of Activities that support the action requested in getTextIntent. The second argument in queryIntentActivities() says we’re looking only for Activities that have a DEFAULT category.

If there are any Activities that support the requested action, the size of this list will be greater than zero, so we test for that and then call startActivityForResult() only if there are some eligible Activities. If there is only one such Activity, it is started immediately. If there are more than one (as is the case here), Android will display a chooser dialog asking the user to select which Activity should be used.

One cautionary note here: using an implicit intent to start an Activity that returns data is fine, but if you’re doing this with Activities that you didn’t write, beware that often different Activities that support a given action might return different types of data (or none at all). In such a case, it’s better to use an explicit intent and start up an Activity whose behaviour you know, so you can handle the returned data properly.

Android: explicit intents

The Intent class is used in Android programs to communicate between various types of processes. We’ll consider the case where an Intent is used to start one Activity from within another Activity, and another Intent is used to send data back from the second Activity to the first.

Create a new project in Eclipse as described in our previous post. Add a main Activity called StartActivity which is displayed when the app starts. The layout of this Activity consists of:

  • a Button with ID getTextButton that has the caption “Get text”
  • a TextView with the text “You entered:”
  • another TextView, with ID enteredText, initially blank

When the button is pressed, a second Activity, called GetTextActivity, will start that will have the following layout:

  • a horizontal LinearLayout (we’ll get to these in a later post) which contains:
    • a TextView with the text “Enter your text:”
    • an EditText, with ID userText, into which the user can type some text
  • a Button with ID sendTextButton and caption “Send text”

All of this can be set up using Eclipse’s graphical UI editor, which will create the corresponding code in the various XML files automatically.

The idea is that the user presses the “Get text” button to show the second Activity, then types some text into the EditText box and presses the “Send text” button. This returns the app to the first Activity, which displays the entered text in the enteredText TextView.

In order to do this, we need to use one Intent to start GetTextActivity from within StartActivity and another Intent to send the entered text back from GetTextActivity to StartActivity.

Here’s the code for StartActivity:

package com.example.ex02explicitintent;

import android.os.Bundle;
import android.app.Activity;
import android.content.Intent;
import android.view.View;
import android.widget.TextView;

public class StartActivity extends Activity {

	static final int GET_TEXT_ACTIVITY = 0;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_start);
	}

	public void onClickGetText(View view) {
		Intent getTextIntent = new Intent(getBaseContext(), GetTextActivity.class);
		startActivityForResult(getTextIntent, GET_TEXT_ACTIVITY);
	}

	@Override
	protected void onActivityResult(int requestCode, int resultCode, Intent data) {
		if (resultCode != RESULT_OK)
		{
			return;
		}
		if (requestCode == GET_TEXT_ACTIVITY)
		{
			String userText = data.getStringExtra(getString(R.string.userTextTag));
			TextView enteredText = (TextView) findViewById(R.id.enteredTextView);
			enteredText.setText(userText);
		}
	}
}

The onClickGetText() (lines 19-22) method is the event handler for getTextButton. It illustrates how to create an explicit Intent, which is an Intent that creates a specifically named Activity. (There are also implicit Intents). Here we want a GetTextActivity to start in response to the button press, so we name this class explicitly in the Intent constructor.

We then start the Activity by calling startActivityForResult(). The ‘ForResult’ in the method name means that we expect the Activity to return a result after it finishes running. If we just wanted to start an Activity with no return data, we can call startActivity().

The startActivityForResult() method takes the Intent as its first argument, and an int label (defined on line 11) as its second argument. We need a label because if we start more than one Activity that will return data, they all call the same method onActivityResult() when they return, so we need to tag each Activity so we know which one is returning the data. (OK, since we’re creating only one Activity here, technically we don’t need a tag, but in the more general case we will so it’s a good idea to get into the habit of adding the tag.)

Before we see how to handle the returned result, we need to look at the other Activity to see how it sends back its data. Here’s the code for GetTextActivity:

package com.example.ex02explicitintent;

import android.os.Bundle;
import android.app.Activity;
import android.content.Intent;
import android.view.View;
import android.widget.EditText;

public class GetTextActivity extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_get_text);
	}

	public void onClickSendText(View view) {
		EditText userEditText = (EditText)findViewById(R.id.userText);
		String userText = userEditText.getText().toString();
		Intent userIntent = new Intent();
		userIntent.putExtra(getString(R.string.userTextTag), userText);
		setResult(RESULT_OK, userIntent);
		finish();
	}
}

This Activity contains only the event handler for the sendText button. We retrieve the EditText control by using its ID and extract the text from it (lines 18-19). Then we create a new Intent. This time, we’re not starting a new Activity, so the Intent doesn’t have an Activity specified in its constructor. What we do want to do is send back userText to StartActivity. We can attach data to an Intent by adding Extras to it. Each Extra consists of a key (which is a String) and a corresponding value, which here is just userText. Note that we’ve defined the key as a string resource (by adding it to the strings.xml file) and retrieved it on line 21 using getString() which looks up a string using its ID. We do it this way since we’ll need to use the same key back in StartActivity to extract the Extra from the Intent.

After adding userText to the Intent, we use setResult() on line 22 to attach the Intent to the GetTextActivity. The first argument to setResult() is a result code to indicate the status of GetTextActivity when it finishes. This result code is also sent back to StartActivity and can be checked to determine what to do with the returned Intent.

Finally, we call finish() to shut down GetTextActivity and send the Intent back to StartActivity.

Returning to the code above for StartActivity, the onActivityResult() method is called when GetTextActivity finishes. The arguments in this method are:

  • requestCode: the tag that was attached to the Activity that has just finished
  • resultCode: the result code that was set in the Activity that has just finished
  • data: the Intent returned by the other Activity

On line 26, we check the resultCode to ensure that GetTextActivity finished properly. Then we check the requestCode to ensure that it really is GetTextActivity that is calling onActivityResult(). If so, then we extract the userText from the Intent, again using the string resource for the tag. Then we retrieve the enteredText TextView control and set its text to userText.

Android programming: a simple activity

To get started programming an Android application, I’m assuming you have the Eclipse IDE with Android Developer Tools (ADT) installed. You can get this package here. To test your apps, you’ll need to set up one or more Android emulators, which you can do using the Android Virtual Device Manager from within Eclipse (there should be an icon on the default toolbar, or you can get it from the Window menu).

You may find that the emulator takes ages (well, several minutes) to start up. If so, and if you have the right kind of Intel processor, you should consider installing the Intel Hardware Acceleration Execution Manager (HAXM). Once installed (assuming your system supports it), select the Intel Atom option for the CPU/ABI in the Android Virtual Device Manager when setting up your emulator. This cuts the startup time down to a few seconds.

Assuming you’ve got everything installed, let’s create an Android app. In Eclipse, click the ‘New’ icon and select Android–>Android Application Project, then Next. Enter the app, project and package names in the next dialog, then click ‘Next’ again. In the Configure Project dialog, we’ll start with the most basic app, so uncheck the ‘Create custom launcher icon’ and ‘Create activity’ boxes, then click Finish. Eclipse will now create a skeleton project.

For example, suppose we want to create a simply app that displays a button that toggles its text between ‘Off’ and ‘On’ when you click it. We’ll call the package growe.ex01onoff.

Activities

You won’t be able to run this project, since there’s no Java source code yet, so the first thing we need to do is add some. An Android app is built using one or more Activity objects, so we’ll add an Activity to our project. To do this, right click on the package in the Package Explorer and select ‘New…’, then pick Android–>Android Activity and Next. Then select ‘Blank activity’ and Next. In the Blank Activity dialog, give the activity a name such as OnOffActivity and accept the generated layout name and title. Click the Launcher Activity box since we want this activity to be launched when the app starts.

You can click Finish here, since clicking Next just gives you a Preview dialog where you can see the pending changes to the various files in your project that adding this activity will make.

You should now find that Eclipse has created a Java file called OnOffActivity.java, with the following code:

package growe.ex01onoff;

import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;

public class OnOffActivity extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_on_off);
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		// Inflate the menu; this adds items to the action bar if it is present.
		getMenuInflater().inflate(R.menu.on_off, menu);
		return true;
	}

}

The onCreateOptionsMenu() method (not surprisingly) creates a menu, but we don’t want one in this simple app so you can safely comment out this code for now.

The onCreate() method is called when this Activity starts, but since an app can have several Activities, how does it know that we want this one to be the startup Activity? The answer is in the AndroidManifest.xml file, which contains the lines:

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="growe.ex01onoff.OnOffActivity"
            android:label="@string/title_activity_on_off" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

Within the activity tag, we see the name of the OnOffActivity and an intent-filter. The action of  android.intent.action.MAIN indicates that it is the entry point of the app. (We’ll worry about intents later.)

Resources

Going back to the Java code, the call to setContentView() draws the user interface. The argument of this call illustrates the way apps refer to resources that are defined elsewhere, usually in XML files. Every Android app creates a Java class called R. The source code for R can be found in the gen folder and, since it’s generated by Eclipse, it shouldn’t be edited since any changes will just be overwritten. Within the R class are defined other classes that contain int codes referring to the resources that are defined in the various XML files within the res folder. Within R, you’ll find a class called layout which contains an int named activity_on_off. If you look within the res folder, you’ll find a layout folder which contains the file activity_on_off.xml. This file contains the definitions of the user interface elements that are to be drawn on the screen when this layout is used. The file generated by Eclipse when an activity is created contains a single TextView which displays the text ‘Hello world!’.

We want the interface to display a button instead. We could do this by editing the XML file directly, but Eclipse has a graphical editor which allows interfaces to built visually. To do this, open the activity_on_off.xml file and click on the Graphical Layout tab at the bottom. Click on the TextView and delete it, then select a button from the Form Widgets menu on the left and drag it onto the window. You’ll notice that a Properties box on the right displays all the properties of this button that can be set. We want the button to start off displaying the caption ‘Off’. One way of doing this is just to write Off in the Text property, but there is a more general way of doing this using a String resource. 

Why should we bother with this more general way of defining strings? A major reason is that it makes apps easier to internationalize by writing strings in different languages. We can write a set of string resources for each language we want to support and then just load in the correct set of strings when we change languages. This works only if each string is given a resource name rather than hard coded.

To add a new String resource, open the res–>values–>strings.xml file. If you look at the XML code first, you’ll see several strings already defined. To add a new String, go to the Resources tab at the bottom, click the Add… button and then double-click String in the dialog. Give the resource a name like offText and a value of Off. While you’re at it, add another String resource called onText with a value of On, which we’ll use later. Make sure to save strings.xml before proceeding.

Now go back to activity_on_off.xml and select the button again. In the properties box click the … button for the Text property and you should see a list of all the string resources defined in strings.xml. Select offText, and the button’s caption should now be Off.

At this point you can run the app in your emulator and you should see the white screen with the Off button on it. Pressing the button has no effect, of course, because we haven’t added an event handler to the button yet.

Event handlers

Unlike many other IDEs, it seems that Eclipse doesn’t provide any shortcuts to adding event handlers to components, so the only option is just to write in the code by hand.

There are two ways of attaching an event handler to a button. The easiest is to find the On Click property of the button (listed in the View section in the properties box) and provide the name of a method that will handle the click event. This method must be placed in the Activity class that contains the button in its layout. If we specify the event handler as toggleOnOff, then we can modify the OnOffActivity class as follows:

package growe.ex01onoff;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.app.Activity;

public class OnOffActivity extends Activity {
	boolean on = false;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_on_off);
	}

	public void toggleOnOff(View view) {
		Button onOffButton = (Button) view;
		onOffButton.setText(on ? R.string.offText : R.string.onText);
		on = !on;
	}
}

The event handler must always have the signature shown. It must be public, void, and have a single argument of class View (which is the View that triggered the event; in this case, the button). Here, we’ve defined a boolean variable on which stores the state of the button and we set the text of the button according to the state of this variable, then toggle the variable.

The second way of attaching an event handler to a button is done entirely within the Activity class’s onCreate() method:

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_on_off);

		Button onOffButton = (Button) findViewById(R.id.onOffButton);
		onOffButton.setOnClickListener(new OnClickListener() {

			@Override
			public void onClick(View view) {
				Button button = (Button) view;
				button.setText(on ? R.string.offText : R.string.onText);
				on = !on;
			}
		});
	}

First, we need to find the button, which we do using the findViewById() method on line 6. Each UI component is assigned an ID number in the R class, and this method retrieves a reference to the component using this ID. Next, we attach an OnClickListener to the button using setOnClickListener(). Within this listener, we override the onClick() method, which becomes the event handler, so it contains the same code as we had earlier.