Android App Links, Deep Links, iOS Universal Links and Custom URL schemes handler (desktop included).
This plugin allows you to:
- catch HTTPS URLs to open your app instead of the browser (App Link / Universal Link).
- catch custom schemes to open your app (Deep Link / Custom URL scheme).
Before using the plugin, you'll need to setup each platforms you target.
All those configurations below are accessible in the example project.
- App Links: Documentation
- Deep Links: Documentation
Notes:
- By default, flutter Activity is set with
android:launchMode="singleTop"
. This is perfectly fine and expected, but this launches another instance of your app, specifically for the requested view.
If you don't want this behaviour, you can setandroid:launchMode="singleInstance"
in yourAndroidManifest.xml
and avoid another flutter warmup.
- Universal Links: Documentation
- Custom URL schemes: Documentation
Warning:
If you have a custom AppDelegate with overriden methods either:
- application(_:willFinishLaunchingWithOptions:)
- or application(_:didFinishLaunchingWithOptions:)
Both methods must call super and return true to enable app link workflow. If you can't respect those two constraints:
Here's how to setup
import app_links
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
...
// Retrieve the link from the parameters
if let url = AppLinks.shared.getLink(launchOptions: launchOptions) {
// We have a link, propagate it to your Flutter app
AppLinks.shared.handleLink(url: url)
}
return false
}
}
If you have a scene-based app.
Here's how to setup
import app_links
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
for context in URLContexts {
AppLinks.shared.handleLink(url: context.url)
}
}
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
if let url = userActivity.webpageURL {
AppLinks.shared.handleLink(url: url)
}
}
}
How to setup
Don't be afraid, this is just copy/paste commands to follow. But yes, it we will be a bit painful...
Declare this method in <PROJECT_DIR>\windows\runner\win32_window.h as private method.
// Dispatches link if any.
// This method enables our app to be with a single instance too.
// This is mandatory if you want to catch further links in same app.
bool SendAppLinkToInstance(const std::wstring& title);
Add this inclusion in <PROJECT_DIR>\windows\runner\win32_window.cpp
#include "app_links/app_links_plugin_c_api.h"
Add this method in <PROJECT_DIR>\windows\runner\win32_window.cpp
bool Win32Window::SendAppLinkToInstance(const std::wstring& title) {
// Find our exact window
HWND hwnd = ::FindWindow(kWindowClassName, title.c_str());
if (hwnd) {
// Dispatch new link to current window
SendAppLink(hwnd);
// (Optional) Restore our window to front in same state
WINDOWPLACEMENT place = { sizeof(WINDOWPLACEMENT) };
GetWindowPlacement(hwnd, &place);
switch(place.showCmd) {
case SW_SHOWMAXIMIZED:
ShowWindow(hwnd, SW_SHOWMAXIMIZED);
break;
case SW_SHOWMINIMIZED:
ShowWindow(hwnd, SW_RESTORE);
break;
default:
ShowWindow(hwnd, SW_NORMAL);
break;
}
SetWindowPos(0, HWND_TOP, 0, 0, 0, 0, SWP_SHOWWINDOW | SWP_NOSIZE | SWP_NOMOVE);
SetForegroundWindow(hwnd);
// END Restore
// Window has been found, don't create another one.
return true;
}
return false;
}
Add the call to the previous method in Create
bool Win32Window::Create(const std::wstring& title,
const Point& origin,
const Size& size) {
if (SendAppLinkToInstance(title)) {
return false;
}
Destroy();
...
Great!
Now you can register your own scheme. On Windows, URL protocols are setup in the Windows registry.
This package won't do it for you (and will never sorry).
You may achieve it with using the win32_registry package with the following code to register the URL protocol:
Future<void> register(String scheme) async {
String appPath = Platform.resolvedExecutable;
String protocolRegKey = 'Software\\Classes\\$scheme';
RegistryValue protocolRegValue = const RegistryValue(
'URL Protocol',
RegistryValueType.string,
'',
);
String protocolCmdRegKey = 'shell\\open\\command';
RegistryValue protocolCmdRegValue = RegistryValue(
'',
RegistryValueType.string,
'"$appPath" "%1"',
);
final regKey = Registry.currentUser.createKey(protocolRegKey);
regKey.createValue(protocolRegValue);
regKey.createKey(protocolCmdRegKey).createValue(protocolCmdRegValue);
}
How to setup
Add this XML chapter in your macos/Runner/Info.plist
inside <plist version="1.0"><dict>
chapter:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<!-- abstract name for this URL type (you can leave it blank) -->
<string>sample_name</string>
<key>CFBundleURLSchemes</key>
<array>
<!-- your schemes -->
<string>sample</string>
</array>
</dict>
</array>
Done!
How to setup
Apply the following changes to your linux/my_application.cc
file:
diff --git a/example/linux/my_application.cc b/example/linux/my_application.cc
index 0ba8f43..f07f765 100644
--- a/example/linux/my_application.cc
+++ b/example/linux/my_application.cc
@@ -17,6 +17,13 @@ G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
// Implements GApplication::activate.
static void my_application_activate(GApplication* application) {
MyApplication* self = MY_APPLICATION(application);
+
+ GList* windows = gtk_application_get_windows(GTK_APPLICATION(application));
+ if (windows) {
+ gtk_window_present(GTK_WINDOW(windows->data));
+ return;
+ }
+
GtkWindow* window =
GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
@@ -78,7 +85,7 @@ static gboolean my_application_local_command_line(GApplication* application, gch
g_application_activate(application);
*exit_status = 0;
- return TRUE;
+ return FALSE;
}
// Implements GObject::dispose.
@@ -99,6 +106,6 @@ static void my_application_init(MyApplication* self) {}
MyApplication* my_application_new() {
return MY_APPLICATION(g_object_new(my_application_get_type(),
"application-id", APPLICATION_ID,
- "flags", G_APPLICATION_NON_UNIQUE,
+ "flags", G_APPLICATION_HANDLES_COMMAND_LINE | G_APPLICATION_HANDLES_OPEN,
nullptr));
Notes:
- Please ensure your
APPLICATION_ID
is the same as your desktop file name if you're using Flathub. - Please ensure you have added this section in your
snapcraft.yaml
file if you're using SnapStore:
slots:
dbus-appflowy:
interface: dbus
bus: session
name: `APPLICATION_ID`
- You can refer to these two repositories for more details: FlatHub setup and Snapcraft setup.
Done!
Simpliest usage with a single stream
final _appLinks = AppLinks();
// Subscribe to all events when app is started.
// (Use allStringLinkStream to get it as [String])
_appLinks.allUriLinkStream.listen((uri) {
// Do something (navigation, ...)
});
Decomposed usage
final _appLinks = AppLinks();
// Get the initial/first link.
// This is useful when app was terminated (i.e. not started)
final uri = await _appLinks.getInitialAppLink();
// Do something (navigation, ...)
// Subscribe to further events when app is started.
// (Use stringLinkStream to get it as [String])
_linkSubscription = _appLinks.uriLinkStream.listen((uri) {
// Do something (navigation, ...)
});
...
// Maybe later. Get the latest link.
final uri = await _appLinks.getLatestAppLink();
The following commands will help you to test links.
adb shell am start -a android.intent.action.VIEW \
-d "sample://open.my.app/#/book/hello-world"
For App Links, you can also test it from Android Studio: Documentation.
Android 13:
- While in development, you may need to manually activate your links.
- Go to your app info/settings: Open by default > Add link > (your links should be already filled).
/usr/bin/xcrun simctl openurl booted "app://www.example.com/#/book/hello-world"
Open your browser and type in your address bar:
sample://foo/#/book/hello-world2