跳转至

8 Deep Linking

You just went through an entire chapter on routing and navigation. Since this isn’t a beginner’s book, that surely wasn’t a new topic for you at that point, but the reason it had to be covered was that the routing system you were probably familiar with, Navigator 1, doesn’t have good deep link support.

You then learned how to use Flutter’s Navigator 2, built from the ground up with deep links in mind. This chapter is where deep links finally come into play, and all the work from the previous chapter pays off. But what are deep links?

An app that supports deep links is an app that can launch in response to the user opening a link. Have you ever tapped a link, and that link opens an app instead of a web page? That’s because that app supports deep links. Notice the link doesn’t simply launch the app; it navigates to specific content within the app — hence the deep in the name.

Deep links are primarily used to allow users to share content — Spotify playlists, social media posts, etc. — the examples are countless. Another example of deep links in action is when you receive a notification, tap it, and that takes you to a related screen inside the app — you see that a lot in chat apps.

Adding deep link support to an app isn’t as easy as one might imagine. It involves two different challenges:

  • Making the system know it has to launch your app when the user opens certain links.
  • Making your app take the user to the right screen once the system launches it.

The second part is pretty much complete, thanks to the robust routing strategy you already have in place. The first part, though, can be quite complicated, as it involves some web knowledge, like domains. Luckily, Firebase is your friend here. Firebase’s specialty is saving client developers from having to know stuff they don’t care about.

You’ll implement deep links with the help of Firebase Dynamic Links. Firebase’s solution works on top of an enhanced type of deep links, called dynamic links. Dynamic links are deep links that can:

  • Work across different platforms: If a user opens the link on their phone, they can be taken directly to the linked content in your native app. If a user opens the same link in a desktop browser, they can be taken to the equivalent content on your website.
  • Work across app installs: If a user opens the link on their phone and doesn’t have your app installed, the user is sent to the Play Store or App Store to install it. Then, after installation, your app starts and opens the specific content.

Roll up your sleeves! In this chapter, you’ll learn how to:

  • Configure a Firebase project to support dynamic links.
  • Generate dynamic links on the fly.
  • Launch your app in response to a dynamic link.
  • Respond to a dynamic link coming in when your app is already open.

Throughout this chapter, you’ll work on the starter project from this chapter’s assetsfolder.

Getting Started

Use your IDE of choice to open the starter project. Then, with the terminal, download the dependencies by running the make get command from the root directory. Wait a while for the command to finish executing, then build and run your app on an Android device — either physical or virtual.

Why Android? You can’t test dynamic links on iOS unless you have a paid Apple Developer Account; thus, very few readers of this book probably have one. Therefore, for brevity, this chapter will proceed using an Android emulator but will give you a link with the additional instructions you need for iOS.

After running your app, this is what you should see:

img

Note

If you’re having trouble running the app, it’s because you forgot to propagate the configurations you did in the first chapter’s starter project to the following chapters’ materials. If that’s the case, please revisit Chapter 1, “Setting up Your Environment”.

Your app is pretty much complete, except it can’t handle deep links yet. Tap a quote to go to the quote details screen, and you’ll notice the Share button on the app bar doesn’t respond when you tap it.

img

What that Share button should do is allow the user to share a deep link that, when opened, takes the recipient to that same exact quote inside the WonderWords app. From here on, your job is to make sure that:

  • The button responds appropriately when users tap it.
  • The app can open that same quote when the link is opened.

Your first step on that mission is to configure your Firebase’s dashboard.

Setting up Firebase

Go to Firebase’s console, and open the project you created in Chapter 1, “Setting up Your Environment”.

On the left sidebar, expand the Engage section, then click Dynamic Links. Finally, click Get Started.

img

In the dialog that opens, you have to type what you want your links’ prefix to be, that is, the domain. You have two options here:

  • Use a custom domain that you might own, like myproject.com.
  • Use a free domain provided by Firebase, like myproject.page.link.

The first option certainly looks more professional when you’re doing this for a real project that you own, but it involves some additional steps — you can find the instructions here. Since you’re only doing this for educational purposes now, the second option is more than enough.

Type in wonderwordsSOMETHING_UNIQUE.page.link, but don’t forget to replace SOMETHING_UNIQUE with any unique value you know won’t already be taken by other readers of this book. Finally, click Continue, and then Finish.

img

You’ll see your domain listed in the Firebase console. Make sure to write it down because you’ll need it later.

img

You’d need to perform a few more steps to make your links work on iOS. But again, as mentioned in the Getting Started section, that requires you to have both an Apple Developer Account and your app registered there. Since this is just an educational project and can’t be published, it doesn’t make sense to go through those steps right now. When configuring dynamic links for a project you own, just execute the additional steps outlined in the Configuring Firebase Project Settings section of the tutorial Firebase Dynamic Links: Getting Started.

Now, go back to your IDE. Your next task will be to create the logic to generate a dynamic link when users tap a quote’s Share button.

You might think generating a dynamic link is just a matter of appending the path of the screen you want to open to the base URL you created on Firebase, something like: https://wonderwords1.page.link/quotes/27231. It’s actually a bit more complicated than that.

A dynamic link is a complex link that contains several parameters. The actual link you want to open is encoded inside the last part of them, for example: https://wonderwords1.page.link?sd=Abdul%20Kalam&st=You%20have%20to%20dream%20before%20your%20dreams%20can%20come%20true.&amv=0&apn=com.raywenderlich.wonder_words&ibi=com.raywenderlich.wonderWords&imv=0&link=https%3A%2F%2Fwonderwords1.page.link%2Fquotes%2F15140.

It wouldn’t be hard for you to create a function that builds a dynamic link by replacing the variable parts in the example above, but you’d still have one problem: That link is too ugly to share — shareable links should ideally be concise. There’s an elegant solution for that: The package provided by Firebase Dynamic Links has a function to help you build shortened versions of links like the one above. In the end, the result will be something like: https://wonderwords1.page.link/jHcE. Beautiful, right?

To see how this works in practice, open lib/src/dynamic_link_service.dart under the monitoring package.

img

Note

WonderWords’ architecture uses this monitoring package to encapsulate all the Firebase services you’ll use throughout this book. The biggest advantage of doing this is that if one day you decide to replace Firebase with another tool, the only affected package will be this one.

This file holds a class named DynamicLinkService. The goal of this class is to wrap the Firebase Dynamic Links package and expose only the functionalities you’ll need. Kick off your work on this class by replacing // TODO: Create a constant to hold your dynamic link prefix. with:

static const _domainUriPrefix = 'YOUR_FIREBASE_DYNAMIC_LINK_URL';

Don’t forget to replace YOUR_FIREBASE_DYNAMIC_LINK_URL with the URL you got in the Setting Up Firebase section.

Next, find // TODO: Create a function that generates dynamic links., and replace it with:

// 1
Future<String> generateDynamicLinkUrl({
  required String path,
  SocialMetaTagParameters? socialMetaTagParameters,
}) async {
  // 2
  final parameters = DynamicLinkParameters(
    link: Uri.parse(
      '$_domainUriPrefix$path',
    ),
    uriPrefix: _domainUriPrefix,
    androidParameters: const AndroidParameters(
      packageName: _androidPackageName,
    ),
    iosParameters: const IOSParameters(
      bundleId: _iOSBundleId,
    ),
    socialMetaTagParameters: socialMetaTagParameters,
  );

  // 3
  final shortLink = await _dynamicLinks.buildShortLink(parameters);
  return shortLink.shortUrl.toString();
}

Here’s what’s going on with the code above:

  1. You’re creating a function that receives two parameters: * The path of the screen you want your link to open. * An optional SocialMetaTagParametersobject that can contain information you want to appear when your link is shared in a social post, such as a short description and an image.
  2. You then combine the parameters you received with some of the information you already had to build a DynamicLinkParameters object.
  3. Finally, you delegate the link’s construction to the buildShortLink() function from the FirebaseDynamicLinks class.

What you need to do now is connect this function to the quote details screen so that when the user taps the Share button in there, this code will run, giving the user a link to share.

Go back to the main package, and open the routing_table.dart file.

img

Scroll down to // TODO: Specify the shareableLinkGenerator parameter., and replace it with:

// 1
shareableLinkGenerator: (quote) {
  // 2
  return dynamicLinkService.generateDynamicLinkUrl(
    path: _PathConstants.quoteDetailsPath(
      quoteId: quote.id,
    ),
    socialMetaTagParameters: SocialMetaTagParameters(
      title: quote.body,
      description: quote.author,
    ),
  );
},

First of all, observe where you’re inserting this code. You dove into this routing_table.dart file in the last chapter. This is where you define all your routes and connect all your features. In the snippet above, you:

  1. Specified the shareableLinkGenerator parameter of the QuoteDetailsScreen class. This parameter expects a function that the quote details screen can use to generate a dynamic link. That function receives a quote and must return a Future<String> containing the shareable link of that quote.
  2. Then, to actually generate the link, you’re calling the generateDynamicLinkUrl() function you created inside the DynamicLinkService class and provided it:

  3. The path of the quote details screen containing the ID for that specific quote the user wants to share.

  4. The socialMetaTagParameters containing some information about the quote, so the link looks good on social media.

Build and run your app to check on your progress. Tap any quote to open the quote details screen, and this time you’ll be able to use the Share button in the upper-right corner. The only reason it wasn’t working before is because you weren’t specifying the shareableLinkGenerator parameter.

Tap the Share button and write down the link that appears in the bottom sheet. You’ll use it later for testing.

img

Note

The exact look of this bottom sheet might change depending on your Android version.

Great! Now that your app can properly generate dynamic links, your next job is to make sure you can open them as well.

When the user opens a dynamic link, your app can be in two states:

  • Closed.
  • Open — and minimized, since the user can only launch the link from another app.

You’ll begin by handling the first scenario.

Go back to lib/src/dynamic_link_service.dart under the monitoring package.

img

Replace // TODO: Create a function that returns the link that launched the app. with:

Future<String?> getInitialDynamicLinkPath() async {
  final data = await _dynamicLinks.getInitialLink();
  final link = data?.link;
  return link?.path;
}

That’s it! The name of the function says it all. If the app was launched from a dynamic link, this function you just created is capable of returning that link to you so you can navigate to the corresponding screen.

You’ll now put that function to use. Open the main.dart file under the main package.

img

Replace // TODO: Handle initial dynamic link if any. with:

@override
void initState() {
  super.initState();

  _openInitialDynamicLinkIfAny();

  // TODO: Listen to new dynamic links.
}

Future<void> _openInitialDynamicLinkIfAny() async {
  // 1
  final path = await _dynamicLinkService.getInitialDynamicLinkPath();
  if (path != null) {
    // 2
    _routerDelegate.push(path);
  }
}

Since you’re adding this to the topmost widget in your app, this will run every time your app launches. The logic you wrote will then:

  1. Check if a dynamic link launched the app.
  2. If it did, then navigate to the appropriate path.

Time to test this. Build and run your app, so your new code gets deployed to your phone, but then hard close the app after that. Don’t forget to make sure your app is really closed by swiping it out of the recent apps list.

Use a browser to open the link you generated using the Share button in the last section. Your app should open and navigate directly to a quote’s details screen.

img

Note

The quote you’ll see will be different than the one above. It depends on which quote you generated your link for.

You just covered the scenario in which a dynamic link launches your app. But what if your app was already open? To handle that, go back to the monitoring internal package and open lib/src/dynamic_link_service.dart.

img

Replace // TODO: Expose a way to listen to new links. with:

// 1
Stream<String> get onNewDynamicLinkPath {
  // 2
  return _dynamicLinks.onLink.map(
    (PendingDynamicLinkData data) {
      final link = data.link;
      final path = link.path;
      // 3
      return path;
    },
  );
}

Here’s what’s going on with this code:

  1. You’re creating a property that exposes a Stream<String> so that users of this function can listen to get notified about when a new link comes in.
  2. The FirebaseDynamicLinks class contains an onLink property, which is pretty much what you need. You then just use the map function to change the data type of that Stream from PendingDynamicLinkData to a Stringcontaining just the path of the screen you need to open — which is the only thing you need to navigate.

Now, to finish your work for good, go back to the main.dart file.

img

Replace // TODO: Listen to new dynamic links. with:

// 1
_incomingDynamicLinksSubscription =
    // 2
    _dynamicLinkService.onNewDynamicLinkPath.listen(
          // 3
          _routerDelegate.push,
        );

Here, you’re:

  1. Storing the result of the listen() call in the _incomingDynamicLinksSubscription property. This is necessary so you can cancel() the subscription when your widget gets disposed.
  2. Using the onNewDynamicLinkPath property you just created inside the DynamicLinkService class.
  3. Forwarding any new paths coming in from that Stream to the push()function of your _routerDelegate property. This is what makes the navigation happen.

Finally, build and run your app for the last time, but don’t close it this time. Minimize the app just so you can open a browser, and try opening that same link from last time. If everything went fine, your app should be in that same quote details screen.

img

That’s all for this chapter. Congratulations!

Key Points

  • An app that supports deep links can be launched in response to the user tapping a link.
  • Using Firebase Dynamic Links is the easiest and most robust way to implement deep links in an app.
  • Dynamic links are just special deep links that:Work across different platformsWork across app installs
  • When generating dynamic links, use the functions provided by the official package so you can build shortened links easily.
  • When writing the code that handles an incoming dynamic link, you always need to consider that your app can be in two different states: closed or minimized.