Android Channels

The Android reactive programming

The text titled "Observables on Android gives a light introduction to why we should declare all Activity/Application Components in AndroidManifest.xml. At the end of this text, we shed some light on how Android uses the Observable pattern, also, we were left with the promise to bring more light to this topic, so, the future became present...

The previous text exemplified two ways of declaring an Activity in the manifest, making use of the class name com.some.package.SomeActivity or .SomeActivity in the shortened form.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.some.package">
    <applicationsome parameters >
       <activity android:name=".SomeActivity"/>
    </application>
</manifest>

Bringing the view that, from the perspective of the observer pattern, this form of declaration implies that we subscribe the class SomeActivity (Observer) to the channel com.some.package.SomeActivity (Subject), which is the default form of subscription, where the components subscribed in this standard, which is mandatory, can only be accessed through Explicit Intents.

Explicit Intent

In an Explicit Intent, we must make it clear to the operating system which application component we want to interact with, that is, the knowledge to the target class, being an Activity, Service, or Broadcast Receiver is required.

val intent = Intent(context, SomeActivity::class.java)
startActivity(intent)

or

val intent = Intent().setComponent(
  ComponentName("com.some.package","com.some.package.SomeActivity")
  startActivity(intent)
)

The necessity to explicitly declare our Intets causes a coupling to the components that we want to use, being an internal App Component, located in a third-party module and/or API. An alternative to this problem would be the use of Implicit Intents.

Implicit Intent

In contrast to Explicit Intents, where we have a target component class, with Implicit Intents we have target actions. In this way of communication with the operating system, we simply say what actions we want to perform, whether it's opening a screen, sharing files… Leaving all the work of searching for the component that can perform such action with the OS.

Despite being a more flexible and powerful form of communication, Implicit Intents imposes the existence of Intent Filters, crucial for the mapping between App Components and the actions they can perform.

Intent Filters

Target actions are simply strings, keys that declare or should declare an action supported by a particular application component. This mapping between supported actions and components takes place in the manifest.

<activity android:name=".SomeActivity">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />

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

In the example above, it is possible to observe one of the forms of intent filters declaration, with this being example form mandatory for at least one activity of the application, it is responsible for indicating to the operating system which is the first activity to be started when a touch is made on the icon of the app.

There are some standard actions provided by the operating system in addition to the MAIN action, such as the action VIEW, which is indicated for actions where it is necessary to present data to the user, photo, contacts... for example.

Besides action, we have two more attributes that can be defined in an intent filter, they are category and data.

  • Category: It is a string that provides additional information to an Intent Filter, which allows you to further specify the type of action supported by a given component. In the previous example, we have the category LAUNCHER, used to indicate to the OS that this screen will be presented in the applications drawer. We also have the CATEGORY_DEFAULT, mandatory in cases where we want to start an Activity using a Custom Action.

  • Data: It can be used to specify MIME data types that a feature of your application supports, such as texts (text/plain), images (image/jpg), and some other data that can be checked here. Another way to use the data attribute is in the URI declaration, which when combined with the BROWSABLE category, allows the creation of Deep Links (More details in the documentation).

Custom Actions

In addition to the default actions provided by the system, we have the ability to create custom actions, which allow sending messages (Intents) to an app component without having to know its class, by using the key of an action linked to that component through an Intent Filter, as exemplified below:

AndroidManifest.xml

<activity android:name=".SomeActivity">
    <intent-filter>
        <action android:name="com.some.example.action.MY_ACTION" />
    </intent-filter>
</activity>

Any secondary activity

val intent = Intent("com.some.example.action.MY_ACTION")
startActivity(intent)

In this example it would be possible to replace our action with any string of characters, like "foo.bla.bla" for example, it is not a good custom action name, but, if an Intent("foo.bla.bla") were sent to the system, our component would certainly be found and launched.

In cases where more than one component can perform the desired action, the system starts the disambiguation process, showing a dialog to the user allowing him to select which app he wants to use in the process. This is what usually happens when we try to share an image, for example, a dialog is shown with different apps that we can share this image.

In short, when we send an Intent("target.action") to the operating system, a search for all apps that can perform the target action is started, making use of intent filters to find which components support that action. Upon the occurrence of a match, the component that declares to support such action is initialized and the message (Intent) is delivered to it. A warning here is that, if the target action is directed to a BroadcastReceiver, then we should use sendBroadcast(Intent("target.action")), in the case of services, we use startService(Intent("target.action") ) and in the case of activities, as already exemplified, we use startActivity(Intent("target.action")).


Conclusion

The use of Implicit Intents together with Intent Filters brings us to a new form of subscription and declaration (in case of custom actions) of channels, where the channel name is given by the action declared in the manifest. In the case of deep links, the channel name is given by URI (scheme, host, and path).

The power of an Implicit Intent is enormous and allows us to navigate through the entire application through strings, without a direct dependency on a component. This power is even more evident when we work with modules, with the possibility of communication between modularized features exclusively through messages.