Search This Blog

18 July 2010

Intent Filters

Let's start with a basic app:

F:\work> mvn archetype:generate -DarchetypeCatalog=http://kallisti.eoti.org:8081/content/repositories/snapshots/archetype-catalog.xml
2: http://kallisti.eoti.org:8081/content/repositories/snapshots/archetype-catalog.xml -> galatea-archetype (null)
Choose a number: : 2
groupId: org.eoti.android.tutorial.intent
artifactId: IntentTest
version: 1.0-SNAPSHOT
package: org.eoti.android.tutorial.intent

F:\work> cd IntentTest
F:\work\IntentTest> mvn clean install


Add this into your IntentTestActivity:
    public void customClickHandler1(View target)
    {
        startActivity(new  Intent(Intent.ACTION_VIEW, Uri.parse("custom1://localhost/param1/param2")));
    }
   
Add this into the end of your LinearLayout in main.xml:
    <Button
        android:text="Click Me #1"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:onClick="customClickHandler1" />

Following this methodology, we can have custom methods rather than multiple if/else.


Create a new activity:
package org.eoti.android.tutorial.intent;

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

public class SubActivity extends Activity
{
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.sub);
        ((TextView)findViewById(R.id.text)).setText(getClass().getSimpleName() + " loaded: " + getIntent().getData());
    }
}



And create the res\layout\sub.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:id="@+id/text"
        />
</LinearLayout>


And add it to the AndroidManifest.xml:
        <activity android:name=".SubActivity">
            <intent-filter>
                <action android:name="android.intent.action.VIEW"/>
                <category android:name="android.intent.category.DEFAULT"/>
                <data android:scheme="custom1" android:host="localhost" android:pathPattern="/param1/.*" />
            </intent-filter>
        </activity>


Redeploy (mvn clean install)

Ok, that all works well... let's try having different path prefixes...

Add another button:
Update main.xml:
    <Button
        android:text="Click Me #2"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:onClick="customClickHandler2" />

Update IntentTestActivity:
    public void customClickHandler2(View target)
    {
        startActivity(new  Intent(Intent.ACTION_VIEW, Uri.parse("custom1://localhost/paramA/paramB")));
    }

Create SubActivity2:
package org.eoti.android.tutorial.intent;

import android.os.Bundle;

public class SubActivity2 extends SubActivity
{
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }
}

Update AndroidManifest.xml:
        <activity android:name=".SubActivity2">
            <intent-filter>
                <action android:name="android.intent.action.VIEW"/>
                <category android:name="android.intent.category.DEFAULT"/>
                <data android:scheme="custom1" android:host="localhost" android:pathPattern="/paramA/.*" />
            </intent-filter>
        </activity>

Redeploy (mvn clean install)... Now, we can launch either one by the first part of the URL...  what about something at the end?


Make a new activity:
package org.eoti.android.tutorial.intent;

import android.os.Bundle;

public class SubActivity3 extends SubActivity
{
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }
}

        <activity android:name=".SubActivity3">
            <intent-filter>
                <action android:name="android.intent.action.VIEW"/>
                <category android:name="android.intent.category.DEFAULT"/>
                <data android:scheme="custom1" android:host="localhost" android:pathPattern="/paramA/.*/paramC" />
            </intent-filter>
        </activity>

    <Button
        android:text="Click Me #3"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:onClick="customClickHandler3" />

    public void customClickHandler3(View target)
    {
        startActivity(new  Intent(Intent.ACTION_VIEW, Uri.parse("custom1://localhost/paramA/paramB/paramC")));
    }

Redeploy (mvn clean install)... what happens?  #1 and #2 work; but #3 asks you how you want to handle it (the picker is making you choose between #2 and #3).  That's because the 3rd url matches both intent-filters.  How do we fix that?

We don't have access to the full regex library (almost none of it really), so the simplest way is to make it behave more like files and directories...

Let's make "/paramA/paramB" more like a directory by appending a slash (ie: "/paramA/paramB/")
    public void customClickHandler2(View target)
    {
        startActivity(new  Intent(Intent.ACTION_VIEW, Uri.parse("custom1://localhost/paramA/paramB/")));
    }

        <activity android:name=".SubActivity2">
            <intent-filter>
                <action android:name="android.intent.action.VIEW"/>
                <category android:name="android.intent.category.DEFAULT"/>
                <data android:scheme="custom1" android:host="localhost" android:pathPattern="/paramA/.*/" />
            </intent-filter>
        </activity>

Redeploy (mvn clean install). Now what happens? All three of them now launch.  We have a gotcha here though.  Try this:

    <Button
        android:text="Broken"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:onClick="customClickHandler4" />

    public void customClickHandler4(View target)
    {
        startActivity(new  Intent(Intent.ACTION_VIEW, Uri.parse("custom1://localhost/paramA/paramB")));
    }

Redeploy (mvn clean install) and you will see that clicking the "Broken" button will cause a stack trace:
E/AndroidRuntime( 1278): Caused by: android.content.ActivityNotFoundException: No Activity found to handle Intent { act=android.intent.action.VIEW dat=custom1://localhost/paramA/paramB }
E/AndroidRuntime( 1278):        at android.app.Instrumentation.checkStartActivityResult(Instrumentation.java:1408)
E/AndroidRuntime( 1278):        at android.app.Instrumentation.execStartActivity(Instrumentation.java:1378)
E/AndroidRuntime( 1278):        at android.app.Activity.startActivityForResult(Activity.java:2817)
E/AndroidRuntime( 1278):        at android.app.Activity.startActivity(Activity.java:2923)
E/AndroidRuntime( 1278):        ... 15 more
W/ActivityManager(   61):   Force finishing activity org.eoti.android.tutorial.intent/.IntentTestActivity

This is because we now require a slash to be on the end (or 'paramC').

Let's try one more thing:

    <Button
        android:text="Wrong"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:onClick="customClickHandler5" />

    public void customClickHandler5(View target)
    {
        startActivity(new  Intent(Intent.ACTION_VIEW, Uri.parse("custom1://localhost/paramA/paramB/paramC/paramD/")));
    }

Redeploy (mvn clean install).  What happens?  Why doesn't it match "/paramA/.*/" from SubActivity2?  My only guess is that the "everything" in ".*" excludes the "/"?

So end result?  If you are willing to carefully layout your Uris and be limited to ".*" or literals (rather than say '[0-9]+') then this solution MIGHT be cleaner than creating an intent hub to process the strings and start your activities...  too bad we don't have access to true Java regex functionality.

3 comments: