PluginInstaller class
Fluent builder consumed by every plugin's <plugin>:install command.
PluginInstaller collects an ordered list of InstallOperations through
chain methods (Steps 19-23) and a final commit call dispatches them to
an InstallTransaction for atomic write + record persistence + conflict
pre-flight.
Lifecycle (one-shot)
Each PluginInstaller instance commits exactly once. After commit
returns ANY result (Success / DryRun / Conflict / Error) a second
call throws StateError. The reasoning is symmetric with
InstallTransaction's own one-shot semantics: replaying chain-built ops
against partially mutated disk state silently corrupts the install record.
Plugin authors should construct a fresh PluginInstaller per install pass.
startWith / endWith escape hatches
- startWith fires immediately before the ops dispatch and runs even when the dispatch later returns a non-Success result. Use it for setup that must happen regardless of outcome (e.g. priming a logger).
- endWith fires only after a Success result lands. Use it for post-install side effects that are unsafe to run on a half-applied install (e.g. printing a "next steps" banner).
Method classification, IMMEDIATE vs DEFERRED
Chain methods fall into two distinct execution categories. Plugin authors MUST internalise the split because it determines whether a method's effect is visible inside the chain or only after commit.
IMMEDIATE (run synchronously when called)
- ask, confirm, choice: drive InstallContext.prompt synchronously
and store the answer in an internal
_varsmap. Subsequent chain calls can read the captured value via the vars getter and branch on it (if (installer.vars['enableX'] == 'true') ...). - startWith, endWith: register lifecycle hooks. Neither fires at registration time; both fire later from commit.
DEFERRED (enqueued as an InstallOperation, applied during commit)
- All
add*/inject*/write*/delete*/copy*/publish*/merge*/wrap*methods plus runShell. They append to the internal op list and only touch the filesystem during commit.
askToRunShell is a hybrid: it runs the prompt IMMEDIATELY, but enqueues a RunShell op when the user confirms (so the shell command still executes deferredly).
Limitations
Several dispatcher arms delegate to legacy helper classes
(ConfigEditor, JsonEditor, MainDartEditor, XmlEditor, PlistWriter,
PodfileEditor, GradleEditor, HtmlEditor, EnvEditor) which read and
write through FileHelper/dart:io directly. Those helpers BYPASS the
InstallContext.fs VirtualFs abstraction. Consequences:
- Tests targeting pubspec / inject / native / web / env dispatcher arms
must point
projectRootat a real temp directory; an InMemoryFs alone will not see the writes. - The atomic
.tmpswap performed by InstallTransaction.commit applies only to WriteFile / DeleteFile / CopyFile / PublishFile ops. Helper-backed ops commit synchronously during the stage phase, so a failure later in the dispatch does NOT roll them back. V1 trade-off: plugins should order their ops so the helper-backed mutations follow any high-risk file writes, not precede them.
Usage
final installer = PluginInstaller(ctx, pluginName: 'magic_logger');
final result = await installer
.startWith((ctx) => ctx.artisanContext.output.info('Installing...'))
.addDependency('intl', '^0.20.0')
.publishConfig(stubName: 'install/logger.dart.stub',
targetPath: 'lib/config/logger.dart')
.injectProvider('LoggerServiceProvider')
.ask(varName: 'logPath', question: 'Log path?', defaultValue: '/tmp/log')
.endWith((ctx) => ctx.artisanContext.output.success('Run "flutter pub get"'))
.commit(dryRun: false, force: false);
Constructors
- PluginInstaller(InstallContext ctx, {required String pluginName})
-
Creates a
PluginInstallerbound toctxfor the plugin identified bypluginName.
Properties
- hashCode → int
-
The hash code for this object.
no setterinherited
- pendingCount → int
-
Number of operations currently queued for the next commit.
no setter
-
pendingOps
→ List<
InstallOperation> -
Read-only view of the queued operations in insertion order. Mutating the
returned list throws UnsupportedError.
no setter
- runtimeType → Type
-
A representation of the runtime type of the object.
no setterinherited
-
vars
→ Map<
String, String> -
Read-only view of the captured prompt answers.
no setter
Methods
-
addDependency(
String name, String version) → PluginInstaller -
Enqueues an AddDependency for the runtime
dependencies:map. -
addDevDependency(
String name, String version) → PluginInstaller -
Enqueues an AddDependency flagged for the
dev_dependencies:map. -
addPathDependency(
String name, String path) → PluginInstaller - Enqueues an AddPathDependency for a relative-path dependency.
-
addPubspecAsset(
String assetPath) → PluginInstaller -
Enqueues an AddPubspecAsset appending
assetPathtoflutter.assets. -
addWebMetaTag(
Map< String, String> attributes) → PluginInstaller -
Enqueues an AddWebMetaTag adding a
<meta>element to<projectRoot>/web/index.html. -
ask(
{required String varName, required String question, String? defaultValue, String? validator(String)?}) → PluginInstaller -
IMMEDIATE: prompts the user via InstallContext.prompt and stores the
answer under
varNamein_vars. -
askToRunShell(
{required String prompt, required String command, List< String> args = const <String>[]}) → PluginInstaller - HYBRID: prompts immediately. When the user confirms, enqueues a RunShell that the dispatcher will execute during commit.
-
choice(
{required String varName, required String question, required List< String> options, String? defaultValue}) → PluginInstaller -
IMMEDIATE: prompts the user to pick one of
optionsand stores the selected option string undervarName. -
commit(
{bool dryRun = false, bool force = false}) → Future< TransactionResult> - Dispatches the queued ops to an InstallTransaction and returns its result.
-
confirm(
{required String varName, required String question, bool defaultValue = false}) → PluginInstaller -
IMMEDIATE: prompts the user for a yes/no answer and stores it under
varNameas'true'or'false'. -
copyFile(
{required String sourcePath, required String targetPath}) → PluginInstaller -
Enqueues a CopyFile copying
sourcePathtotargetPath. -
deleteFile(
String targetPath) → PluginInstaller -
Enqueues a DeleteFile removing the file at
targetPath. Idempotent at dispatcher level: deleting an absent file is a silent no-op. -
endWith(
void hook(InstallContext)) → PluginInstaller - Registers a callback invoked after commit returns Success.
-
injectAfter(
{required String targetFile, required Pattern pattern, required String code}) → PluginInstaller -
Enqueues an InjectAfterPattern that inserts
codeimmediately after the first match ofpatternintargetFile. -
injectAfterMagicInit(
String code) → PluginInstaller - Enqueues an InjectIntoMainDart with placement MainDartPlacement.afterInit.
-
injectAndroidMetaData(
{required String name, required String value}) → PluginInstaller -
Enqueues an InjectAndroidMetaData adding a
<meta-data>element inside<application>. Skipped on non-Android consumers. -
injectAndroidPermission(
String permission) → PluginInstaller -
Enqueues an InjectAndroidPermission. Dispatcher silently skips when
the consumer project has no
android/directory. -
injectBefore(
{required String targetFile, required Pattern pattern, required String code}) → PluginInstaller -
Enqueues an InjectBeforePattern that inserts
codeimmediately before the first match ofpatternintargetFile. -
injectBeforeMagicInit(
String code) → PluginInstaller - Enqueues an InjectIntoMainDart with placement MainDartPlacement.beforeInit.
-
injectConfigFactory(
String factoryName, {String? package}) → PluginInstaller -
Enqueues a composite that registers
factoryNameinsidelib/main.dart'sconfigFactories: [...]list. -
injectEntitlement(
{required String platform, required String key, required Object value}) → PluginInstaller -
Enqueues an InjectEntitlement setting
keytovaluein<projectRoot>/<platform>/Runner/Runner.entitlements. -
injectEnvVar(
{required String key, required String value, String? comment}) → PluginInstaller -
Enqueues an InjectEnvVar writing
<key>=<value>to<projectRoot>/.env. Creates.envwhen absent. -
injectGradleDependency(
{required String scope, required String notation}) → PluginInstaller -
Enqueues an InjectGradleDependency adding
notationunderscopein<projectRoot>/android/app/build.gradle.kts(or.gradle). -
injectGradlePlugin(
{required String pluginId, String? version}) → PluginInstaller -
Enqueues an InjectGradlePlugin adding
pluginIdto theplugins { ... }block of<projectRoot>/android/app/build.gradle.kts(or.gradlewhen the Kotlin variant is absent). -
injectImport(
{required String targetFile, required String importStatement}) → PluginInstaller -
Enqueues an InjectImport that appends
importStatementtotargetFile(after any existing imports). -
injectInfoPlistKey(
{required String key, required Object value, String platform = 'ios'}) → PluginInstaller -
Enqueues an InjectInfoPlistKey setting
keytovalueinside<projectRoot>/<platform>/Runner/Info.plist. -
injectIntoWebHead(
String content) → PluginInstaller -
Enqueues an InjectIntoWebHead inserting
contentbefore</head>in<projectRoot>/web/index.html. Skipped silently on consumers without aweb/directory. -
injectMainDartImport(
String importStatement) → PluginInstaller -
Enqueues an InjectMainDartImport adding
importStatementto<projectRoot>/lib/main.dart. -
injectPodfileLine(
{String platform = 'ios', required String line}) → PluginInstaller -
Enqueues an InjectPodfileLine appending
lineto thetarget 'Runner'block of the platform Podfile. -
injectProvider(
String providerClassName, {String? package}) → PluginInstaller -
Enqueues a composite (import + after-pattern injection) that registers
providerClassNameinsidelib/config/app.dart's'providers': [...]list. -
injectRoute(
String registerFunctionName) → PluginInstaller -
Enqueues an InjectRouteRegistration that calls
<registerFunctionName>();inside theboot()method of<projectRoot>/lib/app/providers/route_service_provider.dart. -
mergeJson(
{required String targetPath, required Map< String, dynamic> sourceData, bool additive = true}) → PluginInstaller -
Enqueues a MergeJson that deep-merges
sourceDatainto the JSON file attargetPath. -
noSuchMethod(
Invocation invocation) → dynamic -
Invoked when a nonexistent method or property is accessed.
inherited
-
publishConfig(
{required String stubName, required String targetPath, Map< String, String> replacements = const <String, String>{}}) → PluginInstaller -
Enqueues a PublishFile: the dispatcher loads
stubNamevia the context's StubDriver, substitutesreplacements, and writes the rendered content totargetPath. -
removeDependency(
String name) → PluginInstaller -
Enqueues a RemoveDependency that strips
namefrom either map. -
runShell(
{required String command, List< String> args = const <String>[], String? workingDir}) → PluginInstaller -
Enqueues a RunShell that executes
commandwithargsinsideworkingDir(defaults to_ctx.projectRoot). -
stageForTest(
InstallOperation op) → void -
Test-only seam: enqueues
opdirectly without going through a chain method. -
startWith(
void hook(InstallContext)) → PluginInstaller - Registers a callback invoked immediately before the ops are dispatched inside commit.
-
toString(
) → String -
A string representation of this object.
inherited
-
wrapRunApp(
String wrapperName) → PluginInstaller - Enqueues an InjectIntoMainDart with placement MainDartPlacement.wrapRunApp.
-
writeFile(
{required String targetPath, required String content}) → PluginInstaller -
Enqueues a WriteFile that writes
contentverbatim totargetPath.
Operators
-
operator ==(
Object other) → bool -
The equality operator.
inherited