During our planning sessions we decided to add push notifications and deep linking into the Australia Post app, the team and I discussed creating a design that could execute common code (such as initialisation), regardless of the mechanism used to launch it.
To get the execution to occur we knew that we’d have to involve some type of dispatch mechanism, for example a central controller to ensure that common logic such as checking whether the app needed to force an upgrade, would perform from any entry point.
While hunting around for pre-existing frameworks, we came across the totally wonderful DeepLinkDispatch from Airbnb. A real winner!!
DeepLinkDispatch
is a declarative annotation based API that dispatches a deeplink to the appropriate activity. If ever there’s an issue handling the deeplink, the user is taken to the main activity (just as if they were launching the app normally).
DeepLinkResult result = DeepLinkDelegate.dispatchFrom(this);
if (!result.isSuccessful()) {
gotToMainActivity();
} else {
finish();
}
The framework was built specifically for deeplinking but after we’d spent a bit of time with it we reaslised that it could be also be used across other use cases, such as app shortcuts and push notifications. Cool huh?
Here’s how it all works.
Deeplinking
Deeplinking allows users to launch the installed app from emails and http links, and provides a more seamless integration with the web.
The following shows an activity that can launch via multiple urls and even a custom url scheme such as aupost://
.
The deeplinking framework also allows parameters to be specified so you can support multiple urls from different environments e.g. https://systest/track/track.html
and https://uat/track/track.html
@DeepLink({
"https://{host}/track/track.html?id={id}&ilink={ilink}",
"https://{host}/track/track.html?id={id}",
"https://{host}/mypost/track/#/article/{id}",
"https://{host}/view/tracking#/track?id={id}",
"https://{host}/track/track.html",
"https://{host}/view/tracking",
"auspost://tracklist"
})
public class TrackActivity extends Activity {
To enable deeplinking, register the urls as intent filter with the activity used to dispatch the deeplinks. Also, deploy the assetlink json file to the root domain as outlined in this link -https://developer.android.com/training/app-links/index.html
To support more granular matching after the url parameters (?). use a slightly customised version of the deeplink framework. You’ll find the fork of the code via this link (the main branch might support this functionality now, so it is worth checking) -https://github.com/android-mypost-consumer/DeepLinkDispatch.
<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:host="tracklist"
android:scheme="auspost"/>
</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="https"/>
<data android:host="auspost.com.au"
android:pathPrefix="/track/"/>
<data android:host="auspost.com.au"
android:pathPrefix="/mypost/track/"/>
<data android:host="m.auspost.com.au"
android:pathPrefix="/view/tracking"/>
</intent-filter><meta-data android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" />
Push Notifications
As anyone who’s anyone will tell you, push notifications are hugely popular native app features. Directing a push notification via the deeplink framework to an activity works pretty much the same way, except we used a custom url scheme such as auspost://
@DeepLink({
“auspost://authoritytoleave/{id}”
})
public class AuthorityToLeaveActivity extends Activity
When building the push notification, we used TaskStackBuilder
to create an artificial back stack that supports back button navigation. The deeplink framework also supports the TaskStackBuilder
via a static method. However, since the only entry point to this deeplink was via push notification, we decided against including it.
private NotificationCompat.Action createSafeDropAction(Context context,String articleId, int drawable) {
String deepLinkUri ="auspost://authoritytoleave/" + articleId;
Uri uri = Uri.parse(deepLinkUri);
Intent intent = new Intent(Intent.ACTION_VIEW,uri);
//bundle notification id to the intent
intent.putExtra(ID, notificationId);
intent.putExtra(NAME, notificationName);
TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
// Adds the back stack
stackBuilder.addParentStack(AuthorityToLeaveActivity.class);
// Adds the Intent to the top of the stack
stackBuilder.addNextIntent(intent);
// Gets a PendingIntent containing the entire back stack
PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
return new NotificationCompat.Action(drawable,
context.getString(R.string.push_notification_safedrop_action),
resultPendingIntent);
}
The entry in the AndroidManifest.xml
to provide parentStack.
<activity android:name=".feature.atl.AuthorityToLeaveActivity"> <meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="au.com.MainActivity" />
</activity>
If the deeplink was accessible via multiple entry points e.g. push and http url we could define the deeplink via a static annotated method to be reusable as follows;
@DeepLink("auspost://authoritytoleave/{id}")
public static TaskStackBuilder authorityToLeave(Context context) {
Intent details = new Intent(context, AuthorityToLeaveActivity.class).setAction(ACTION_DEEP_LINK_COMPLEX);
Intent parent = new Intent(context, MainActivity.class).setAction(ACTION_DEEP_LINK_COMPLEX);
TaskStackBuilder tsb = TaskStackBuilder.create(context);
tsb.addNextIntent(parent);
tsb.addNextIntent(details);
return tsb;
}
App Shortcuts
App Shortcuts are a relatively new feature that came out with Nougat API 25. They give users the opportunity to navigate to common tasks quickly and conveniently within the app.
The deeplink framework proved its worth yet again when it revealed itself as a natural fit to implement this functionality. As with the push notifications, we went with a custom url scheme -
@DeepLink({“auspost://shortcut_pay_bill_activity”})public class PayBillActivity extends Activity {
We defined the app shorts in the shorcuts.xml
file and decided to specify the back stack information in this file (as this was the only entry point). In the example below, when the user presses back the activity will pop-off the back stack and will return to the MainActivity
. Cool or what!!
<shortcut
android:shortcutId="pay_bill"
android:enabled="true"
android:icon="@drawable/ic_pay_a_bill"
android:shortcutShortLabel="@string/pay_bill"
android:shortcutLongLabel="@string/pay_bill"
android:shortcutDisabledMessage="@string/shortcut_disabled_message">
<intent
android:action="android.intent.action.VIEW"
android:targetPackage="au.com"
android:targetClass="au.com.MainActivity" />
<intent
android:action="android.intent.action.VIEW"
android:data="auspost://shortcut_pay_bill_activity"
android:targetPackage="au.com"
android:targetClass="au.com.StartActivity" />
<categories android:name="android.intent.category.DEFAULT" />
</shortcut>
Interestingly, one of the activities that we wanted to shortcut to was the result of another activity calling startActivityForResult()
. We could launch this activity, however there was no way of returning the result. As a workaround we decided to shortcut to the Activity
that initiated startActivityForResult()
. Pretty annoying really, and I’ve got to say this is one of the many reasons — and I really mean many reasons — why I’m not a big fan of startActivityForResult()
.
@DeepLink({"auspost://shortcut_trackscan"})
public static Intent scanArticle(Context context) {
return new Intent(context, TrackActivity.class)
.putExtra(ARG_CURRENT_SCREEN, TrackScreens.SCAN);
}
Conclusion
The Airbnb framework played a critical part in helping our team implement push notifications, deeplinking and app shortcuts. A big hat tip and major round of applause to the Airbnb team — it really is an awesome framework.
With the arrival of instant apps at Google IO 2017, we know that this technology will provide even better app discovery. Google has estimated it takes between 4–6 weeks to implement instant apps. At this stage we’re not planning on implementing instant apps for at least another 12-months, mainly due the disruptive nature of refactoring the app into different modules.
But for now, we have a great intermediate framework that suits all our needs. Gee, I love finding a great framework.
- Update Jan 2020— If you work on an existing app and find it hard to implement the new Navigation Component without making massive changes then combining Airbnb DeeplinkDispatch & Dar Henson 3 is an alternative solution that doesn’t involve using a single activity and less evasive code changes.
Acknowledgments
Fraser Mason — Editing and proof reading