Mobile and Ubiquitous Computing
Labsheet 5 – Intents, Bundles and Threads
In expectation of your assessment in Week 6, this week will be a relatively light week in terms of work on this labsheet. So far, we’ve focused on single activities. You start your application, do something on one activity and then that’s it. (From Labsheet 4, you should now also have a basic idea of how you can lay things out in your applications.) Obviously, real apps tend not to work like this. There are normally main activities chained together to produce complex and useful interactions. This week we’re going to focus on extending our application so that it comprises two activities.
Multiple Activities, Multiple Intents
Recall, an activity is a single focused thing your user can do. If you chain multiple activities together to do something more complex, it’s called a task.
Here you’re going to build an app that contains two activities:
1. Typing a message
2. Displaying the message that was typed
Let’s get started:
The mains steps to get going will be:
1. Create a basic app with a single activity and layout
2. Add a second activity and layout
3. Get the first activity to call the second activity
4. Get the first activity to pass data to the second activity
The Structure of the App:
The app contains two activities and two layouts:
1. When launching the app, CreateMessageActivity is started
This uses the layout activity_create_message.xml
2. The user clicks on a button in CreateMessageActivity
This launches activity ReceiveMessageActivity, which uses layout
activity_receive_message.xml
First things first:
Create a new ‘Add No Activity’ project an application named “Messenger”. You’ll need to create a blank activity called “CreateMessageActivity” with a layout called “activity_create_message”. To do this click File → New → Activity → Empty Activity. Set the activity name to CreateMessageActivity and the layout name to activity_create_message. Tick ‘Launcher Activity’ for this activity and then click Finish. You should now have an activity ready to use as normal.
In activity_create_message.xml in res → layout in the project tree, remove (using the Design view), add a Button and an EditText element. An EditText element gives you an editable text field you can use to enter data.
As in last week, update strings.xml to hold the relevant value for the text of the send button (“Send Message”), and create the method onSendMessage() in CreateMessageActivity.java, that will fire
when the button is pressed. Remember, this will take a View object argument. (Think back, or look back, to last week if you’re stuck.) You should end up with something that looks a little like this in
your layout XML:
So, now we have the basics to create a message, we must create another activity to receive a message. To do so File → New → Activity, choose Blank Activity. Name this one ReceiveMessageActivity, and name the layout activity_receive_message. Ensure that this is
placed in the same package as the other activity (it should, by default). On clicking Finish, ReceiveMessageActivity.java and activity_receive_message.xml will have been created. We now have two activities in our app. On this new activity add a TextView with the ID messageReceived.
Given this addition of a new activity and a new layout, the configuration of the app will have changed. You can find details of this in the AndroidManifest.xml file, in the “manifests” folder. All activities must be declared in this file. If they are not, the system will not know that the activity
exists. Declaration happens inside the
android:name=”.ReceiveMessageActivity”
It is important here that class name is prefixed with a “.” This is as Android combines the class name with the name of the package to derive the fully qualified class name. Android added this new activity to the manifest file for us automatically, but be careful if you are doing things manually, as you’ll need to edit the manifest file yourself.
Progressing with intent
So, we’ve created an app with two activities, each activity which has its own layout. When we run this, our first activity, CreateMessageActivity, will run. What we need to now do, is get CreateMessageActivity to call ReceiveMessageActivity when the user clicks the send message button. To do this, we need to use an intent. Intents allow you to bind together activities at runtime, so for example, if one activity wants to start a second activity, it does so by sending an intent to Android. Android will then start the second activity and pass it the intent.
To create and send an intent, you can do so in a few lines of code. First:
Intent intent = new Intent(this,
The first argument specifies which activity instance the intent is coming from (this one!), and the
second argument is the name of the activity that needs to receive the intent (i.e, ReceiveMessageActivity), so it becomes:
Intent intent = new Intent(this, ReceiveMessageActivity.class) Following the creation of the intent, you can then pass it to Android as follows:
startActivity(intent)
So, let’s apply this to get things going with the SendMessageActivity. In the onSendMessage()
method, the method that runs when the button is clicked in the app, create an intent, and call the startActivity() method on this intent.
Once you’ve done this, run the app, click the send message button, and marvel in the wonders that occur. Oh no… wait a minute… our message has not appeared in the new screen. Let’s fix this.
Waiting for that all important message
We need to tweak three things to get that text:
3. Update CreateMessageActivity.java so that it gets the text that the user inputs. It needs to add the text to the intent before it sends it.
4. Update ReceiveMessageActivity.java so that it displays the text in the intent.
Now, we need to put some extra information into our intent, to pass the data between the two
activities. To do this, we can use the putExtra() method, before we call startActivity. For example:
intent.putExtra(“message”, value)
Here, message is the String name for the value we’re passing in, and value is the value. Here, value can have many possible types, and doesn’t necessarily have to be limited to a string. For this activity you want the value to be the current text in the EditText.
We also need some way to retrieve the extra information from the intent in ReceiveMessageActivity. To do this, we can use the method getIntent(). This should return
the intent that started the activity, and this can be used to retrieve any extra information that was sent along with it. This all depends on the type of information that was sent. For example, if you know that the intent includes a String value with a name “message”, you could do the following:
Intent intent = getIntent();
String text = intent.getStringExtra(“message”);
If you wanted to retrieve a different type of data, you could use the following:
int intNum = intent.getIntExtra(“name”, default_value); where default_value specifies what int value should be used as a default.
So, given all this, you now need to:
1. Update the onSendMessage() method of the CreateMessageActivity code to send the text that the user enters on screen and adds it to an intent.
A. Tip1:remembertogettheelementusingthefindViewById()method,thattakesone argument that reference R.id.message, that we looked at in the previous lab sheets.
B. Tip2:Afterdeclaringtheintent,theputExtramethodwillcontaintwoarguments:
1. ReceiveMessageActivity.EXTRA_MESSAGE (constant for the name of the extra info)
2. The contents of the EditText element. Remember that getText returns an Editable and not a String. Do not worry about error messages at this point, these will be amended
when we create the constant in ReceiveMessageActivity.
C. Tip3:Don’tforgettostarttheactivitywiththeparticularintent.
2. Get the ReceiveMessageActivity to access the information in the intent. You will have to edit ReceiveMessageActivity’s onCreate() method, as this is the method that gets called as soon
as the activity is created. Once you have done this, test your app to see if messages can be passed between the activities. If not, have a look at the sample code and try and work out where things have gone wrong.
Going beyond within app activities
We can send the message instead to a messaging app, making this activity sequence potentially more useful. To do this, instead of initialising our intent with a particular class, we can instead initialise it with a particular action, for example:
Intent intent = new Intent(Intent.ACTION_SEND);
to send a message, or
Intent intent = new Intent(Intent.ACTION_WEB_SEARCH);
to perform a web search.
Once you’ve specified the action you wish to use, extra information can be added as follows:
intent.setType(“text/plain”);
intent.putExtra(Intent.EXTRA_TEXT, messageText);
where messageText is the text you wish to send. This will set the MIME data-type to “text/plain”. You can continue adding extra information to the intent, such as subject and so on. Given this,
amend the intent in CreateMessageActivity that you have already created to send a message via another app.
Testing across activities
One of the things you need to get used to is getting your code to pass tests that I have written. This is because when you come to your assessment in Week 12, amongst other things I will be expecting that your code to pass tests that I specify.
There are two simple tests to run on this week’s code. One simply checks the set-up of the CreateMessageActivity to make sure the EditText instance, message and Button instance,
send are visible and in the correct place on the screen in relation to each other:
@Rule
public ActivityScenarioRule
ActivityScenarioRule<>(CreateMessageActivity.class);
@Test
public void checkCreateAppearance() { onView(withId(R.id.send)).check(matches(isDisplayed())); onView(withId(R.id.message)).check(matches(isDisplayed())); onView(withId(R.id.send)).check(isCompletelyAbove(withId(R.id.message)));
}
So far so familiar. The other test I want you to test against is a test that spans across activities. Now, the important thing to know about these instrumented tests is that the tests will evaluate what
can be ‘seen’ at a particular moment. So if you expect the activity to change after an action and then test to see if a particular view on the new activity is visible then this will pass, provided that the activity has changed as expected. The starting point for the test is critical. In the @Rule we have
told Android to use create an ActivityScenarioRule before any of the tests in our test class.
This means all tests are going to start off, in this instance, in CreateMessageActivity. If you
wanted to start from a different activity (e.g., start at ReceiveMessageActivity) then it’d make
sense to create another test class for those tests. If you wanted to test an activity like ReceiveMessageActivity without having to programme instructions to get to that activity, then
you can use things like mock objects to make sure that variables the activity requires to start-up are provided before tests are run. This kind of testing is out of the scope of this module, though.
Here’s our other test:
This first of all performs a typeText on our EditText instance message in CreateMessageActivity. Watch the test carefully, you’ll see that the instrumented test simulates actually typing at human speed. Note,
though, that it is not necessary to perform a click() on the EditText instance before it starts typing. After
this, the Button instance send is clicked. You know from the code that this causes an intent to be sent to the
other activity in this app, ReceiveMessageActivity. The lines that follow test that the TextView instance
messageReceived in the ReceiveMessageActivity is visible and that its text matches what the EditText
instance message was set to in CreateMessageActivity. We don’t have to tell the test to check the activity,
because the test is ‘looking’ at what’s on the screen at the point that the check() is made. If the activity has
changed successfully this passes, if not, the test fails. In this way we can build tests that use chains of activities quite easily; you just need to remember which activity it is that you’re starting off with.
Note that TEST_MESSAGE is set as a class property:
final String TEST_MESSAGE = “Yeah, here is a test message.”;
@Test
public void checkFillAndSend() { onView(withId(R.id.message)).perform(typeText(TEST_MESSAGE)); onView(withId(R.id.send)).perform(click()); onView(withId(R.id.messageReceived)).check(matches(isDisplayed())); onView(withId(R.id.messageReceived)).check(matches(withText(TEST_MESSAGE))); }