Repository navigation
[flutter_tools] Lazily initialize AndroidStudio validation and optimize candidate selection - #191968
Conversation
…ze candidate selection Defers synchronous Java validation (`_initAndValidate()`) out of the `AndroidStudio` constructor so that object instantiation is fast and side-effect free. Access to `isValid`, `javaPath`, and `validationMessages` now evaluates validation lazily on first read. Additionally, `AndroidStudio.latestValid()` now sorts discovered candidates in descending order of version before testing validity, allowing candidate discovery to short-circuit on the newest valid installation without executing Java version checks against older, unneeded candidate installations.
…tudio_constructor_initialization
There was a problem hiding this comment.
Code Review
This pull request introduces lazy validation for AndroidStudio instances, deferring initialization until properties like javaPath, isValid, or validationMessages are accessed. This allows the latestValid method to short-circuit validation once a valid installation is found. The tests are updated to verify this lazy behavior and short-circuiting logic. The review feedback suggests copying the studios list before sorting it in latestValid to prevent potential runtime errors or side effects from in-place sorting.
I am having trouble creating individual review comments. Click here to see my feedback.
packages/flutter_tools/lib/src/android/android_studio.dart (269-275)
Sorting the studios list in-place can lead to unexpected side effects if the list returned by allInstalled() is cached, shared, or unmodifiable (which would cause a runtime UnsupportedError).
To prevent this, copy the list before sorting it.
final List<AndroidStudio> sortedStudios = studios.toList()..sort(_compareCandidates);
for (final studio in sortedStudios) {
if (studio.isValid) {
return studio;
}
}|
With this PR, commands that do not need
|
…ntext Accepts lazy factory closures in AndroidContext so that AndroidStudio and Java discovery are not executed on CLI startup. Uses late final variables in ToolDependencies and AndroidContext to memoize discovery on first access and share instances without duplicate disk scans.
Updates Java.find to take an AndroidStudio factory closure instead of an instance so that AndroidStudio discovery is not eagerly triggered when resolving Java.
Renames androidStudio and java initializers to androidStudioBuilder and javaBuilder to clearly distinguish factory closures from resolved properties.
…es in ToolDependencies Defines androidStudioBuilder and javaBuilder directly as closure functions in ToolDependencies.init rather than local late final variables.
Accepts androidSdkBuilder in AndroidContext so that AndroidSdk.locateAndroidSdk is not eagerly executed on CLI startup. Evaluates and memoizes androidSdk on first access.
Accepts gradleUtilsBuilder in AndroidContext so that all AndroidContext properties are lazily initialized on demand via builder closures.
…dio_constructor_initialization
|
Good catch! The eager execution was originating from
|
…io in ToolDependencies
Is it just the EmulatorManager - or does it happen when we just pass the params to bootstrap? |
…structor_initialization
…dio_constructor_initialization
…_comments # Conflicts: # packages/flutter_tools/lib/src/context/tool_dependencies.dart
…and command registration
…meters where G3Fix is unnecessary
Sorry, not sure how I missed this reply. Yes, this also happens when we pass the params to |
…_comments # Conflicts: # packages/flutter_tools/test/general.shard/context/dependency_injection_test.dart
Roll Flutter from 4249ccbc18a3 to 426be968b5eb (44 revisions) flutter/flutter@4249ccb...426be96 2026-10-14 [email protected] Allow color delta tolerance in in-memory fuzzy image comparator (flutter/flutter#193937) 2026-10-14 [email protected] [web] Drop stale composing range after text is replaced mid-composition (flutter/flutter#194096) 2026-10-14 [email protected] Roll Skia from 2a2bbea66844 to acde3755d2e2 (1 revision) (flutter/flutter#194376) 2026-10-14 [email protected] Roll Skia from 128c983b12de to 2a2bbea66844 (1 revision) (flutter/flutter#194371) 2026-10-14 [email protected] Roll Skia from 48a294ed4955 to 128c983b12de (3 revisions) (flutter/flutter#194353) 2026-10-14 [email protected] feat(flutter_tools): extract WebDebugConfig from DebuggingOptions (flutter/flutter#194312) 2026-10-14 [email protected] Roll Skia from ea6781c5b6d1 to 48a294ed4955 (2 revisions) (flutter/flutter#194349) 2026-10-14 [email protected] Include <vector> in Android proc_table.h. (flutter/flutter#194325) 2026-10-14 [email protected] Roll Dart SDK from 5933cb486b4a to 6b50116630d0 (28 revisions) (flutter/flutter#194341) 2026-10-14 [email protected] Move GLES3 instancing API entry points to FOR_EACH_IMPELLER_GLES3_PROC (flutter/flutter#194319) 2026-10-14 [email protected] feat(flutter_tools): extract AndroidGradleConfig and move toGradleConfig to AndroidBuildInfo (flutter/flutter#194016) 2026-10-14 [email protected] Roll Skia from ae297bec6012 to ea6781c5b6d1 (8 revisions) (flutter/flutter#194331) 2026-10-14 [email protected] [flutter_tools] Migrate gradle_errors and deferred_components_prebuild_validator away from globals (flutter/flutter#193649) 2026-10-14 [email protected] [Impeller] Cache GLES sampler configuration in RenderPassStateCache (flutter/flutter#193922) 2026-10-14 [email protected] Roll pub packages (flutter/flutter#194332) 2026-10-13 [email protected] [tool] Migrate RunCommand to modular dependency injection (flutter/flutter#190791) 2026-10-13 [email protected] Include `engine/src/flutter/.ci.yaml` in quarterly `goldctl` checklist (flutter/flutter#194316) 2026-10-13 [email protected] [flutter_tools] Remove the last AMD module system support (flutter/flutter#193607) 2026-10-13 [email protected] Roll Skia from 8b3394a60654 to ae297bec6012 (5 revisions) (flutter/flutter#194310) 2026-10-13 [email protected] [web] Remove the a11y placeholder only after the gesture ends (flutter/flutter#194109) 2026-10-13 [email protected] Upgrade goldctl to the latest version (flutter/flutter#193932) 2026-10-13 [email protected] [flutter_tools] Lazily initialize AndroidStudio validation and optimize candidate selection (flutter/flutter#191968) 2026-10-13 [email protected] [flutter_tools] Clean up outdated widget preview TODOs and refactor static state (flutter/flutter#189437) 2026-10-13 [email protected] Roll Skia from e5b5db6d0d6f to 8b3394a60654 (2 revisions) (flutter/flutter#194306) 2026-10-13 [email protected] Redistribute TESTOWNERS for Android team (flutter/flutter#193861) 2026-10-13 [email protected] feat(flutter_tools): introduce Test and Drive specification objects (flutter/flutter#194017) 2026-10-13 [email protected] [Impeller] Keep the aspect ratio when shrinking images to the max texture size (flutter/flutter#194010) 2026-10-13 [email protected] [flutter tool] preserve inherited `DASH__SUPPRESS_ANALYTICS=true` for sub-tools (flutter/flutter#194261) 2026-10-13 [email protected] Roll Packages from d8d9e97 to a1ed23c (9 revisions) (flutter/flutter#194302) 2026-10-13 [email protected] Use PE/COFF AOT snapshots for Windows x64 (flutter/flutter#187594) 2026-10-13 [email protected] Fix Expansible crash when PageStorage holds a non-bool value (flutter/flutter#192195) 2026-10-13 [email protected] Roll Skia from ffeccb3ca58a to e5b5db6d0d6f (1 revision) (flutter/flutter#194297) 2026-10-13 [email protected] Explain why initState() is too early to depend on an inherited widget (flutter/flutter#192727) 2026-10-13 [email protected] Refactor TestPlatformDispatcher (flutter/flutter#192747) 2026-10-13 [email protected] Roll Skia from 3de0984208ec to ffeccb3ca58a (2 revisions) (flutter/flutter#194291) 2026-10-13 [email protected] Roll Skia from 0580c2b780f6 to 3de0984208ec (2 revisions) (flutter/flutter#194281) 2026-10-13 [email protected] Roll Skia from f641d55e07a8 to 0580c2b780f6 (3 revisions) (flutter/flutter#194275) 2026-10-13 [email protected] Roll Skia from 0e5d8d1be81b to f641d55e07a8 (1 revision) (flutter/flutter#194266) 2026-10-13 [email protected] Scaffold view metrics overrides (flutter/flutter#192764) 2026-10-13 [email protected] [web] Add SemanticsRole.listBox and SemanticsRole.option (flutter/flutter#192971) 2026-10-13 [email protected] Pin mac_unopt to macOS 15 (flutter/flutter#194254) 2026-10-13 [email protected] Roll pub packages (flutter/flutter#194257) 2026-10-12 [email protected] Roll Skia from 17f6986c226c to 0e5d8d1be81b (4 revisions) (flutter/flutter#194249) 2026-10-12 [email protected] Make DisplayLists' use of DlPaint, SkPaint, and impeller::Paint agree on the default isAntiAlias (flutter/flutter#193939) If this roll has caused a breakage, revert this CL and set the roller ...
…ze candidate selection (flutter#191968) ## Description This PR optimizes Android toolchain initialization by deferring expensive synchronous operations, candidate discovery, and SDK location scans out of tool startup. ### 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, `AndroidStudio` and `AndroidSdk` were initialized lazily only when a command or service queried `globals.androidStudio` or `context.get<AndroidStudio>()`. With upfront `AndroidContext` creation, `AndroidStudio.latestValid()` and `AndroidSdk.locateAndroidSdk()` ran on tool startup for every command. Because the constructors and locators performed synchronous filesystem scans and executed `java -version` (a JVM cold start), this introduced unnecessary latency to tool startup, including commands completely unrelated to Android (such as `flutter config` or iOS-specific workflows). ### Changes - Defers synchronous Java validation (`_initAndValidate()`) out of the `AndroidStudio` constructor so that object instantiation is fast and side-effect free. - `isValid`, `javaPath`, and `validationMessages` are evaluated lazily on first access. - In `AndroidStudio.latestValid()`, candidate installations are sorted in descending order of version prior to validation, allowing candidate discovery to short-circuit upon encountering the first valid installation without executing Java checks against older, unneeded candidates. - `AndroidContext` accepts lazy builder closures (`required AndroidSdk? Function() androidSdkBuilder`, `required AndroidStudio? Function() androidStudioBuilder`, `required GradleUtils Function() gradleUtilsBuilder`, and `required Java? Function() javaBuilder`) so commands that do not use Android do not pay any startup initialization cost. - `Java.find` accepts `required AndroidStudio? Function() androidStudioBuilder` builder closure so that creating the `Java` object does not eagerly trigger `AndroidStudio.latestValid()`. ## Related Issues Part of [flutter#47161](flutter#47161) (flutter_tools modular dependency injection migration). ## Tests - Updated `packages/flutter_tools/test/general.shard/android/android_studio_test.dart` to remove obsolete `FakeCommand` expectations for unneeded `java -version` executions. - Added unit tests verifying: - Constructor instantiation does not execute validation or touch the process manager. - `isValid`, `javaPath`, and `validationMessages` evaluate lazily on first access across independent instances. - `latestValid()` short-circuits candidate validation once a valid installation is found. - `latestValid()` falls back to older candidate installations when a newer installation is invalid. - Updated `packages/flutter_tools/test/general.shard/context/dependency_injection_test.dart` to verify: - `AndroidContext` lazily evaluates and memoizes `androidSdk`, `androidStudio`, `gradleUtils`, and `java`. - `AndroidContext` memoizes `null` results without re-evaluating builder closures. - `ToolDependencies.bootstrap` shares the same `AndroidStudio` instance across `AndroidContext` and `Java`. - `ToolDependencies.bootstrap` lazily evaluates `androidSdk` on first access rather than on startup. - Updated `packages/flutter_tools/test/general.shard/android/java_test.dart` to verify: - `Java.find` does not invoke the `androidStudioBuilder` factory if `jdk-dir` is configured.
Description
This PR optimizes Android toolchain initialization by deferring expensive synchronous operations, candidate discovery, and SDK location scans out of tool startup.
Why this is needed now
As part of the modular dependency injection migration (#190724), context objects like
AndroidContextare created upfront duringToolDependencies.bootstrap/ runner initialization rather than via the old global ambient context (AppContext).Previously,
AndroidStudioandAndroidSdkwere initialized lazily only when a command or service queriedglobals.androidStudioorcontext.get<AndroidStudio>(). With upfrontAndroidContextcreation,AndroidStudio.latestValid()andAndroidSdk.locateAndroidSdk()ran on tool startup for every command. Because the constructors and locators performed synchronous filesystem scans and executedjava -version(a JVM cold start), this introduced unnecessary latency to tool startup, including commands completely unrelated to Android (such asflutter configor iOS-specific workflows).Changes
_initAndValidate()) out of theAndroidStudioconstructor so that object instantiation is fast and side-effect free.isValid,javaPath, andvalidationMessagesare evaluated lazily on first access.AndroidStudio.latestValid(), candidate installations are sorted in descending order of version prior to validation, allowing candidate discovery to short-circuit upon encountering the first valid installation without executing Java checks against older, unneeded candidates.AndroidContextaccepts lazy builder closures (required AndroidSdk? Function() androidSdkBuilder,required AndroidStudio? Function() androidStudioBuilder,required GradleUtils Function() gradleUtilsBuilder, andrequired Java? Function() javaBuilder) so commands that do not use Android do not pay any startup initialization cost.Java.findacceptsrequired AndroidStudio? Function() androidStudioBuilderbuilder closure so that creating theJavaobject does not eagerly triggerAndroidStudio.latestValid().Related Issues
Part of #47161 (flutter_tools modular dependency injection migration).
Tests
packages/flutter_tools/test/general.shard/android/android_studio_test.dartto remove obsoleteFakeCommandexpectations for unneededjava -versionexecutions.isValid,javaPath, andvalidationMessagesevaluate lazily on first access across independent instances.latestValid()short-circuits candidate validation once a valid installation is found.latestValid()falls back to older candidate installations when a newer installation is invalid.packages/flutter_tools/test/general.shard/context/dependency_injection_test.dartto verify:AndroidContextlazily evaluates and memoizesandroidSdk,androidStudio,gradleUtils, andjava.AndroidContextmemoizesnullresults without re-evaluating builder closures.ToolDependencies.bootstrapshares the sameAndroidStudioinstance acrossAndroidContextandJava.ToolDependencies.bootstraplazily evaluatesandroidSdkon first access rather than on startup.packages/flutter_tools/test/general.shard/android/java_test.dartto verify:Java.finddoes not invoke theandroidStudioBuilderfactory ifjdk-diris configured.