Writing4 min read
Flutter Deep Link with Command Pattern: Best Practices for Scalability
In this article, instead of focusing solely on deep link setup, we’ll explore ways to build a more flexible and scalable structure for handling multiple links.

Hello Everyone!
Today, we’re diving into one of the must-have features of modern mobile applications: deep link management. Deep links provide an impressive mechanism to direct users to specific content within your app with just a single click. Whether you’re on iOS or Android, Flutter offers a robust infrastructure for this.
So, what exactly is a deep link? Simply put, it’s a link that points to a specific destination within your app, much like a URL for websites. When a user clicks on this link, the operating system steps in to check if your app is installed. If the app is installed, the link opens directly within the app, leading the user to the intended content. If the app isn’t installed, the operating system redirects the user to the appropriate app store.
This seamless functionality not only optimizes user experience but also provides a valuable gateway for driving traffic to your app. For example, links included in email campaigns or social media posts can guide users to the desired content within seconds.
In this article, instead of focusing solely on deep link setup, we’ll explore ways to build a more flexible and scalable structure for handling multiple links. To achieve this, we’ll utilize the app_links package.

DeepLinkClient
DeepLinkClient is an interface used for managing deep links in your application. This interface facilitates the easy replacement of dependencies from the outside and helps create a flexible structure. The main responsibilities of the interface are:
init: Initializes the processes needed to listen for deep links and retrieve the initial link.checkInitialUri: Checks the initial URI when the application is first launched.dispose: Cancels listeners to prevent unnecessary resource consumption.
import 'dart:async';
import 'package:app_links/app_links.dart';
abstract interface class DeepLinkClient {
factory DeepLinkClient.instance() => _DeepLinkClientImpl();
Future<void> init();
void dispose();
void checkInitialUri();
}
final class _DeepLinkClientImpl implements DeepLinkClient {
final AppLinks _appLinks = AppLinks();
StreamSubscription<Uri>? _linkSubscription;
Uri? initialUri;
@override
Future<void> init() async {
initialUri = await _appLinks.getInitialLink();
_linkSubscription = _appLinks.uriLinkStream.listen((uri) {
_checkUri(uri, isInitial: false);
});
}
@override
void checkInitialUri() {
if (initialUri != null) {
_checkUri(initialUri, isInitial: true);
initialUri = null;
}
}
@override
void dispose() {
_linkSubscription?.cancel();
}
void _checkUri(Uri? uri, {required bool isInitial}) {
if (uri == null) return;
unawaited(DeeplinkNavigator.instance.execute(uri.path, isInitial: isInitial));
}
}
IDeeplinkAction:
IDeeplinkAction is an interface designed to define a separate action for each type of deep link. This structure is based on the Command Pattern design and allows for defining different behaviors for each link.
isSatisfied: Checks whether the incoming link matches the criteria for this action.execute: Defines the operation to be performed for a specific link.
This interface makes the deep link logic in your application more extensible and organized.
Let’s not get hung up on the method names; different names can be used without compromising the overall meaning.
abstract interface class IDeeplinkAction {
bool isSatisfied(String path);
Future<void> execute(String path, {required bool isInitial});
}
DeepLinkType
DeepLinkType is an enum that defines the types of deep links supported in your application. This structure is used to easily match links and determine which action should be executed.
store: Links starting with/store.productDetail: Links starting with/productDetail.getDeepLinkType: Identifies the type of the incoming link.
This enum organizes different types of links in your application in a structured and manageable way.
import 'package:collection/collection.dart';
enum DeepLinkType {
store('/store'),
productDetail('/productDetail');
const DeepLinkType(this.rawValue);
final String rawValue;
static DeepLinkType? getDeepLinkType(String path) {
final type = DeepLinkType.values.firstWhereOrNull((element) => path.contains(element.rawValue));
return type;
}
}
StoreAction
StoreAction is an action class designed to open the store screen.
final class StoreAction implements IDeeplinkAction {
@override
bool isSatisfied(String path) => DeepLinkType.getDeepLinkType(path) == DeepLinkType.store;
@override
Future<void> execute(String path, {required bool isInitial}) async {
App.router.push(StoreRoute());
}
}
ProductDetailAction
ProductDetailAction is an action class designed to open the product detail screen.
final class ProductDetailAction implements IDeeplinkAction {
@override
bool isSatisfied(String path) => DeepLinkType.getDeepLinkType(path) == DeepLinkType.productDetail;
@override
Future<void> execute(String path, {required bool isInitial}) async {
final productId = path?.split('/').last;
if (productId case final String productId) {
App.router.push(ProductDetailRoute(productId: productId));
}
}
}
DeeplinkNavigator
DeeplinkNavigator is a central routing structure used for handling deep links. This class determines the appropriate action for incoming links and initiates the corresponding process.
items: Contains all the deep link actions defined in your application (e.g.,StoreAction,ProductDetailAction).execute: Checks the incoming URI and executes the appropriate action.
This structure centralizes the management of deep links, making the code more organized and extensible.
import 'package:collection/collection.dart';
final class DeeplinkNavigator {
DeeplinkNavigator._();
static final DeeplinkNavigator instance = DeeplinkNavigator._();
final items = <IDeeplinkAction>[
StoreAction(),
ProductDetailAction(),
];
Future<void> execute(String path, {required bool isInitial}) async {
await items.firstWhereOrNull((element) => element.isSatisfied(path))?.execute(path, isInitial: isInitial);
}
}
Conclusion
In this article, we explored the deep linking mechanism and how it can be made flexible and extensible using the Command Pattern. With Flutter’s robust infrastructure, it is possible to quickly direct users to the right content.
The Command Pattern allows you to define separate actions for each type of link, making the code modular and easily extendable. This approach not only enhances the user experience but also supports the growth of your application.