Table of Contents
In this article, we will see what are Deep Links and how it is used in Flutter for Android and iOS. Well, when building Flutter applications, we often see, debug, and test multiple scenarios inside the app and from the backend perspective, but there are also some cases where we test our app from that perspective. Currently, you might be thinking, "Why do we need other things/apps to test our app ?"
We have things such as unit testing, integration testing, and widget testing, which test our app from the debug point of view of the API, backend, and UI. So, don't be confused. Let me give you a case in which you will require some other things to check your testing point. Here are some cases to have to look out for before getting to the main point.
Case 1: When you click on any notification of any app, you are redirected to the same page of that app. But, how ? The app has a default home page in every case, so how does it navigate to that screen ?
Case 2: When you see a mobile app sell an ad in a social media outlet and download it using the provided link, why does the app open to the same product page ?
Well, there are some internal and external connections between the apps, don’t you think so ? So, this internal and deep linking of apps and their content is known as “Deep linking." Well, this is not an accurate description of deep linking, because deep linking is mainly about accessing app content or any content related to the app using external resources via a link attached to the app's screen.
Understanding Deep Links in Flutter [Explained with examples]
Also Read: Understanding Singleton Pattern in Flutter Using Best Examples
So, deep linking from the user's perspective is made in a way such that, if the user is interested in some product and he got that information from another link, then when downloading the app, he/she shouldn’t face problems searching for the same product in the whole app.
A deep link mainly contains a link/URL that looks in the same format as “deeplinkpageurl.page.link”, which is made with the concept of redirecting to the same screen when clicked on a product of the app from any external resources. As a result, the process of instantiating Deep Link on Android and iOS differs but behaves the same.
Implement Deep Linking on Android
So, to have a deep link in your app, you need to add the link for the redirection, and that can be instantiated by adding intent filters for the incoming links. So, in Android, there are mainly two types of links:-
- App Links: App Links are the same as deep links, but it does require a specified host, i.e a hosted file and along with this, the app links only tend to work with the HTTPS scheme.
- Deep Links: Deep links are the links in android, which doesn’t require a specified host or any other custom scheme.
Add the following to AndroidManifest.XML:-
<!-- Deep linking --> <meta-data android:name="flutter_deeplinking_enabled" android:value="true" /> <intent-filter android:autoVerify="true"> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="http" android:host="flutterbooksample.com" /> <data android:scheme="https" /> </intent-filter>
The intent filter is a statement in the manifest file that specifies what intent or component will be received on the app side, according to native app implementation. When an action occurs, it can specify additional actions that should be taken on this intent filter.
Data is what the URI format, which primarily resolves to the activity, represents. There can be multiple usages of <data>. By indicating the type of URI format, data tags can be made even more precise. However, a data tag must at the very least have the android:scheme attribute.
A category is mainly the type of intent-filter category that is off. So, for example, in the above code, our category is "BROWSABLE", which means that the intent filter can be accessed from the browser. If we haven’t added this to the category, then this intent filter can't be browsed.
Implement Deep Linking on iOS
Similarly, in IOS, there are two types of links:-
a) Universal Links: Universal links are those links that require a specified host or a custom scheme in their app. It works only with the HTTPS scheme. Below is the defined example of a universal link.
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <!-- ... other keys --> <key>com.apple.developer.associated-domains</key> <array> <string>applinks:[YOUR_HOST_HERE]</string> </array> <!-- ... other keys --> </dict> </plist>
b) Custom URL: In Custom URL, you don’t need a host or a custom scheme for your iOS app. An example of a custom URL is defined below.
<key>FlutterDeepLinkingEnabled</key> <true/> <key>CFBundleURLTypes</key> <array> <dict> <key>CFBundleTypeRole</key> <string>Editor</string> <key>CFBundleURLName</key> <string>flutterbooksample.com</string> <key>CFBundleURLSchemes</key> <array> <string>customscheme</string> </array> </dict> </array>
So, in the above code, you have learned to initialize the deep link native implementation. But, after initializing the deep links, you also need to handle the application in all aspects, like how there can be different scenarios where a deep link use case can occur.
How does Deep Link Triggers
There are two primary scenarios in which the trigger and handling of the deep link comes into the picture.
a) The first scenario comes when your app is not running in the background (i.e the killed state), and the app is freshly started. This is called the “cold start”. In this the app will have the deep link as the initialized link.
b) In the second scenario, suppose your app is currently running in the background state and later it comes to the foreground. Then after coming to the foreground, stream will generate a link. In this case, the link can either be null or be the link, which is the same as when app starts.
main.dart :-
bool _initDeeplinkHandled = false; // is checking that, is deeplink handling your routing Uri? _initialDeeplink; // the initial deeplink, when the app starts Uri? _currentDeeplink; // the deeplink on the current screen Object? _err; StreamSubscription? _streamSubscription; Future<void> _initDeeplink() async { // 1 if (!_initDeeplinkHandled) { _initDeeplinkHandled = true; // 2 Fluttertoast.showToast( msg: "Invoked _initDeeplink", toastLength: Toast.LENGTH_SHORT, gravity: ToastGravity.BOTTOM, timeInSecForIosWeb: 1, backgroundColor: Colors.green, textColor: Colors.white ); try { // 3 final initialDeeplink = await getInitialDeeplink(); // 4 if (initialDeeplink != null) { debugPrint("Initial Deeplink received $initialDeeplink"); if (!mounted) { return; } setState(() { _initialDeeplink = initialDeeplink; }); } else { debugPrint("Null Initial Deeplink received"); } } on PlatformException { // 5 debugPrint("Failed to receive Deeplink uri"); } on FormatException catch (err) { // 6 if (!mounted) { return; } debugPrint('Malformed Deeplink URI received'); setState(() => _err = err); } } }
The above function _initDeeplink() is handling and instantiating the state of the deep link only once, and it will not change the state throughout the app.
void _incomingDeeplink() { // 1 if (!kIsWeb) { // 2 _streamSubscription = deeplinkStream.listen((Uri? uri) { if (!mounted) { return; } debugPrint('Received URI: $uri'); setState(() { _currentDeeplink = uri; _err = null; }); // 3 }, onError: (Object err) { if (!mounted) { return; } debugPrint('Error occurred: $err'); setState(() { _currentURI = null; if (err is FormatException) { _err = err; } else { _err = null; } }); }); } }
The above function is used to receive the deep links, after starting the app. And, the below function is triggered, so all the functions written should be triggered while initializing the app.
@override void initState() { super.initState(); _initDeeplink(); _incomingDeeplink(); }
The below function is used to close the subscription to the deep link when the current screen is stopped.
@override void dispose() { _streamSubscription?.cancel(); super.dispose(); }
Conclusion
In this article, we have seen what are deep links, their types, and subtypes, as well as some instantiating work. Along with that, we have also seen how to handle deep links in Android and iOS app development with examples.