Education
Software Development Executive - II
Last updated onAug 20, 2024
Last updated onAug 1, 2023
Deep linking in Flutter lets you craft complex routes, pass custom data, and navigate into specific areas of your app (iOS, Android, or web app) by clicking a URL. It is an essential part of the mobile and web ecosystem as it allows users to delve deeper into your app from web links, email campaigns, advertisements, or even other apps.
This comprehensive guide is aimed at clarifying how to work with deep links in Flutter. By the end of this guide, you will be acquainted with the configuration and implementation process of deep linking in Flutter applications for iOS, Android, and web applications.
Before diving into the actual implementation of deep linking in Flutter, it's crucial to understand how to configure your Flutter app to respond to deep links.
In an iOS or Android application, you must first configure platform-specific settings to specify which URLs your app can handle.
In iOS, you declare your app’s supported URL schemes in your Info.plist file by adding the 'CFBundleURLTypes' key.
Here's a sample 'Info.plist' file where the app supports a custom URL scheme:
1 <key>CFBundleURLTypes</key> 2 <array> 3 <dict> 4 <key>CFBundleTypeRole</key> 5 <string>Editor</string> 6 <key>CFBundleURLName</key> 7 <string>[URL]</string> 8 <key>CFBundleURLSchemes</key> 9 <array> 10 <string>poc</string> 11 </array> 12 </dict> 13 </array> 14
For Android, you will need to add an <intent-filter>
inside the <activity>
tag in your 'AndroidManifest.xml' file. This tells the Android platform that your application can handle certain types of intents.
Here's an example of how to specify that your app can handle a certain URL intent:
1 <intent-filter> 2 <action android:name="android.intent.action.VIEW" /> 3 <category android:name="android.intent.category.DEFAULT" /> 4 <category android:name="android.intent.category.BROWSABLE" /> 5 <data 6 android:scheme="poc" 7 android:host="[URL]" /> 8 </intent-filter> 9
Configuring these platform-specific permissions is the first step in enabling Flutter deep linking.
Deep Links in iOS & Android
Deep linking in Flutter for iOS devices can be achieved through two techniques - Custom URL Schemes and Universal Links. Each has its unique purpose and usefulness.
Custom URL schemes allow you to work with any scheme, with no host specification. However, the challenge is to ensure that the scheme is unique. Also, with custom URL schemes, if the app isn't installed on the device, the URL won't work.
On the other hand, Universal Links allow work with an 'https' scheme only, and require a specified host, entitlements, and an associated file — 'apple-app-site-association'. This technology gives you an opportunity to start your app with a standard web URL, like: 'https://your_host'
However, since configuring Universal Links involves complex settings such as an entitlement, and setting up an 'apple-app-site-association' file on your website, this guide will focus on the 'Custom URL Schemes' approach which is simpler and more common for simple use cases.
Deep linking in Flutter for Android devices can be accomplished using two methods - App Links and Deep Links. Both methods serve different purposes.
App Links work with the https scheme and require a specified host, and a hosted file similar to Universal Links in iOS. They are secure and verified, making them ideal for guaranteed app and web page associations. However, the configuration is a bit more complex, requiring digital asset links and hosting files.
Deep Links, on the other hand, are easier to configure. They can work with a custom scheme and do not require a host or hosted file. Deep links essentially allow any app that understands the specified URI format to appropriately respond to the deep link.
For the sake of simplicity, this blog focuses on handling deep links using the traditional deep linking approach - a simpler path for adding the deep linking feature to your Android app.
Below is how you specify an intent filter for deep linking in the Android Manifest file:
1 <intent-filter> 2 <action android:name="android.intent.action.VIEW" /> 3 <category android:name="android.intent.category.DEFAULT" /> 4 <category android:name="android.intent.category.BROWSABLE" /> 5 <data 6 android:scheme="poc" 7 android:host="deeplink.flutter.dev" /> 8 </intent-filter> 9
With this in place, using the link 'test://deeplink.flutter.dev' will trigger the specified activity in the Android app.
Remember, specifying the intent filter under the correct activity tag is essential. This signifies which activity should be launched when a user clicks a deep link that matches the specified pattern.
Platform channels form the bridge between Flutter and the platform-specific components of iOS and Android. They facilitate communication between the Dart codebase and the native code of your app. This is crucial when implementing "Flutter deep linking", as your app’s native code must listen for deep link events and communicate them back to Flutter code.
For Android, we should catch the incoming 'Intent' in the 'onCreate' method, create a 'MethodChannel', and send the URL to it if the application is launched via a deep link. Here's a snippet showcasing this process:
1 private val CHANNEL = "test.deeplink.flutter.dev/channel" 2 3 private var startString: String? = null 4 5 override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { 6 MethodChannel(flutterEngine.dartExecutor, CHANNEL).setMethodCallHandler { call, result -> 7 if (call.method == "initialLink") { 8 if (startString != null) { 9 result.success(startString) 10 } 11 } 12 } 13 } 14 15 override fun onCreate(savedInstanceState: Bundle?) { 16 super.onCreate(savedInstanceState) 17 val intent = getIntent() 18 startString = intent.data?.toString() 19 } 20
The code creates a channel named 'test.deeplink.flutter.dev/channel'. Then, in our 'onCreate' method, we extract the data from the starting intent (if any) and save it to 'startString'.
In iOS, we need to perform a similar task, but the implementation will look a bit different since we handle this via 'AppDelegate.swift'. The following implementation is done using Swift:
1 @UIApplicationMain 2 @objc class AppDelegate: FlutterAppDelegate { 3 4 private var methodChannel: FlutterMethodChannel? 5 6 override func application( 7 _ application: UIApplication, 8 didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]? 9 ) -> Bool { 10 11 let controller = window.rootViewController as! FlutterViewController 12 methodChannel = FlutterMethodChannel(name: "poc.deeplink.flutter.dev/cnannel", binaryMessenger: controller) 13 14 methodChannel?.setMethodCallHandler({ (call: FlutterMethodCall, result: FlutterResult) in 15 guard call.method == "initialLink" else { 16 result(FlutterMethodNotImplemented) 17 return 18 } 19 }) 20 21 GeneratedPluginRegistrant.register(with: self) 22 return super.application(application, didFinishLaunchingWithOptions: launchOptions) 23 } 24 } 25
In the code above, we're creating the same method channel and setting up a method call handler. We're looking for a 'call.method' named 'initialLink'.
Flutter deep linking not only caters to situations where the link is clicked to initiate the application, but it also handles cases where the link is followed when the application is already running.
While handling deep linking in Flutter, an integral situation you need to manage is the scenario when a user clicks on a deep link while the application is already running. In such a case, we need to ensure the application can respond to the newly clicked link. Let's see how we can accomplish this for both Android and iOS.
In Android, we override the 'onNewIntent' method to process each incoming 'Intent'. If the action is a deep linking action, we will send an event into the 'EventChannel', which we added for this purpose into the 'configureFlutterEngine' method through a specially created Android 'BroadcastReceiver'.
1 private val EVENTS = "test.deeplink.flutter.dev/events" 2 private var linksReceiver: BroadcastReceiver? = null 3 4 override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { 5 EventChannel(flutterEngine.dartExecutor, EVENTS).setStreamHandler( 6 object : EventChannel.StreamHandler { 7 override fun onListen(args: Any?, events: EventSink) { 8 linksReceiver = createChangeReceiver(events) 9 } 10 override fun onCancel(args: Any?) { 11 linksReceiver = null 12 } 13 } 14 ) 15 } 16 17 override fun onNewIntent(intent: Intent) { 18 super.onNewIntent(intent) 19 if (intent.action === Intent.ACTION_VIEW) { 20 linksReceiver?.onReceive(this.applicationContext, intent) 21 } 22 } 23 24 fun createChangeReceiver(events: EventSink): BroadcastReceiver? { 25 return object : BroadcastReceiver() { 26 override fun onReceive(context: Context, intent: Intent) { 27 val dataString = intent.dataString ?: 28 events.error("UNAVAILABLE", "Link unavailable", null) 29 events.success(dataString) 30 } 31 } 32 } 33
This Android implementation captures deep link actions and sends the information of clicked links to the Flutter side.
For iOS, it's a bit different. Here, we have to create a 'FlutterStreamHandler' and handle every link that we get when the application is in the background. Let's modify 'AppDelegate.swift' accordingly.
1 @UIApplicationMain 2 @objc class AppDelegate: FlutterAppDelegate { 3 private var eventChannel: FlutterEventChannel? 4 5 private let linkStreamHandler = LinkStreamHandler() 6 7 override func application( 8 _ application: UIApplication, 9 didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]? 10 ) -> Bool { 11 12 let controller = window.rootViewController as! FlutterViewController 13 eventChannel = FlutterEventChannel(name: "test.deeplink.flutter.dev/events", binaryMessenger: controller) 14 15 GeneratedPluginRegistrant.register(with: self) 16 eventChannel?.setStreamHandler(linkStreamHandler) 17 return super.application(application, didFinishLaunchingWithOptions: launchOptions) 18 } 19 20 override func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool { 21 return linkStreamHandler.handleLink(url.absoluteString) 22 } 23 } 24 25 class LinkStreamHandler:NSObject, FlutterStreamHandler { 26 var eventSink: FlutterEventSink? 27 var queuedLinks = [String]() 28 29 func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? { 30 self.eventSink = events 31 queuedLinks.forEach({ events($0) }) 32 queuedLinks.removeAll() 33 return nil 34 } 35 36 func onCancel(withArguments arguments: Any?) -> FlutterError? { 37 self.eventSink = nil 38 return nil 39 } 40 41 func handleLink(_ link: String) -> Bool { 42 guard let eventSink = eventSink else { 43 queuedLinks.append(link) 44 return false 45 } 46 eventSink(link) 47 return true 48 } 49 } 50
With the preceding implementations for both Android and iOS, you now have a Flutter application that fully supports deep linking and is equipped to handle deep links clicked both at the time of launch and in the background.
As we integrate deep linking in Flutter, it's essential to structure our app's architecture in a way that it can efficiently manage the state and logic flow. One such pattern recommended for larger applications or those with complex workflows is the BLoC (Business Logic Component) pattern.
BLoC is a design pattern that segregates the presentation layer from the business logic layer to offer a predictable state management solution. It applies the concept of streams and sinks, where sinks accept input and streams provide output, and leverages reactive programming to manage the lifecycle of data.
By handling your deep links in a BLoC, we will ensure our code is not tightly bound to the UI, and we have a centralized place for the logic.
BLoC creates two channels: one 'EventChannel' to listen for background deep link events, and a 'MethodChannel' to catch the deep link if the app is started from a deep link. A separate method ('_onRedirected') is included to handle redirects.
In your Flutter app, you would use this BLoC as part of the widget that you want to reload or change when a deep link is clicked.
In addition to iOS and Android, deep linking in Flutter also supports web applications. By adopting deep links, you can direct users to the desired piece of content in your Flutter web application - all with a simple web URL.
Navigating through websites is inherently different from navigating through mobile applications. When users visit a website, they often expect to use regular URLs to jump directly to the specific content they need. In turn, they anticipate that they can share these URLs or bookmark them for later use. This integration of deep links provides simplicity and enhances user experience.
Implementing deep links in Flutter web applications is mainly a matter of setting up proper routing in your Flutter code. The package 'flutter_web_plugins' allows Flutter web to subscribe to browser location changes. This way, deep links can navigate directly to any route within your web application.
Here's an example of how you might set up routing in your application:
1 void main() { 2 runApp(MaterialApp( 3 initialRoute: '/', 4 routes: { 5 // The slash refers to the "root" route. In a web application, it typically represents the base URI. 6 '/': (context) => HomePage(), 7 '/about': (context) => AboutPage(), 8 '/contact': (context) => ContactPage(), 9 }, 10 )); 11 } 12
In the above example, we have three pages: 'HomePage', 'AboutPage', and 'ContactPage'. If a user visits the '/about' URL, they will be served with the 'AboutPage'.
Testing deep links in a Flutter web application is straightforward. Visiting any of the specified routes directly will navigate to the corresponding page.
With this, you're now equipped to handle deep linking in Flutter web applications, enhancing your web app's navigation and usability.
While the approach outlined in this guide provides a sturdy and flexible solution for deep linking in Flutter, there are alternative methods and tools available which can simplify this process or offer extended functionalities.
Firebase Dynamic Links affords you the capability to create links that can lead users to any location in your iOS, Android, or web application. These links survive the app installation process, therefore your app can access the link data even after the app is installed. This is useful when you want to encourage users to install your application.
Firebase Dynamic Links work even when your app is not installed. Upon clicking, it takes the user to related content on your website. Once the app is installed, the user is automatically taken to the linked content within your app.
If you prefer a third-party package solution, Evo Stamatov's 'uni_links' package for Flutter is a robust choice. It offers easy-to-use APIs to parse incoming URLs obtained through deep links.
To add it to your project, include it in your 'pubspec.yaml' and call 'getInitialLink()' to fetch the initial link that launched the app.
Remember, while these alternatives can simplify certain tasks, they do come with the overhead of depending on an external package or service.
By exploring these alternative methods, not only do you add an extra level of sophistication to your Flutter deep linking skills, but you also keep your applications versatile and user-friendly.
Incorporating deep links into your Flutter applications enables a more seamless experience for users, allowing for direct navigation to specific content or pages. In this blog post, we explored how to achieve deep linking in Flutter for iOS, Android, and the web.
Before you go, I'd like to introduce you to WiseGPT, a remarkable plugin that generates code for APIs in your React project. With no output size limits and seamless integration into your coding style, WiseGPT eliminates manual API management. It automatically creates models and functions, and handles all API requests, response parsing, and error management, leaving you free to focus on creativity. Say goodbye to prompts and welcome the promptless power of WiseGPT!
WiseGPT
Thank you for journeying through this guide on implementing and understanding deep links in Flutter. We trust it has been a helpful resource.
Tired of manually designing screens, coding on weekends, and technical debt? Let DhiWise handle it for you!
You can build an e-commerce store, healthcare app, portfolio, blogging website, social media or admin panel right away. Use our library of 40+ pre-built free templates to create your first application using DhiWise.