loadChildModule method

  1. @protected
Future<Null> loadChildModule(
  1. LifecycleModule? childModule
)

Public method to async load a child module and register it for lifecycle management.

If an Exception is thrown during the call to the parent onWillLoadChildModule it will be emitted on the willLoadChildModule lifecycle stream. The returned Future will also resolve with this exception.

If an Exception is thrown during the call to the child onLoad it will be emitted on the didLoadChildModule lifecycle stream. The returned Future will also resolve with this exception.

If an Exception is thrown during the call to the parent onDidLoadChildModule it will be emitted on the didLoadChildModule lifecycle stream. The returned Future will also resolve with this exception.

Attempting to load a child module after a module has been unloaded will throw a StateError.

Implementation

@protected
Future<Null> loadChildModule(LifecycleModule? childModule) {
  if (isOrWillBeDisposed) {
    return _buildDisposedOrDisposingResponse(methodName: 'loadChildModule');
  }

  if (childModule == null || _childModules.contains(childModule)) {
    return Future.value(null);
  }

  if (isUnloaded || isUnloading) {
    var stateLabel = isUnloaded ? 'unloaded' : 'unloading';
    return Future.error(
        StateError('Cannot load child module when module is $stateLabel'),
        StackTrace.current);
  }

  final completer = Completer<Null>();
  onWillLoadChildModule(childModule).then((_) async {
    // It is possible to reach this point due to the asynchrony of onWillLoadChildModule.
    // In that case, simply do not load the child module and instead dispose it.
    if (isUnloaded || isUnloading) {
      await childModule.dispose();
      completer.complete();
      return;
    }

    _willLoadChildModuleController.add(childModule);

    final childModuleWillUnloadSub = listenToStream(
        childModule.willUnload, _onChildModuleWillUnload,
        onError: _willUnloadChildModuleController.addError);
    final childModuleDidUnloadSub = listenToStream(
        childModule.didUnload, _onChildModuleDidUnload,
        onError: (error, stackTrace) =>
            _didUnloadChildModuleController.addError);

    // The child module may not reach an unloaded state successfully, but
    // should always eventually be disposed. For this reason, we listen for
    // its disposal before removing it from the list of child modules.
    // ignore: unawaited_futures
    childModule.didDispose.then((_) {
      _childModules.remove(childModule);
    });

    try {
      manageDisposable(childModule);
      _childModules.add(childModule);
      childModule.parentContext = _loadContext;

      await childModule.load();
      try {
        await onDidLoadChildModule(childModule);
      } catch (error, stackTrace) {
        _logger.severe(
          'Exception in onDidLoadChildModule ($name)',
          error,
          stackTrace,
        );
        rethrow;
      }
      _didLoadChildModuleController.add(childModule);
      completer.complete();
    } catch (error, stackTrace) {
      // If the child module failed to load, we can dispose of it and cleanup
      // any state/subscriptions related to it.
      _childModules.remove(childModule);
      await childModule.dispose();
      await childModuleWillUnloadSub.cancel();
      await childModuleDidUnloadSub.cancel();

      _didLoadChildModuleController.addError(error, stackTrace);
      completer.completeError(error, stackTrace);
    } finally {
      childModule.parentContext = null;
    }
  }).catchError((Object error, StackTrace stackTrace) {
    _logger.severe(
      'Exception in onWillLoadChildModule ($name)',
      error,
      stackTrace,
    );
    _willLoadChildModuleController.addError(error, stackTrace);
    completer.completeError(error, stackTrace);
  });

  return completer.future;
}