This practical codelab is part of Unit 2: Activities and Intents for CS3040/DC3040. You will get the most value out of this course if you work through the codelabs in sequence:
In a previous section you learned about explicit intents. In an explicit intent, you carry out an activity in your app, or in a different app, by sending an intent with the fully qualified class name of the activity. In this section you learn more about implicit intents and how to use them to carry out activities.
With an implicit intent, you initiate an activity without knowing which app or activity will handle the task. For example, if you want your app to take a photo, send email, or display a location on a map, you typically don't care which app or activity performs the task.
Conversely, your activity can declare one or more intent filters in the AndroidManifest.xml
file to advertise that the activity can accept implicit intents, and to define the types of intents that the activity will accept.
To match your request with an app installed on the device, the Android system matches your implicit intent with an activity whose intent filters indicate that they can perform the action. If multiple apps match, the user is presented with an app chooser that lets them select which app they want to use to handle the intent.
In this practical you build an app that sends an implicit intent to perform each of the following tasks:
Sharing—sending a piece of information to other people through email or social media—is a popular feature in many apps. For the sharing action you use the ShareCompat.IntentBuilder
class, which makes it easy to build an implicit intent for sharing data.
Finally, you create a simple intent-receiver that accepts an implicit intent for a specific action.
You should be able to:
Button
and a click handler.Activity
.Intent
between one Activity
and another.Intent
, and use its actions and categories.ShareCompat.IntentBuilder
helper class to create an implicit Intent
for sharing data.Intent
by declaring Intent
filters in the AndroidManifest.xml
file.Intent
.Intent
that opens a web page, and another that opens a location on a map.Intent
for opening a web page.In this section you create a new app with one Activity
and three options for actions: open a web site, open a location on a map, and share a snippet of text. All of the text fields are editable (EditText
), but contain default values.
For this exercise, you create a new project and app called Implicit Intents, with a new layout.
In this task, create the layout for the app. Use a LinearLayout
, three Button
elements, and three EditText
elements, like this:
<string name="edittext_uri">http://developer.android.com</string>
<string name="button_uri">Open Website</string>
<string name="edittext_loc">Golden Gate Bridge</string>
<string name="button_loc">Open Location</string>
<string name="edittext_share">\'Twas brillig and the slithy toves</string>
<string name="button_share">Share This Text</string>
android.support.constraint.ConstraintLayout
to LinearLayout
, as you learned in a previous practical.android:orientation
attribute with the value "vertical"
. Add the android:padding
attribute with the value "16dp"
.<Linearlayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp"
tools:context=".MainActivity">
EditText
element and a Button
element. Use these attribute values:EditText attribute | Value |
|
|
|
|
|
|
|
|
|
|
Button attribute | Value |
|
|
|
|
|
|
|
|
|
|
|
|
android:onClick
attribute will remain underlined in red until you define the callback method in a subsequent task.EditText
and Button
) to the layout for the Open Location button. Use the same attributes as in the previous step, but modify them as shown below. (You can copy the values from the Open Website button and modify them).EditText attribute | Value |
|
|
|
|
Button attribute | Value |
|
|
|
|
|
|
android:onClick
attribute will remain underlined in red until you define the callback method in a subsequent task.EditText
and Button
) to the layout for the Share This button. Use the attributes shown below. (You can copy the values from the Open Website button and modify them).EditText attribute | Value |
|
|
|
|
Button attribute | Value |
|
|
|
|
|
|
activity_main.xml
code should look something like the following. The values for the android:onClick
attributes will remain underlined in red until you define the callback methods in a subsequent task.<?xml version="1.0" encoding="utf-8"?>
<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"
android:padding="16dp"
tools:context=".MainActivity">
<EditText
android:id="@+id/website_edittext"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:autofillHints="Enter a URL"
android:inputType="textUri"
android:text="@string/edittext_uri" />
<Button
android:id="@+id/open_website_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
android:text="@string/button_uri"
android:onClick="openWebsite"/>
<EditText
android:id="@+id/location_edittext"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:autofillHints="Enter the name of a location/place"
android:inputType="textCapWords"
android:text="@string/edittext_loc" />
<Button
android:id="@+id/open_location_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
android:text="@string/button_loc"
android:onClick="openLocation"/>
<EditText
android:id="@+id/share_edittext"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:autofillHints="Enter a message to share"
android:inputType="textShortMessage|text"
android:text="@string/edittext_share" />
<Button
android:id="@+id/share_text_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
android:text="@string/button_share"
android:onClick="shareText" />
</LinearLayout>
In this task you implement the on-click handler method for the first button in the layout, Open Website. This action uses an implicit Intent to send the given URI to an Activity that can handle that implicit Intent (such as a web browser).
"openWebsite"
in the activity_main.xml
XML code.Alt+Enter
(Option+Enter
on a Mac) and select Create ‘openWebsite(View)' in ‘MainActivity.MainActivity
file opens, and Android Studio generates a skeleton method for the openWebsite()
handler.public void openWebsite(View view) {
}
MainActivity
, add a private variable at the top of the class to hold the EditText
object for the web site URI.private EditText mWebsiteEditText;
onCreate()
method for MainActivity, use findViewById()
to get a reference to the EditText
instance and assign it to that private variable:mWebsiteEditText = findViewById(R.id.website_edittext);
With API level 30, if you're targeting that version or higher, your app cannot see, or directly interact with, most external packages without explicitly requesting that be allowed. We need that permission here because we want to find an installed application that can view URIs, normally a web browser. In this example, we want the open website button to send the URL to a web browser via an implicit intent.
You can request permission to see external packages by including an appropriate element in your manifest.
AndroidManifest
, inside the manifest
element but before the application
element: <queries>
<intent>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http" />
</intent>
</queries>
https
scheme to also be handled, you need another intent element: <queries>
<intent>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http" />
</intent>
<intent>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
</intent>
</queries>
openWebsite()
method that gets the string value of the EditText
:String url = mWebsiteEditText.getText().toString();
Uri webpage = Uri.parse(url);
Intent
with Intent.ACTION_VIEW
as the action and the URI as the data:Intent intent = new Intent(Intent.ACTION_VIEW, webpage);
This Intent constructor is different from the one you used to create an explicit Intent
. In the previous constructor, you specified the current context and a specific component (Activity
class) to send the Intent
. In this constructor you specify an action and the data for that action. Actions are defined by the Intent
class and can include ACTION_VIEW
(to view the given data), ACTION_EDIT
(to edit the given data), or ACTION_DIAL
(to dial a phone number). In this case the action is ACTION_VIEW
because you want to display the web page specified by the URI in the webpage variable.resolveActivity()
method and the Android package manager to find an Activity
that can handle your implicit Intent
. Make sure that the request resolved successfully.if (intent.resolveActivity(getPackageManager()) != null) {
}
This request that matches your Intent
action and data with the Intent
filters for installed apps on the device. You use it to make sure there is at least one Activity
that can handle your requests.resolveActivity
method will return null
on API 30 or above if you haven't complete task 2.2 above. For API 29 and lower, it should work without requiring the manifest to be updated.if
statement, call startActivity()
to send the Intent.startActivity(intent);
else
block to print a Log
message if the Intent
could not be resolved.} else {
Log.d("ImplicitIntents", "Can't handle this!");
}
The openWebsite()
method should now look as follows. (Comments added for clarity.)public void openWebsite(View view) {
// Get the URL text.
String url = mWebsiteEditText.getText().toString();
// Parse the URI and create the intent.
Uri webpage = Uri.parse(url);
Intent intent = new Intent(Intent.ACTION_VIEW, webpage);
// Find an activity to hand the intent and start that activity.
if (intent.resolveActivity(getPackageManager()) != null) {
startActivity(intent);
} else {
Log.d("ImplicitIntents", "Can't handle this intent!");
}
}
In this task you implement the on-click handler method for the second button in the UI, Open Location. This method is almost identical to the openWebsite()
method. The difference is the use of a geo
URI to indicate a map location. You can use a geo
URI with latitude and longitude, or use a query string for a general location. In this example we've used the latter.
"openLocation"
in the activity_main.xml
XML code.MainActivity
for the openLocation()
handler.public void openLocation(View view) {
}
MainActivity
to hold the EditText
object for the location URI.private EditText mLocationEditText;
onCreate()
method, use findViewByID()
to get a reference to the EditText
instance and assign it to that private variable:mLocationEditText = findViewById(R.id.location_edittext);
AndroidManifest
to request permission to look for external packages that can view a geo
URI (such as Google Maps). Add the following intent
element to that you added in task 2:<intent>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="geo" />
</intent>
Your manifest should now start with the following code:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="uk.aston.implicitintents">
<queries>
<intent>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http" />
</intent>
<intent>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
</intent>
<intent>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="geo" />
</intent>
</queries>
...
</manifest>
openLocation()
method, add a statement to get the string value of the EditText
called mLocationEditText
.String loc = mLocationEditText.getText().toString();
geo
search query:Uri addressUri = Uri.parse("geo:0,0?q=" + loc);
Intent
with Intent.ACTION_VIEW
as the action and addressUri
as the data.Intent intent = new Intent(Intent.ACTION_VIEW, addressUri);
Intent
and check to make sure that the Intent
resolved successfully. If so, startActivity()
, otherwise log an error message.if (intent.resolveActivity(getPackageManager()) != null) {
startActivity(intent);
} else {
Log.d("ImplicitIntents", "Can't handle this intent!");
}
The openLocation() method should now look as follows (comments added for clarity):
public void openLocation(View view) {
// Get the string indicating a location. Input is not validated; it is
// passed to the location handler intact.
String loc = mLocationEditText.getText().toString();
// Parse the location and create the intent.
Uri addressUri = Uri.parse("geo:0,0?q=" + loc);
Intent intent = new Intent(Intent.ACTION_VIEW, addressUri);
// Find an activity to handle the intent, and start that activity.
if (intent.resolveActivity(getPackageManager()) != null) {
startActivity(intent);
} else {
Log.d("ImplicitIntents", "Can't handle this intent!");
}
}
A share action is an easy way for users to share items in your app with social networks and other apps. Although you could build a share action in your own app using an implicit Intent
, Android provides the ShareCompat.IntentBuilder helper class to make implementing sharing easy. You can use ShareCompat.IntentBuilder
to build an Intent
and launch a chooser to let the user choose the destination app for sharing.
In this task you implement sharing a bit of text in a text edit, using the ShareCompat.IntentBuilder
class.
"shareText"
in the activity_main.xml
XML code.MainActivity
for the shareText()
handler.public void shareText(View view) {
}
MainActivity
to hold the EditText
.private EditText mShareTextEditText;
In onCreate()
, use findViewById()
to get a reference to the EditText
instance and assign it to that private variable:mShareTextEditText = findViewById(R.id.share_edittext);
In the new shareText()
method, add a statement to get the string value of the EditText
called mShareTextEditText
.
String txt = mShareTextEditText.getText().toString();
String mimeType = "text/plain";
Call ShareCompat.IntentBuilder with these methods:ShareCompat.IntentBuilder
.from(this)
.setType(mimeType)
.setChooserTitle("Share this text with: ")
.setText(txt)
.startChooser();
.setChoosterTitle
to a string resource.The call to ShareCompat.IntentBuilder
uses these methods:
Method | Description |
| The |
| The MIME type of the item to be shared. |
| The title that appears on the system app chooser. |
| The actual text to be shared |
| Show the system app chooser and send the |
This format, with all the builder's setter methods strung together in one statement, is an easy shorthand way to create and launch the Intent
. You can add any of the additional methods to this list.
The shareText()
method should now look as follows:
public void shareText(View view) {
String txt = mShareTextEditText.getText().toString();
String mimeType = "text/plain";
ShareCompat.IntentBuilder
.from(this)
.setType(mimeType)
.setChooserTitle(R.string.share_text)
.setText(txt)
.startChooser();
}
EditText
above the Button. The map with the location should appear as shown below.Your MainActivity
class should look like this:
package uk.aston.implicitintents;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ShareCompat;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.EditText;
public class MainActivity extends AppCompatActivity {
private EditText mWebsiteEditText;
private EditText mLocationEditText;
private EditText mShareTextEditText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mWebsiteEditText = findViewById(R.id.website_edittext);
mLocationEditText = findViewById(R.id.location_edittext);
mShareTextEditText = findViewById(R.id.share_edittext);
}
public void openWebsite(View view) {
// Get the URL text.
String url = mWebsiteEditText.getText().toString();
// Parse the URI and create the intent.
Uri webpage = Uri.parse(url);
Intent intent = new Intent(Intent.ACTION_VIEW, webpage);
// Find an activity to hand the intent and start that activity.
if (intent.resolveActivity(getPackageManager()) != null) {
startActivity(intent);
} else {
Log.d("ImplicitIntents", "Can't handle this intent!");
}
}
public void openLocation(View view) {
// Get the string indicating a location. Input is not validated; it is
// passed to the location handler intact.
String loc = mLocationEditText.getText().toString();
// Parse the location and create the intent.
Uri addressUri = Uri.parse("geo:0,0?q=" + loc);
Intent intent = new Intent(Intent.ACTION_VIEW, addressUri);
// Find an activity to handle the intent, and start that activity.
if (intent.resolveActivity(getPackageManager()) != null) {
startActivity(intent);
} else {
Log.d("ImplicitIntents", "Can't handle this intent!");
}
}
public void shareText(View view) {
String txt = mShareTextEditText.getText().toString();
String mimeType = "text/plain";
ShareCompat.IntentBuilder
.from(this)
.setType(mimeType)
.setChooserTitle(R.string.share_text)
.setText(txt)
.startChooser();
}
}
So far, you've created an app that uses an implicit Intent
in order to launch some other app's Activity
. In this task you look at the problem from the other way around: allowing an Activity
in your app to respond to an implicit Intent
sent from some other app.
An Activity
in your app can always be activated from inside or outside your app with an explicit Intent
. To allow an Activity
to receive an implicit Intent
, you define an Intent
filter in your app's AndroidManifest.xml
file to indicate which types of implicit Intent
your Activity
is interested in handling.
To match your request with a specific app installed on the device, the Android system matches your implicit Intent
with an Activity
whose Intent
filters indicate that they can perform that action. If there are multiple apps installed that match, the user is presented with an app chooser that lets them select which app they want to use to handle that Intent.
When an app on the device sends an implicit Intent
, the Android system matches the action and data of that Intent
with any available Activity
that includes the right Intent
filters. When the Intent
filters for an Activity
match the Intent
:
Activity
, Android lets the Activity
handle the Intent
itself.In this task you create a very simple app that receives an implicit Intent
to open the URI for a web page. When activated by an implicit Intent
, that app displays the requested URI as a string in a TextView
.
TextView
, delete the android:text
attribute. There's no text in this TextView
by default, but you'll add the URI from the Intent
in onCreate()
.layout_constraint
attributes alone, but add the following attributes:Attribute | Value |
|
|
|
|
|
|
MainActivity
already has this Intent
filter:<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
This Intent
filter, which is part of the default project manifest, indicates that this Activity
is the main entry point for your app (it has an Intent
action of "android.intent.action.MAIN"
), and that this Activity
should appear as a top-level item in the launcher (its category is "android.intent.category.LAUNCHER"
).<intent-filter>
tag inside <activity>
, and include these elements:<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http" android:host="developer.android.com" />
These lines define an Intent
filter for the Activity
, that is, the kind of Intent
that the Activity
can handle. This Intent
filter declares these elements:Filter type | Value | Matches |
action |
| Any |
category |
| Any implicit |
category |
| Requests for browsable links from web pages, email, or other sources. |
data |
| URIs that contain a scheme of |
Note that the data filter has a restriction on both the kind of links it will accept and the hostname for those URIs. If you'd prefer your receiver to be able to accept any links, you can leave out the element.
The application section of AndroidManifest.xml
should now look as follows:
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http" android:host="developer.android.com" />
</intent-filter>
</activity>
</application>
In the onCreate()
method for your Activity
, process the incoming Intent
for any data or extras it includes. In this case, the incoming implicit Intent has the URI stored in the Intent
data.
onCreate()
method, get the TextView for the message:TextView textView = findViewById(R.id.text_uri_message);
Intent
that was used to activate the Activity
:Intent intent = getIntent();
Intent
data. Intent
data is always a URI object:Uri uri = intent.getData();
uri
variable is not null
. If that check passes, create a string from that URI object:if (uri != null) {
String uri_string = "URI: " + uri.toString();
}
"URI: "
portion of the above into a string resource (uri_label
).TextView
to the URI:textView.setText(uri_string);
else {
textView.setText("No URI Found!");
}
This will ensure that the screen contains some text if the App is started manually."No URI Found!"
to a string resource with the name error_message
.onCreate()
method for MainActivity should now look like the following: @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView textView = findViewById(R.id.text_uri_message);
Intent intent = getIntent();
Uri uri = intent.getData();
if (uri != null) {
String uri_string = getString(R.string.uri_label) + uri.toString();
textView.setText(uri_string);
} else {
textView.setText(R.string.error_message);
}
}
To show the result of receiving an implicit Intent
, you will run both the Implicit Intents Receiver and Implicit Intents apps on the emulator or your device.
Activity
with the error message "No URI Found!"
. This is because the Activity
was activated from the system launcher, and not with an Intent
from another app.Open Website
with the default URI.Intent
filter that matches only exact URI protocol (http
) and host (developer.android.com
). Any other URI opens in the default web browser.The solution code can be found on Blackboard.
Intent
allows you to activate an Activity
if you know the action, but not the specific app or Activity
that will handle that action.Activity
that can receive an implicit Intent
must define Intent
filters in the AndroidManifest.xml
file that match one or more Intent
actions and categories.Intent
and the Intent
filters of any available Activity
to determine which Activity
to activate. If there is more than one available Activity
, the system provides a chooser so the user can pick one.ShareCompat.IntentBuilder
class makes it easy to build an implicit Intent
for sharing data to social media or email.The related concept documentation is in 2.3: Implicit intents.
Android developer documentation: