Universal Links opening the Flutter app AND a web browser?

Universal Links opening the Flutter app AND a web browser?
An ostrich at Monarto Zoo
💡
This article describes a weird problem our customers experienced with our mobile app. It discusses Flutter, Google's mobile app framework, and app_links, a Flutter package that handles deep linking.

We had an odd customer support ticket last week. When opening CareApp from an email notification, they were taken to a web browser, and were always logged out. Digging further, what we discovered is that tapping the link in the email would open CareApp's mobile app correctly, but then also open CareApp in a web browser. This only affected iOS users.

We use the app_links package to handle Universal Links on iOS and AppLinks on Android. Both work in a similar way. You, the app developer, registers URL schemes that should be handled by your app. For example, if your website is at https://shop.example.com, then you register that domain with your app. If the user has your app installed, opening that URL will open the app instead of the website.

Exactly how you register the URL depends on iOS and Android, and can be found in the documentation I linked to above.

CareApp is a pretty old app by Flutter standards - we started development in 2019 to replace our older Ionic app. And so we've used a package to handle deep linking from the start. First uni_links until it was discontinued, and then app_links.

The problem comes as we have updated the Flutter version used by our app over time. As Flutter evolved to support the web, it has had to implement it's own support for deep linking. This native support clashes with Flutter plugins, such as app_links, and leads to unexpected behaviour.

In our case, routine updating of package and Flutter version introduced this error without us catching it before release. Unfortunately testing deep linking is a bit of a pain to set up, and so our automated testing missed it.

The Fix - Read the documentation

Thankfully the solution here is documented. It involves opting out of Flutter's native deep linking, and making a change to our App.

Opt Out

Edit Info.plist in ios/Runner, and add the following key:

		<key>FlutterDeepLinkingEnabled</key>
		<false/>

That tells Flutter not do do it's own deep linking.

Update AppDelegate.swift

Reading the documentation, this sounds like it's optional. However, we still had issues until we added the following to ios/Runner/AppDelegate.swift

    // Retrieve the link from parameters
    if let url = AppLinks.shared.getLink(launchOptions: launchOptions) {
      // We have a link, propagate it to your Flutter app or not
      AppLinks.shared.handleLink(url: url)
      return true // Returning true will stop the propagation to other packages
    }

With those two changes, the problem appeared resolved. Opening links only opened the mobile app.

Alternative - Migrate to Native Deep Linking

Long term, the better solution might be to migrate away from the app_links plugin all together. Using the native way is better because it's a first party recommendation, and offers more flexibility.

Unfortunately it's quite a bit more work. To implement it properly you will need to migrate to a router package such as go_router. Again, CareApp is old and our navigation system predates the Router API. It's more than we were willing to do to fix this issue for our user. Migrating to Router will probably happen one day, but on our own time and in a testable fashion.