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 _vars map. 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 projectRoot at a real temp directory; an InMemoryFs alone will not see the writes.
  • The atomic .tmp swap 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 PluginInstaller bound to ctx for the plugin identified by pluginName.

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 assetPath to flutter.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 varName in _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 options and stores the selected option string under varName.
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 varName as 'true' or 'false'.
copyFile({required String sourcePath, required String targetPath}) PluginInstaller
Enqueues a CopyFile copying sourcePath to targetPath.
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 code immediately after the first match of pattern in targetFile.
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 code immediately before the first match of pattern in targetFile.
injectBeforeMagicInit(String code) PluginInstaller
Enqueues an InjectIntoMainDart with placement MainDartPlacement.beforeInit.
injectConfigFactory(String factoryName, {String? package}) PluginInstaller
Enqueues a composite that registers factoryName inside lib/main.dart's configFactories: [...] list.
injectEntitlement({required String platform, required String key, required Object value}) PluginInstaller
Enqueues an InjectEntitlement setting key to value in <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 .env when absent.
injectGradleDependency({required String scope, required String notation}) PluginInstaller
Enqueues an InjectGradleDependency adding notation under scope in <projectRoot>/android/app/build.gradle.kts (or .gradle).
injectGradlePlugin({required String pluginId, String? version}) PluginInstaller
Enqueues an InjectGradlePlugin adding pluginId to the plugins { ... } block of <projectRoot>/android/app/build.gradle.kts (or .gradle when the Kotlin variant is absent).
injectImport({required String targetFile, required String importStatement}) PluginInstaller
Enqueues an InjectImport that appends importStatement to targetFile (after any existing imports).
injectInfoPlistKey({required String key, required Object value, String platform = 'ios'}) PluginInstaller
Enqueues an InjectInfoPlistKey setting key to value inside <projectRoot>/<platform>/Runner/Info.plist.
injectIntoWebHead(String content) PluginInstaller
Enqueues an InjectIntoWebHead inserting content before </head> in <projectRoot>/web/index.html. Skipped silently on consumers without a web/ directory.
injectMainDartImport(String importStatement) PluginInstaller
Enqueues an InjectMainDartImport adding importStatement to <projectRoot>/lib/main.dart.
injectPodfileLine({String platform = 'ios', required String line}) PluginInstaller
Enqueues an InjectPodfileLine appending line to the target 'Runner' block of the platform Podfile.
injectProvider(String providerClassName, {String? package}) PluginInstaller
Enqueues a composite (import + after-pattern injection) that registers providerClassName inside lib/config/app.dart's 'providers': [...] list.
injectRoute(String registerFunctionName) PluginInstaller
Enqueues an InjectRouteRegistration that calls <registerFunctionName>(); inside the boot() 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 sourceData into the JSON file at targetPath.
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 stubName via the context's StubDriver, substitutes replacements, and writes the rendered content to targetPath.
removeDependency(String name) PluginInstaller
Enqueues a RemoveDependency that strips name from either map.
runShell({required String command, List<String> args = const <String>[], String? workingDir}) PluginInstaller
Enqueues a RunShell that executes command with args inside workingDir (defaults to _ctx.projectRoot).
stageForTest(InstallOperation op) → void
Test-only seam: enqueues op directly 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 content verbatim to targetPath.

Operators

operator ==(Object other) bool
The equality operator.
inherited