Skip to content

DeeplinkManager

View raw .md
On this page

Introduction#

DeeplinkManager is the central coordinator of the magic_deeplink plugin. It owns a single platform driver, an ordered list of URI handlers, and a broadcast stream that emits every incoming deep link. The DeeplinkServiceProvider creates and wires the manager during app boot — consuming code rarely needs to interact with it directly.

dart
// Resolve the singleton anywhere
final manager = DeeplinkManager();

// Listen to all incoming deep links
manager.onLink.listen((uri) {
  print('Received: $uri');
});

Singleton Pattern#

DeeplinkManager enforces a process-wide singleton via a private named constructor and a static final field:

dart
class DeeplinkManager {
  static final DeeplinkManager _instance = DeeplinkManager._internal();

  factory DeeplinkManager() => _instance;

  DeeplinkManager._internal();
}

Every call to DeeplinkManager() returns the same _instance. There is no public constructor that creates a new object.

Testing reset: Because the singleton persists across tests, each setUp must clear mutable state before asserting:

dart
setUp(() {
  manager.forgetHandlers();
  manager.forgetDriver();
});

Driver Orchestration#

The manager delegates all platform I/O to a single DeeplinkDriver. The driver is not set at construction time — it is injected by DeeplinkServiceProvider.boot() after the IoC container is ready.

Setting a Driver#

dart
void setDriver(DeeplinkDriver driver)

Assigns the active driver. Replaces any previously set driver without disposing it — the provider is responsible for driver lifecycle.

dart
manager.setDriver(AppLinksDriver());

Removing a Driver#

dart
void forgetDriver()

Sets _driver to null. Subsequent calls to driver (the getter) will throw a DeeplinkException with code NO_DRIVER until a new driver is set.

dart
DeeplinkDriver get driver {
  if (_driver == null) {
    throw DeeplinkException(
      'No deep link driver configured. Make sure to call setDriver() or register the service provider.',
      code: 'NO_DRIVER',
    );
  }
  return _driver!;
}

Driver Contract#

dart
abstract class DeeplinkDriver {
  String get name;
  bool get isSupported;
  Future<void> initialize(Map<String, dynamic> config);
  Future<Uri?> getInitialLink();
  Stream<Uri> get onLink;
  void dispose();
}

The provider calls initialize() once during boot, then subscribes to onLink and pipes each emitted URI into manager.handleUri().

Handler Chain#

The manager maintains an ordered List<DeeplinkHandler>. When a URI arrives, the list is iterated in insertion order and the first handler whose canHandle() returns true wins — subsequent handlers are skipped.

Registering Handlers#

dart
void registerHandler(DeeplinkHandler handler)

Appends the handler if it is not already in the list (identity check via List.contains). Duplicate registrations are silently ignored.

dart
manager.registerHandler(RouteDeeplinkHandler());
manager.registerHandler(OneSignalDeeplinkHandler());

Checking presence:

dart
bool hasHandler(DeeplinkHandler handler)

Returns true if the exact handler instance is registered.

Removing Handlers#

dart
void forgetHandlers()

Clears the entire handler list. Used in test teardown and provider re-boot scenarios.

Handler Contract#

dart
abstract class DeeplinkHandler {
  bool canHandle(Uri uri);
  Future<bool> handle(Uri uri);
}

canHandle is synchronous — it inspects the URI and returns a boolean with no side effects. handle performs the actual work and returns true on success. Handlers must never throw; they return false on failure.

URI Stream#

dart
final StreamController<Uri> _linkController = StreamController<Uri>.broadcast();

Stream<Uri> get onLink => _linkController.stream;

A broadcast controller is used so multiple listeners (e.g., analytics, routing, tests) can subscribe independently without coordinating. The stream is never closed during normal app operation — it lives for the full process lifetime alongside the singleton.

Listening:

dart
manager.onLink.listen((uri) {
  // Called for every URI that passes through handleUri()
});

handleUri Flow#

dart
Future<bool> handleUri(Uri uri) async {
  _linkController.add(uri);
  for (final handler in _handlers) {
    if (handler.canHandle(uri)) {
      return await handler.handle(uri);
    }
  }
  return false;
}

The method does two things unconditionally and sequentially:

  1. Emit — the URI is added to _linkController before any handler runs. Every stream subscriber receives the URI regardless of whether a handler exists.
  2. Dispatch — the handler list is iterated in order. The first handler for which canHandle(uri) is true is invoked. If it succeeds, true is returned and iteration stops. If no handler matches, false is returned.

The stream emission is intentionally first so that observers see all URIs, not only those that are handled.

When an app is launched by tapping a deep link, the link is available via getInitialLink(). The manager caches the result after the first driver call so repeated calls are cheap and deterministic:

dart
Future<Uri?> getInitialLink() async {
  if (_initialLinkFetched) {
    return _initialLink;
  }

  _initialLink = await driver.getInitialLink();
  _initialLinkFetched = true;
  return _initialLink;
}
State Behavior
First call (_initialLinkFetched == false) Delegates to driver.getInitialLink(), stores result, sets flag
Subsequent calls (_initialLinkFetched == true) Returns cached _initialLink immediately, no driver call

_initialLink may be null if the app was launched normally (not via a deep link). The null result is cached just like a non-null URI — the flag is set either way.

Testing implication: forgetDriver() does not reset _initialLinkFetched. If a test exercises initial link caching, it must interact with the singleton before the flag is set, or use a fresh manager instance via the private constructor in test subclasses.

Data Flow#

text
App launch
    │
    ▼
DeeplinkServiceProvider.boot()
    │
    ├─ manager.setDriver(AppLinksDriver())
    │
    ├─ driver.initialize(config)
    │
    ├─ driver.onLink.listen(manager.handleUri)   ◄─── ongoing links
    │
    └─ manager.getInitialLink()
            │
            ▼
        driver.getInitialLink()          (first call only)
            │
            ▼
        manager.handleUri(uri)
                │
                ├─ _linkController.add(uri)      ──► onLink stream subscribers
                │
                └─ iterate _handlers in order
                        │
                        ├─ handler.canHandle(uri) == false  ──► next handler
                        │
                        └─ handler.canHandle(uri) == true
                                │
                                ▼
                            handler.handle(uri)
                                │
                                └─ return bool  ──► handleUri returns
  • lib/src/deeplink_manager.dart — full source
  • lib/src/drivers/deeplink_driver.dart — driver contract
  • lib/src/handlers/deeplink_handler.dart — handler contract
  • lib/src/providers/deeplink_service_provider.dart — wires manager during app boot
  • lib/src/exceptions/deeplink_exception.dart — DeeplinkException with NO_DRIVER code
esc

Search index did not load.

No match for "".

Try a class like rounded-lg or a widget like WButton.