How to Handle Deep Links with Firebase Dynamic Links

Handling entry points and app authentication states.
Christian Aranda
Christian Aranda
May 28, 2018

There are several potential entry points into an iOS app, from a shortcut to a notification, a bluetooth peripheral to a newsstand update.

In this post, I’ll be looking at how we handle these various entry points – focusing, in particular, on deep linking using Firebase Dynamic Links.

Demo App

In order to illustrate how and why we use Firebase in production apps for our clients, we have built a demo app.

Our demo app uses an onboarding process. Once the onboarding has been completed, the user will be authenticated and directed to the home screen. The home screen uses a tab bar controller with two tabs.

The first tab allows us to check our boarding passes and share them. The second tab, which acts as a profile screen, allows us to choose to logout. It’s a very small app, as we only created it for demonstration purposes, but we replicated the same patterns that we use in our much larger production apps. For instance, we used a coordinator pattern to handle the app startup state and a flow coordinator to route into the different parts of our app.

Whilst I’ve seen some examples online of how to handle deep links, I’ve yet to see any that explain how to handle deep links using Firebase Dynamic Links and authentication state, and that’s where things get a little difficult, so that’s what I’m going to do today.

Dynamic link vs. deep link

Before we get started, you need to know the difference between a ‘dynamic link’ and a ‘deep link’.

A ‘deep link’ is the action that allows developers to directly link users to content within their apps. These links, however, only work if the user has the app installed.

A ‘dynamic link’ is a link that allows developers to send existing and potential users to any location within their iOS or Android app, regardless of whether or not the user has their app installed. These links can also transition users between platforms, from desktop website to mobile device, with ease. Pretty nifty, right?!

Firebase Dynamic Links

We used Firebase Dynamic Links when building our demo app. As I mentioned, dynamic links work even if the user hasn’t installed the app – simply redirecting users to the App Store where they can begin downloading it. The links even survive the installation process.

The type of deep link we use is determined by whether or not the user already has the app installed. In order to handle the different scenarios, we created the following protocol:

<p> CODE: "https://gist.github.com/WildStudio/9b7ca76e3083364f24b41ec718afdf7d.js"</p>

Handling deep links when the app is not installed.

If the app is not installed, we call upon the application:openURL:options: method once the app has been opened for the first time (following installation on any version of iOS).

In this instance, links are received through the app’s custom URL scheme.

<p> CODE: "https://gist.github.com/WildStudio/5df53110e45bb8bebdf03f2a4b15f23a.js"</p>

This is what the handle(customScheme: url) looks like:

<p> CODE: "https://gist.github.com/WildStudio/59818abd889432fcb4c12fbd1525bd6c.js"</p>

If a Dynamic Link is found, it is stored to use later using the following function:

<p> CODE: "https://gist.github.com/WildStudio/cd286ac29ee43cfe7cadf0a7959e0086.js"</p>

Handling deep links when the app is installed.

If the app is installed, the method handling link, received as a universal link on iOS 9 and above, is application:continueUserActivity:restora::continueUserActivity:restorationHandler:

<p> CODE: "https://gist.github.com/WildStudio/9f9cc2686627782602ec662327a073a9.js"</p>

In this instance, we handled the universal link with the following function (storing deep links if we found them):

<p> CODE: "https://gist.github.com/WildStudio/9c10d76b2cb353dd8a818c0628bda4f5.js"</p>

When using Firebase Dynamic Links, we need to wait for our callback to return regardless of whether or not the dynamic link is found. This can fail at times, for example, due to any Firebase errors that arise or if the user doesn’t have an internet connection, so that’s something to keep in mind.

If the app is installed, we need to consider a scenario where it finds itself launching from a cold start.  

Handling deep links from a cold start.

If the app isn’t yet running and it needs to open a URL, it will be launched by the system.

The URL can then be found in the options of the method:

<p> CODE: "https://gist.github.com/WildStudio/a2493bddb73491f7e6afe1a71dc2cda6.js"</p>

In order to do this, we created a launch options handler which, as it stands, is just NSUserActivityTypeBrowsingWeb with regards to its activity type. It could, however, handle different launching options if needed e.g shortcut items, etc.

<p> CODE: "https://gist.github.com/WildStudio/cf4b62ad42365ee4aa918298615e34d5.js"</p>

This handler returns a NSUserActivity that is stored in a global property so, instead of showing ‘sign up’ or ‘log in’ when a token is stored, we know that a deep link will arrive at some point from the application:continueUserActivity:restora::continueUserActivity:restorationHandler: delegate method that we previously outlined in this post. In this case, we can present a spinner while the deep link is returned.

The App Startup Controller

Now that we’ve tackled deep links, and explained how we use them, we can look at how we created our App Startup Controller class. The class is responsible for checking the authentication state and running different actions, whether a deep link is already stored or if it will arrive at another stage.

We created a StartupState for our different states:

<p> CODE: "https://gist.github.com/WildStudio/dd1387203cdc3b9a4a7ca0f96de13945.js"</p>

We used the full power of computed properties to access the latest values of its dependencies.

We first checked if there was a CurrentUserActivity on the AppDelegate that would suggest an incoming link opened the app.

<p> CODE: "https://gist.github.com/WildStudio/99bc8d8990f6b9551dd9aaab1c6f8b28.js"</p>

We then checked if the deep link was stored.

<p> CODE: "https://gist.github.com/WildStudio/a168ecc4137afe8b9dc2d86b3ef3c251.js"</p>

If the user was authenticated, there should be a stored token.

<p> CODE: "https://gist.github.com/WildStudio/8940fc64a5e1d9ffaa3cb53a95dc1abc.js"</p>

We then checked the different states in the handle startup function.

<p> CODE: "https://gist.github.com/WildStudio/0013ccde579a8351c83b23a72e5b40d2.js"</p>

The above code is run by a flow coordinator, from the AppDelegate, that works as the main entry point into the app. It will be triggered as soon as the Firebase callback is completed:

<p> CODE: "https://gist.github.com/WildStudio/e716e7b27ce26aa3fea90eca6980a57f.js"</p>

We have defined three actions that will be called upon once they’re triggered. They will run blocks and input the code into our flow coordinator, before navigating to different points of the app:

  • The user is logged in: navigate to the home screen.
  • The user is logged out: navigate to the login/signup screen.
  • Deep link users to a particular point of the app.

Once the deep link action has been completed, we should delete the local copy we previously stored and, if there is one, the CurrentUserActivity.

<p> CODE: "https://gist.github.com/WildStudio/9d0282eeb797a363320650e6c022b8e7.js"</p>

APP STARTUP CONTROLLER LOGIC DIAGRAM – Lee Higgins

Debugging

Testing and debugging can be fairly complicated with regards to deep links. By using an App Startup Controller, however, we can test the different initial states – either by storing a token, a deep link, or in some cases, by storing neither (in order to test the unauthenticated state).

When we test deep links we:

  1. open the deep link and go to the App Store
  2. download the app
  3. replace it with the Xcode version before we open it (to ensure that the deep link is still alive when we first run it)

Our production deep link testing also includes testing when the app is not running. This is how we debug the deep links when launching the app from a cold start:

  1. Navigate to the scheme for the project in Xcode. Under the ‘Run’ section, click on the ‘Info’ tab. There’s a radio button that says ‘wait for the executable to be launched’. Make sure that this option is checked as opposed to the ‘automatically’ option.
  2. Run the app from Xcode. It will not open on the device but the debugger will wait for it to open and then it will attach to it.

Why use an App Startup Controller and deep link handler?

We, personally, appreciate this approach because it allows us to easily see each of the different states from where our app can start, meaning we don’t have to then handle the spread on the AppDelegate. It, thus, keeps the logic in one place.

If we later decide to include another entry point into the app, such as ‘Notifications’, we simply add a handler and model into our App Startup Controller. It makes the process much easier!

It also makes the process much clearer. We need a synchronisation point on startup as we have asynchronous dependencies we need to wait for before we can make the decision of where to send the user. Having a single place to consider it all makes both the app’s startup flow and dependencies much clearer.

Get in touch

If you’d like to see everything we’ve spoken about in action, don’t forget you can head over to our demo app. If you can’t quite get enough of us talking about code, you can head over to our ‘Happy Coders’ post to find out more about our healthy coding practices.

In a future post, I’ll be exploring how we use a flow coordinator to route to different points of the app and I’ll be sharing some more code relating to how we created deep links from the app. Don’t forget to follow us on Twitter, LinkedIn or Medium to be notified of our future posts.

If you need any further advice, drop me a line at christian@wearemobilefirst.com or find me on Twitter. I’d be happy to help!

References

Our demo app:
https://bitbucket.org/wildstudio/boardingpass/src/master/

Apple Developer – Apple universal links:
https://developer.apple.com/library/content/documentation/General/Conceptual/AppSearch/UniversalLinks.html

Apple Developer – Responding to the Launch of Your App:
https://developer.apple.com/documentation/uikit/core_app/managing_your_app_s_life_cycle/responding_to_the_launch_of_your_app

Firebase – How to Receive Dynamic Links on iOS:
https://firebase.google.com/docs/dynamic-links/ios/receive

Apple Developer forum thread – Testing openURL to cold started app:
https://forums.developer.apple.com/thread/21129