Digging into Android: Intents and Sharing

If you have spent much time with Android development, you have likely run across Android’s Intent object. Intents show up pretty regularly in the official Android tutorials and frequently appear in StackOverflow answers. Until recently, all I knew about them was that they were used in everything from navigation within an application to media playback and interacting with external applications.

Despite the many uses of Intents, I never ran across a great explanation of their purpose until I dug into the Android documentation. This may be because Intents tend to work quite smoothly when used in the right circumstances. It is all too easy to copy-paste a few lines from a StackOverflow answer and never come back to it because things seem to just work.

However, after doing this a few times myself, my lack of understanding kept nagging me to dig deeper. My goal with this post is to share what I have learned about these powerful abstractions and to provide a useful and interesting example in the process.

Intents

Let’s start out by getting a baseline definition. Android’s official documentation defines an Intent as “an abstract description of an operation to be performed”…helpful. Further on in the documentation, it gives the more approachable description that “[an Intent’s] most significant use is in the launching of activities, where it can be thought of as the glue between activities.” Okay, that makes more sense and explains why they seem to be involved in so many things.

Intents come in two types: explicit and implicit.

Explicit

If you’re developing a multi-activity application, you will likely use a lot of explicit Intents. These are used to start an application component within your control–a class or activity to which you have access. In their most basic form, explicit Intents are straightforward.

Here is a simple example of starting a new activity:


public class MainActivity extends AppCompatActivity {
  ...
  public void startOtherActivity() {
    Intent intent = new Intent(this, OtherActivity.class);
    startActivity(intent);
  }
}

Note that when we create the Intent object, we explicitly state which component to start. This component can be an Activity or another class like a Service or a Broadcast Receiver for which you can give the fully qualified class name.

Implicit

Implicit Intents are meant for when you want a user to be able to take action without necessarily implementing the functionality yourself. Think about when you are using an app and want to share content–an image or article, for example. With Android Marshmallow, the Android operating system presents the Direct Share menu that displays all of your apps capable of sharing that content. When you select which app to share with, an activity of that app will be launched to allow you to complete your action.

Implicit Intents are a little trickier to grasp, so let’s work through an example. We will walk through both sides of the sharing interaction–sender and receiver–to get a better understanding.

Sending Example

Let’s start out by building a simple app that can share some text to other apps. I created a fresh project in Android Studio with an empty main activity. In the main activity’s layout XML, I added an EditText element where text will be entered and a Button that will trigger the sharing action. Then in the main activity’s onCreate() method, I wired up the button’s onClickListener() method to pass the EditText’s input to a method that we will define later on.


public class MainActivity extends AppCompatActivity {

  private EditText textInput;
  private Button shareButton;

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

    textInput = findViewById(R.id.text_input);

    shareButton = findViewById(R.id.share_button);
    shareButton.setOnClickListener(v -> startShareIntent());
  }

  private void startShareIntent() {
    ...
  }
}

In this startShareIntent() method, we will build up and start an implicit Intent for the “Send” action. Note that we won’t explicitly state which component will resolve this Intent because the user gets to decide.


private void startShareIntent() {
  Intent sendIntent = new Intent();
  sendIntent.setAction(Intent.ACTION_SEND);
  sendIntent.putExtra(Intent.EXTRA_TEXT, textInput.getText().toString());
  sendIntent.setType("text/plain");

  if (sendIntent.resolveActivity(getPackageManager()) != null) {
    startActivity(sendIntent);
  }
}

The first line of this method creates a new Intent object. The empty constructor is what tells us that we are using an implicit Intent.

The next three lines describe the payload of the Intent–the content itself and some meta-data about the content. Every application declares which, if any, kinds of Intents they are willing to accept by filtering on the Intent’s meta-data (more on this later).

The Action parameter describes the purpose of the Intent. In our case, we are using the “Send” action, which is how Android describes a “Share” Intent. Other types of actions include opening a URL in a browser, starting a web search, media playback, and more; see the full list of predefined actions here.

The Type parameter describes the type of content being sent. For example, an audio app might accept .mp3 format files in a media Intent, but not .mov files. The Extra parameter is where you pass along any actual content and other information that the receiving application may need.

In the remainder of the method, we submit our Intent. To do this, we call the startActivity() method with our Intent. When using implicit Intents, it is important to first check that the user’s device has any apps that would be willing to resolve this Intent. If you submit an Intent that cannot be resolved, your app will crash. We check for this by testing that the Intent’s resolveActivity() method does not return null. If the result is non-null, it is safe to submit the Intent.

That’s all there is to a basic approach to sharing content between applications. You can test out this implementation by running the example and typing some text in the field. When you tap the button, it should present you with the Direct Share menu. In a typical simulator, this menu will include options like, “Copy to clipboard,” “Messages,” and “Gmail.”

Receiving Example

Now, let’s build an app that’s capable of receiving the Intent sent from our first example. Again, I created a fresh Android Studio project with an empty main activity. This time, the main activity’s layout XML will need only a TextView element capable of displaying our received text.

The logic for getting the received Intent and wiring it up to the TextView is easy enough. In the activity’s onCreate() method, we declare an Intent object and then assign it to the value of the getIntent() method, which is included in the Activity class.

The getIntent() method returns whatever Intent that launched that activity–so if you open the app through the Direct Share menu, you will get a “Send” Intent. If you open it from the phone’s application menu, you will get a more generic Intent. Because of this, we can check that we indeed have a Send Intent. Then we can access the Intent’s content as you see on line 15 below and wire it up to the TextView’s text property.


public class MainActivity extends AppCompatActivity {

  private Intent intent;
  private TextView textView;

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

    intent = getIntent();
    textView = findViewById(R.id.intent_input);

    if (intent.getAction() != null && intent.getAction().equals(Intent.ACTION_SEND)) {
      String intentContent = intent.getStringExtra(Intent.EXTRA_TEXT);
      textView.setText(intentContent);
    }
  }
}

The trickier part of receiving an implicit Intent is declaring what kinds of Intents your application will accept. We do this by adding an Intent filter inside the activity in the application’s manifest file as shown below.


<activity android:name=".MainActivity">
  ...
  <intent-filter>
    <action android:name="android.intent.action.SEND"/>
    <category android:name="android.intent.category.DEFAULT"/>
    <data android:mimeType="text/plain"/>
  </intent-filter>
</activity>

You will recognize the action tag within the Intent filter. This is how we specify exactly which kinds of actions the app is prepared to handle.

The mimeType property in the data tag allows us to specify that we only accept Send Intents if they send plain text. That way, if another app creates a Send Intent that attempts to share an image, our app won’t advertise itself since our simple text view setup would not know how to handle an image.

In order to receive implicit Intents, an Intent filter must specify the default category. This category is set by the activity’s startActivity() method. Omitting the default category will lead to never receiving any implicit Intents.

When an implicit Intent makes it through one of your app’s Intent filters, it will launch the activity that it is declared within. With this in mind, an Intent filter like this should be in an activity that is specifically designed to handle the expected Intent, not your main activity. But since this sample app’s only purpose is to handle these incoming Intents, the main activity will do.

Finally, we can test everything together. If you run our first sample app, enter some text, and tap the button, you should see our receiver sample app within the Direct Share menu. If you select the receiver app in this menu, it will launch the app, and the text view should display the text that was output from the sender app.