Back
How to set up Deep Linking with Flutter for macOS apps
Learn how to set up deep linking for macOS apps using Flutter in this step-by-step guide. Create seamless navigation experiences, enabling users to access specific content directly within your app.
How to set up Deep Linking with Flutter for macOS apps
In today's world, the ability to open desktop applications with specific parameters is a staple feature that any modern application can easily adopt. Setting up this functionality with Flutter on macOS is quite easy without downloading a bloated package or plugin.
I am Nathan Courtney, Head of DevOps at Pieces.app. I focus on integrating system APIs with Flutter to leverage various features at the operating system level.
Whether your app is currently running or not, we are going to look into how we can utilize Flutter method channels to create a custom URL scheme or a deep link to launch your macOS application with certain arguments or interact when a user opens the link within their browser.
What is Deep Linking?
A deep link allows your app to be opened from specific URLs, carrying parameters for personalized or contextual interactions. This feature is essential for smooth navigation and enhances user experience.
How to set up Deep Linking for MacOS
First, let's start off with a simple Flutter template by creating a new Flutter project on macOS:
Navigate to the new directory you created, /deep_linking
, and create a new file and class within our /deep_linking/lib
directory.
Mine will be /deep_linking/lib/deep_link_handler.dart
.
Let’s start by creating a simple class that will be the structure of our query parameters coming in from our URL.
This doesn’t have to be done, but we can do this to provide more structure to our code if we expect links to arrive in a similar manner and to throw any errors if a link comes in without the proper structure.
Next, within the same file, let's set up our Flutter method channel, which will handle all the incoming URL parameters that come in from the Swift side.
Our Flutter method channel will listen for messages coming in from the Swift side on the channel: "deep_linking/deep_link_handler
." We will extract each parameter coming in from Swift as a map
and print it out to our UI.
In your main.dart
file, we can initialize our class to spin up our method channel and start listening to events coming in from the Swift side:
We can also modify our application's home page to display our Query Parameters when they start coming in:
That is it for the Dart side of things, as the rest of the implementation resides within our application's Swift code.
Let's go ahead and build our Flutter application once, then open it up in Xcode.
Next, let's navigate to our Xcode Project file within /deep_linking/macos/Runner.xcworkspace
and double-click it to open it within XCode.
First, we will want to navigate to our Info.plist
to set the URL our app should respond to.
The easiest way to do this is to right click our Info.plist file and add the following XML code to the document:
Let’s break down what some of this does.
CFBundleURLTypes: This key holds an array of URL types the app can handle. Each type is defined by a dictionary, which specifies details about how the URL should be processed.
Inside the dictionary:
CFBundleTypeRole: This field specifies the app's role when handling this URL type. Setting it to "Viewer" means the app opens the URL for viewing.
CFBundleURLName: This is a unique identifier for the URL type. It is set to "com.example.deepLinking", which can be any unique string, typically following a reverse-domain style.
CFBundleURLSchemes: This key holds an array of URL schemes that the app will recognize. In this case, it's set to ["deep-link-example"], meaning your app will respond to URLs that start with deep-link-example://.
With this configuration, macOS will open your app whenever a URL with the deep-link-example:// scheme is triggered, allowing you to capture parameters from the URL (like those after ?) in your app when launched.
Now that our app is set up to handle URLs that start with deep-link-example://
, it's time to implement the Swift code to send those URL parameters up to our Dart runtime.
One quick note: (and this may not be the case for you) Xcode's inline error detection can sometimes get a little wonky when dealing with Flutter applications. I have found that if you build the Flutter app from within Xcode once, it will properly detect any errors you have within your Swift code. You can see you are having this issue by navigating to MainFlutterWindow.swift in Xcode and see if you get this same error "No such module 'FlutterMacos":
If you do see this, simply click the "Play" button at the top left to build the application. Your issue should be resolved after the fact. You can then hit the "Stop" button to end the application's running.
Next, let's create a new Swift file named DeepLinkHandler.swift
that will handle incoming parameters while our application is running.
We can then start creating our class
to set up our Flutter Method Channel on the Swift side, handle Apple Events for our deep linking, and structure our query parameters using a nice Dictionary
that will conform to a Map
in Dart.
Currently, we are creating a function to register our method channel on “deep_linking/deep_link_handler
” and importing some other Swift APIs that we will use later for our Apple Events.
An important step we do not want to miss is calling our public register()
function within our MainFlutterWindow.swift
to properly link our Flutter Method Channel with the FlutterViewController()
. Doing so will finalize our connection to the Dart runtime and allow our DeepLinkHandler
Dart class to start listening to events captured by our DeepLinkHandler
Swift class.
Simply add the following line in your MainFlutterWindow.swift
file:
Your entire file will look something like this:
Let’s return to our DeepLinkHandler.swift
file and create a function to handle incoming Apple Events and a public function that will parse the URL for query parameters and send them to the Dart runtime via the method channel we previously created.
Let's start by creating a function to handle Apple Events. We will create handleDeepLinkQuery()
after the fact.
This Swift code defines a function handleAppleEvent
that is triggered when the app receives an Apple Event.
Here's how it works:
Extracting the Event Descriptor: The code attempts to retrieve the main parameter of the Apple Event (commonly the URL string for a deep link) by calling
paramDescriptor(forKeyword:)
with thekeyDirectObject
keyword.Converting to a String: If the event descriptor is successfully retrieved, it checks whether it contains a string value, which should be the URL. If either of these steps fails, the function returns early.
Creating a URL and Extracting Query Parameters:
If the URL string is valid, the function initializes a
URL
object.It then attempts to get the query part of the URL (everything after
?
), which contains the parameters.
Parsing and Handling Parameters:
If
params
(query string) is notnil
, the function separates the parameters by&
(common in query strings to separate key-value pairs).If parameters exist, it calls
handleDeepLinkQuery
withparams
to process or handle the parameters further.
Next, let's create a public function handleDeepLinkQuery()
to handle formatting our Query into a nice readable Dictionary that will conform to our Map in dart
Now, let's add some functions to register and unregister our class with the NSAppleEventManger
. In doing so, our macOS app will now accept incoming events. specifically the kAEGetURL
event, fired by the system through the given URL we set in the Info.plist file. This will be triggered when the application is opened via a URL scheme (such as deep-link-example://
).
Let’s break down each function:
registerAppleEventHandler()
:This function sets up a handler for the
kAEGetURL
Apple Event, which is typically triggered when the app is opened via a custom URL scheme.NSAppleEventManager.shared().setEventHandler(...)
registers self (the current instance of the class) as the handler for this event.The
@selector(self.handleAppleEvent(event:replyEvent:))
specifies that thehandleAppleEvent(event:replyEvent:)
method should be called when thekAEGetURL
event occurs.The
forEventClass
:AEEventClass(kInternetEventClass)
andandEventID: AEEventID(kAEGetURL)
arguments specify that this handler should only be triggered for URL events(kAEGetURL)
.
unregisterAppleEventHandler():
This function removes the event handler for the
kAEGetURL
event, effectively stopping the app from handling URL events until the handler is registered again.
We will call these functions on bootup and teardown of our application.
Go ahead and add the following lines of swift in your AppDelegate.swift
:
Here’s how it all works: When a URL with the custom scheme is opened, macOS triggers an Apple Event that our Swift code captures, parses, and sends to Flutter over the Method Channel.
Let’s test this all out now!
Run your Flutter application, and attempt to paste the following URL within your browser. Chrome will be an excellent test:deep-link-example://?id=123&foo=abc&bar=xyz
If everything is done correctly, you should see those query parameters printed out in the terminal and in the Flutter app's UI.
flutter: 123
flutter: abc
flutter: xyz
But we are not quite done yet! When you build out your production application, you will find that you will not receive any of these values from a cold start.
In this scenario, your application is not currently running in the background.
To fix that, we can add a snippet of code within our AppDelegate.swift
file to handle this:
Now, when your app isn’t actively running, we can propagate those query params to the Dart runtime on boot! To test this out, you will need to build a release version of your application and then place it within your /Applications directory. You may also need to launch it at least once for everything to register properly.
Now you’re all set and you should be able to flawlessly launch your desktop application through a deeplink, just like this.