unregister<T extends Object> method

Resolvable<Option<T>> unregister<T extends Object>({
  1. Entity groupEntity = const DefaultEntity(),
  2. bool traverse = true,
  3. bool removeAll = true,
  4. bool triggerOnUnregisterCallbacks = true,
})

Unregisters a dependency.

Honors the documented contract:

  • If traverse is false, parents are not walked. Only this container is touched.
  • If removeAll is true (the default), the dependency is removed from this container and every parent that has a matching registration. When false, only the first matching registration (this-first, parent-walk thereafter) is removed.
  • If triggerOnUnregisterCallbacks is true, every removed dependency's onUnregister callback fires (sequentially, in container-walk order). Returns once all callbacks have completed.

The returned Option<T> is the value of the first removed dependency (or None if nothing was removed).

Implementation

Resolvable<Option<T>> unregister<T extends Object>({
  Entity groupEntity = const DefaultEntity(),
  bool traverse = true,
  bool removeAll = true,
  bool triggerOnUnregisterCallbacks = true,
}) {
  assert(T != Object, 'T must be specified and cannot be Object.');
  final g = groupEntity.preferOverDefault(focusGroup);
  final removed = <Dependency>[];

  // Walk the FULL ancestor chain (with cycle detection) — not just direct
  // parents — so `unregister(traverse: true, removeAll: true)` and
  // `isRegistered(traverse: true)` agree on which deps exist. Previously
  // `unregister` only touched `this` and direct parents, so a grandparent
  // registration that `isRegistered` could see would survive a deep
  // unregister — a real reliability hole on three-level hierarchies.
  final containers = traverse ? _allAncestors() : <DI>[this as DI];
  walk:
  for (final di in containers) {
    switch (di.removeDependency<T>(groupEntity: g)) {
      case Some(value: final dep):
        removed.add(dep);
        // Clean up any pending untilExactlyK completers for this type.
        // The cast is guarded because DIBase itself does NOT require
        // SupportsMixinK — only the concrete `DI` mixes it in.
        (di as SupportsMixinK).cleanupCompleters(
          TypeEntity(T),
          groupEntity: g,
        );
        if (!removeAll) break walk;
      case None():
        if (!removeAll) break walk;
    }
  }

  if (removed.isEmpty) return Sync.okValue(None<T>());

  return _runOnUnregisterChain<T>(
    removed: removed,
    triggerOnUnregisterCallbacks: triggerOnUnregisterCallbacks,
  );
}