Skip to content

[tool] Define modular dependency injection containers and bootstrapper - #190724

Merged
auto-submit[bot] merged 3 commits into
flutter:masterfrom
bkonyi:di/01-foundation
Aug 12, 2026
Merged

auto-submit[bot] merged 3 commits into
flutter:masterfrom
bkonyi:di/01-foundation

Conversation

@bkonyi

@bkonyi bkonyi commented Aug 7, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Part 1 of the modular dependency injection migration.

Defines the foundational modular dependency injection containers and bootstrapper for flutter_tools:

  1. ToolContext: Layer 1 OS wrappers (HostEnvironment) and Layer 2 SDK state (ToolConfiguration).
  2. Nullable Sub-contexts: AndroidContext and AppleContext for clean platform isolation.
  3. ToolDependencies: Topologically instantiates and manages the dependency graph at startup with explicit overrides and lazy closures.
  4. Hermetic Test Doubles: Includes dependency_injection_test.dart and fake context doubles.

Followed by Part 2 (#190922) for runner wiring.

Part of #47161

@github-actions github-actions Bot added tool Affects the "flutter" command-line tool. See also t: labels. team-android Owned by Android platform team labels Aug 7, 2026
@github-actions github-actions Bot added team-ios Owned by iOS platform team team-macos Owned by the macOS platform team labels Aug 11, 2026
@github-actions github-actions Bot removed team-ios Owned by iOS platform team team-macos Owned by the macOS platform team labels Aug 11, 2026
@bkonyi bkonyi changed the title [tool] Bootstrap explicit dependency injection and wire it into the runner [tool] Define modular dependency injection containers and bootstrapper Aug 11, 2026
@bkonyi bkonyi added CICD Run CI/CD and removed team-android Owned by Android platform team labels Aug 11, 2026
@github-actions github-actions Bot added the team-android Owned by Android platform team label Aug 11, 2026
@bkonyi
bkonyi marked this pull request as ready for review August 11, 2026 17:07
@bkonyi
bkonyi requested a review from a team as a code owner August 11, 2026 17:07
@bkonyi
bkonyi requested review from chingjun, gmackall and vashworth and removed request for a team August 11, 2026 17:07

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a structured dependency injection and context bootstrapping mechanism for flutter_tools by adding AndroidContext, AppleContext, ToolContext, and a central ToolDependencies bootstrapper, along with corresponding tests and fakes. Feedback on the implementation highlights that tool_dependencies.dart should use the resolved finalShutdownHooks and finalSystemClock.now() to maintain a single source of truth and ensure hermetic time-dependent behavior. Additionally, the fake context classes in fakes.dart should be refactored to use late final fields instead of returning new instances on every getter invocation, preserving state persistence and referential transparency in tests.

delegate: LocalFileSystem(
LocalSignals.instance,
Signals.defaultExitSignals,
shutdownHooks ?? ShutdownHooks(),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

A new ShutdownHooks instance is created here if shutdownHooks is null, instead of using the already resolved finalShutdownHooks defined on line 136. This breaks the single source of truth for shutdown hooks, meaning any hooks registered on finalShutdownHooks won't be coordinated with the LocalFileSystem's shutdown hooks.

Suggested change
shutdownHooks ?? ShutdownHooks(),
finalShutdownHooks,
References
  1. Avoid duplicating state: Keep only one source of truth. (link)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Addressed in commit 341b711.

Comment on lines +1212 to +1297
@override
Artifacts get artifacts => _artifacts ?? FakeArtifacts(fileSystem: fs);

@override
BotDetector get botDetector => _botDetector ?? const FakeBotDetector(false);

@override
Cache get cache => _cache ?? FakeCache(fileSystem: fs);

@override
Config get config => _config ?? FakeConfig();

@override
CustomDevicesConfig get customDevicesConfig =>
_customDevicesConfig ??
CustomDevicesConfig.test(fileSystem: fs, logger: logger, platform: platform);

@override
FlutterVersion get flutterVersion => _flutterVersion ?? FakeFlutterVersion();

@override
FileSystem get fs => _fs ?? MemoryFileSystem.test();

@override
Git get git => _git ?? Git(currentPlatform: platform, runProcessWith: processUtils);

@override
LocalEngineLocator get localEngineLocator =>
_localEngineLocator ??
LocalEngineLocator(
userMessages: userMessages,
logger: logger,
platform: platform,
fileSystem: fs,
flutterRoot: Cache.flutterRoot ?? '',
);

@override
Logger get logger => _logger ?? BufferLogger.test();

@override
TestCompilerNativeAssetsBuilder? get nativeAssetsBuilder => _nativeAssetsBuilder;

@override
OperatingSystemUtils get os => _os ?? FakeOperatingSystemUtils();

@override
OutputPreferences get outputPreferences => _outputPreferences ?? OutputPreferences.test();

@override
Platform get platform => _platform ?? FakePlatform();

@override
PreRunValidator get preRunValidator => _preRunValidator ?? const NoOpPreRunValidator();

@override
ProcessManager get processManager => _processManager ?? FakeProcessManager.any();

@override
ProcessUtils get processUtils =>
_processUtils ?? ProcessUtils(processManager: processManager, logger: logger);

@override
FlutterProjectFactory get projectFactory =>
_projectFactory ?? FlutterProjectFactory(fileSystem: fs, logger: logger);

@override
ShutdownHooks get shutdownHooks => _shutdownHooks ?? ShutdownHooks();

@override
Signals get signals => _signals ?? Signals.test();

@override
Stdio get stdio => _stdio ?? FakeStdio();

class FakeArtifacts extends Fake implements Artifacts {}
@override
SystemClock get systemClock => _systemClock ?? const SystemClock();

@override
AnsiTerminal get terminal => _terminal ?? AnsiTerminal(stdio: stdio, platform: platform);

@override
UserMessages get userMessages => _userMessages ?? UserMessages();

@override
FileSystemUtils get fileSystemUtils => FileSystemUtils(fileSystem: fs, platform: platform);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

In FakeToolContext, returning new instances of dependencies (like MemoryFileSystem, BufferLogger, FakeCache, etc.) on every getter invocation breaks referential transparency and state persistence. For example, accessing toolContext.fs twice will return two completely different, isolated in-memory file systems, causing state changes in one to be lost in the other.

Using late final fields instead of getters ensures that these dependencies are lazily initialized on first access and then cached for all subsequent accesses, while still allowing them to reference each other safely during initialization.

  @override
  late final Artifacts artifacts = _artifacts ?? FakeArtifacts(fileSystem: fs);

  @override
  late final BotDetector botDetector = _botDetector ?? const FakeBotDetector(false);

  @override
  late final Cache cache = _cache ?? FakeCache(fileSystem: fs);

  @override
  late final Config config = _config ?? FakeConfig();

  @override
  late final CustomDevicesConfig customDevicesConfig =
      _customDevicesConfig ??
      CustomDevicesConfig.test(fileSystem: fs, logger: logger, platform: platform);

  @override
  late final FlutterVersion flutterVersion = _flutterVersion ?? FakeFlutterVersion();

  @override
  late final FileSystem fs = _fs ?? MemoryFileSystem.test();

  @override
  late final Git git = _git ?? Git(currentPlatform: platform, runProcessWith: processUtils);

  @override
  late final LocalEngineLocator localEngineLocator =
      _localEngineLocator ??
      LocalEngineLocator(
        userMessages: userMessages,
        logger: logger,
        platform: platform,
        fileSystem: fs,
        flutterRoot: Cache.flutterRoot ?? '',
      );

  @override
  late final Logger logger = _logger ?? BufferLogger.test();

  @override
  TestCompilerNativeAssetsBuilder? get nativeAssetsBuilder => _nativeAssetsBuilder;

  @override
  late final OperatingSystemUtils os = _os ?? FakeOperatingSystemUtils();

  @override
  late final OutputPreferences outputPreferences = _outputPreferences ?? OutputPreferences.test();

  @override
  late final Platform platform = _platform ?? FakePlatform();

  @override
  late final PreRunValidator preRunValidator = _preRunValidator ?? const NoOpPreRunValidator();

  @override
  late final ProcessManager processManager = _processManager ?? FakeProcessManager.any();

  @override
  late final ProcessUtils processUtils =
      _processUtils ?? ProcessUtils(processManager: processManager, logger: logger);

  @override
  late final FlutterProjectFactory projectFactory =
      _projectFactory ?? FlutterProjectFactory(fileSystem: fs, logger: logger);

  @override
  late final ShutdownHooks shutdownHooks = _shutdownHooks ?? ShutdownHooks();

  @override
  late final Signals signals = _signals ?? Signals.test();

  @override
  late final Stdio stdio = _stdio ?? FakeStdio();

  @override
  late final SystemClock systemClock = _systemClock ?? const SystemClock();

  @override
  late final AnsiTerminal terminal = _terminal ?? AnsiTerminal(stdio: stdio, platform: platform);

  @override
  late final UserMessages userMessages = _userMessages ?? UserMessages();

  @override
  FileSystemUtils get fileSystemUtils => FileSystemUtils(fileSystem: fs, platform: platform);
References
  1. Suggest simplification and refactoring: Assess whether the code can be made simpler or refactored to enhance readability and maintainability. (link)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Addressed in commit 341b711.

Comment on lines +1511 to +1534
@override
CocoaPods get cocoaPods => _cocoaPods ?? FakeCocoaPods();

@override
CocoaPodsValidator get cocoapodsValidator => _cocoapodsValidator ?? FakeCocoaPodsValidator();

@override
IOSSimulatorUtils get iosSimulatorUtils => _iosSimulatorUtils ?? FakeIOSSimulatorUtils();

@override
IOSWorkflow get iosWorkflow => _iosWorkflow ?? FakeIOSWorkflow();

@override
PlistParser get plistParser => _plistParser ?? FakePlistParser();

@override
XCDevice get xcdevice => _xcdevice ?? FakeXCDevice();

@override
Xcode get xcode => _xcode ?? FakeXcode();

@override
XcodeProjectInterpreter get xcodeProjectInterpreter => FakeXcodeProjectInterpreter();
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Similar to FakeToolContext, FakeAppleContext should cache its dependencies using late final fields to ensure referential transparency and state persistence across multiple accesses in tests.

  @override
  late final CocoaPods cocoaPods = _cocoaPods ?? FakeCocoaPods();

  @override
  late final CocoaPodsValidator cocoapodsValidator = _cocoapodsValidator ?? FakeCocoaPodsValidator();

  @override
  late final IOSSimulatorUtils iosSimulatorUtils = _iosSimulatorUtils ?? FakeIOSSimulatorUtils();

  @override
  late final IOSWorkflow iosWorkflow = _iosWorkflow ?? FakeIOSWorkflow();

  @override
  late final PlistParser plistParser = _plistParser ?? FakePlistParser();

  @override
  late final XCDevice xcdevice = _xcdevice ?? FakeXCDevice();

  @override
  late final Xcode xcode = _xcode ?? FakeXcode();

  @override
  late final XcodeProjectInterpreter xcodeProjectInterpreter = FakeXcodeProjectInterpreter();
References
  1. Suggest simplification and refactoring: Assess whether the code can be made simpler or refactored to enhance readability and maintainability. (link)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Addressed in commit 341b711.

AnsiTerminal(
stdio: finalStdio,
platform: finalPlatform,
now: DateTime.now(),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using DateTime.now() directly makes the terminal's time-dependent behavior non-hermetic and unmockable in tests. Since finalSystemClock is already resolved above, we should use finalSystemClock.now() instead.

Suggested change
now: DateTime.now(),
now: finalSystemClock.now(),

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Addressed in commit 341b711.

Comment on lines +1479 to +1480
@override
GradleUtils get gradleUtils => _gradleUtils ?? FakeGradleUtils();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Similar to FakeToolContext, FakeAndroidContext should cache its gradleUtils instance using a late final field to avoid returning a new instance on every access.

Suggested change
@override
GradleUtils get gradleUtils => _gradleUtils ?? FakeGradleUtils();
@override
late final GradleUtils gradleUtils = _gradleUtils ?? FakeGradleUtils();

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Addressed in commit 341b711.

chingjun
chingjun previously approved these changes Aug 11, 2026
@bkonyi bkonyi added the autosubmit Merge PR when tree becomes green via auto submit App label Aug 11, 2026
@auto-submit
auto-submit Bot added this pull request to the merge queue Aug 11, 2026
Merged via the queue into flutter:master with commit cad3054 Aug 12, 2026
22 checks passed
@flutter-dashboard flutter-dashboard Bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Aug 12, 2026
auto-submit Bot pushed a commit to flutter/packages that referenced this pull request Aug 13, 2026
…#12453)

Manual roll Flutter from 27b098811f3b to c2437523d308 (179 revisions)

Manual roll requested by [email protected]

flutter/flutter@27b0988...c243752

2026-08-12 [email protected] Enable Gradle CI cache on all test targets that require android_sdk (flutter/flutter#190723)
2026-08-12 [email protected] [analysis] Reland "Added initial implementation of the flutter_analyzer_plugin (#175679)" (flutter/flutter#191022)
2026-08-12 [email protected] Switch testing to gradle bin distribution type instead of all (flutter/flutter#190738)
2026-08-12 [email protected] Convert Mockito instances in Kotlin to Mockk (flutter/flutter#189884)
2026-08-12 [email protected] Report individual test results to LUCI ResultDB (flutter/flutter#190254)
2026-08-12 [email protected] Toggleable reaction duration respects overrides (flutter/flutter#190857)
2026-08-12 [email protected] Roll Skia from e00dbd7448c4 to fee7272f5bc2 (1 revision) (flutter/flutter#191007)
2026-08-12 [email protected] Started caching text shadows by content. (flutter/flutter#190681)
2026-08-12 [email protected] Adds agent skill for spawning led tasks. (flutter/flutter#190937)
2026-08-12 [email protected] Roll Packages from aaaf246 to 94485f1 (8 revisions) (flutter/flutter#191008)
2026-08-12 [email protected] flutter_tools: validate plugin identifiers before generating registrant code (flutter/flutter#190462)
2026-08-12 [email protected] Roll Skia from 112f36148949 to e00dbd7448c4 (3 revisions) (flutter/flutter#190993)
2026-08-12 [email protected] Roll Skia from 7d366c802307 to 112f36148949 (3 revisions) (flutter/flutter#190983)
2026-08-12 [email protected] remove bringup for flavors test (flutter/flutter#190940)
2026-08-12 [email protected] Roll Skia from 1f10a20bdd61 to 7d366c802307 (2 revisions) (flutter/flutter#190980)
2026-08-12 [email protected] Remove `--no-sim-use-hardfp` flag (flutter/flutter#190790)
2026-08-12 [email protected] Roll pub packages (flutter/flutter#190977)
2026-08-12 [email protected] Roll Skia from 339bedab6766 to 1f10a20bdd61 (1 revision) (flutter/flutter#190975)
2026-08-12 [email protected] RawTooltip respects AnimationStyle updates and reverseCurve (flutter/flutter#190889)
2026-08-12 [email protected] ci: Support --target_arch option in prepare_package.dart (flutter/flutter#190960)
2026-08-12 [email protected] Roll Fuchsia Linux SDK from SFq4FVodIOQAS26Lr... to -uHuSGv3wt7QAlDwa... (flutter/flutter#190973)
2026-08-12 [email protected] Removes building of ci/android_debug_x86 as nobody should be consuming it. (flutter/flutter#190951)
2026-08-12 [email protected] Adds error about wimp_heavy not being implemented. (flutter/flutter#189945)
2026-08-11 [email protected] Add clang, cmake, and ninja deps to Linux windowing_test (flutter/flutter#190119)
2026-08-11 [email protected] [AGP 9.1.0 Migration #1] Add Android Gradle Plugin Public API migration documentation (flutter/flutter#190842)
2026-08-11 [email protected] [flutter_tools] Fix deadlock in debug adapters when process exits early (flutter/flutter#190931)
2026-08-11 [email protected] [tool] Define modular dependency injection containers and bootstrapper (flutter/flutter#190724)
2026-08-11 [email protected] [web] Unify MaskFilter and ColorFilter primitives across CanvasKit and Skwasm (flutter/flutter#190314)
2026-08-11 [email protected] Roll pub packages (flutter/flutter#190958)
2026-08-11 [email protected] [Impeller] Move image upload scheduling waits to GPU disable (flutter/flutter#190445)
2026-08-11 [email protected] Started generating the windows platform for macrobenchmarks (flutter/flutter#190932)
2026-08-11 [email protected] [web] Unify ui.Vertices (flutter/flutter#190563)
2026-08-11 [email protected] Fix accessibility_inspector service extensions map mutability (flutter/flutter#190888)
2026-08-11 [email protected] Add batch3 a11y_assessment for vpat (flutter/flutter#189042)
2026-08-11 [email protected] Remove Xcode environment when building swift tools in Xcode pre-action (flutter/flutter#190848)
2026-08-11 [email protected] [tool] Add missing play element in web test index.html to fix warning (flutter/flutter#190675)
2026-08-11 [email protected] Offload blocking work in ProcessTextPlugin to the background (flutter/flutter#189823)
2026-08-11 [email protected] [flutter_tools] Replace usages of package:dds/dap.dart with package:dap_adapters/dap_adapters.dart (flutter/flutter#190667)
2026-08-11 [email protected] Always update swift package dependencies (flutter/flutter#190886)
2026-08-11 [email protected] [flutter_tools] Add --preset option to flutter test (flutter/flutter#190878)
2026-08-11 [email protected] Include the examples cross imports checker in the analzyer. (flutter/flutter#190674)
2026-08-11 [email protected] [devicelab] Remove orphaned screenshot test files (flutter/flutter#190879)
2026-08-11 [email protected] Remove the bringup flag from the linux_arm_host_desktop_engine builder (flutter/flutter#190935)
2026-08-11 [email protected] Reduce web_skwasm_tests subshards from 8 to 2 (flutter/flutter#190728)
...
pull Bot pushed a commit to TheRakeshPurohit/flutter that referenced this pull request Aug 19, 2026
…ired non-nullable contexts (flutter#190922)

## Summary

Part 2 of the modular dependency injection migration (stacked on
flutter#190724).

Wires explicit dependency injection into the Flutter tool entrypoints:
- Wires `ToolDependencies.bootstrap` into `runner.dart` to initialize
dependencies at startup.
- Updates `FlutterCommandRunner` to accept non-nullable `toolContext`,
`androidContext`, `appleContext`, and `toolDependencies`.
- Updates `executable.dart` to forward `ToolDependencies` into
`generateCommands`.
- Updates `test_flutter_command_runner.dart` and hermetic test doubles.

Part of flutter#47161
pull Bot pushed a commit to TheRakeshPurohit/flutter that referenced this pull request Sep 29, 2026
… discovery (flutter#191972)

## Description

This PR optimizes `AndroidSdk` initialization by deferring expensive
synchronous filesystem operations out of the constructor.

### Why this is needed now
As part of the modular dependency injection migration
([flutter#190724](flutter#190724)), context
objects like `AndroidContext` are created upfront during
`ToolDependencies.bootstrap` / runner initialization rather than via the
old global ambient context (`AppContext`).

Previously, `AndroidSdk` was initialized lazily only when a command or
service queried `globals.androidSdk` or `context.get<AndroidSdk>()`.
With upfront `AndroidContext` creation, `AndroidSdk.locateAndroidSdk()`
runs on tool startup for every command. Because the `AndroidSdk`
constructor eagerly executed `reinitialize()`, it performed synchronous
directory listings across `build-tools` and `platforms` directories and
read `build.prop` files on disk. This introduced unnecessary disk I/O
and latency to tool startup, including for commands completely unrelated
to Android (such as `flutter config` or iOS-specific workflows).

### Changes
- Defers synchronous scanning of build-tools and platform directories
(`reinitialize()`) out of the `AndroidSdk` constructor so that object
instantiation is fast and side-effect free.
- `sdkVersions` and `latestVersion` are evaluated lazily on first
access.
- Retains `_fileSystem` on the instance so that explicit calls to
`reinitialize()` continue to refresh platform and version state with the
expected filesystem.

## Related Issues
Part of [flutter#47161](flutter#47161)
(flutter_tools modular dependency injection migration).

## Tests
- Updated existing test in
`packages/flutter_tools/test/general.shard/android/android_sdk_test.dart`
(`constructing an AndroidSdk handles no matching lines in build.prop`)
to access `sdk.latestVersion` so that `build.prop` parsing
error-handling is executed during lazy initialization.
- Added unit tests verifying:
- Constructor instantiation does not initialize `sdkVersions` or
`latestVersion` or scan platform directories.
- `sdkVersions` and `latestVersion` evaluate lazily on first access
across independent instances.
- Calling `reinitialize()` updates `sdkVersions` and `latestVersion` and
correctly falls back to stored `_fileSystem`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CICD Run CI/CD team-android Owned by Android platform team tool Affects the "flutter" command-line tool. See also t: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants