Packages & Dependencies
Irbisa · cheatsheetSeptember 13, 2026

Packages & Dependencies

Version constraints, pubspec.lock, version solving, overrides, publishing, melos, upgrades

Middle Developer16 itemscompressed for a skim
  1. 01

    Your pubspec says intl: ^0.20.2 and pub refuses to take 0.21.0, yet dio: ^5.7.0 happily moves to 5.9.0 — what is the caret doing?

    Easy

    The caret means "up to the next breaking version", and below 1.0.0 pub moves that boundary down a level: the minor number acts as the major.

    name: checkout_app
    environment:
      sdk: ^3.9.0
    
    dependencies:
      # ^5.7.0  ==  >=5.7.0 <6.0.0  — the usual case.
    …
  2. 02

    Your app keeps pubspec.lock in .gitignore, and CI just built against a different version of a dependency than your laptop did. Who was wrong?

    Easy

    The app was: an application commits its lock file and a published package does not, because only one of the two is ever the root of a version solve.

    # pubspec.lock — generated by pub, never hand-edited.
    packages:
      dio:
        dependency: "direct main"        # you asked for it in `dependencies`
        description:
          name: dio
    …
  3. 03

    You wanted a newer dio, ran flutter pub get, and nothing moved. Which pub command is allowed to change the lock file, and how far?

    Easy

    pub get only answers questions the lock file has not already answered; moving an already-locked package is pub upgrade, and changing the constraint itself is pub upgrade --major-versions.

    # Nothing moved? Ask what the lock file is holding, and what is reachable.
    $ flutter pub outdated
    # Package         Current  Upgradable  Resolvable  Latest
    # dio             5.7.0    5.9.3       5.9.3       5.9.3
    # intl            0.20.2   0.20.2      0.20.2      0.21.0  <- something caps it
    # acme_analytics  1.9.4    1.9.6       2.3.0       2.3.0   <- major bump waiting
    …
  4. 04

    flutter pub get fails with fifteen lines of "because X depends on Y ^2.0.0..." — how do you read that wall down to the one conflict you have to fix?

    Medium

    Read the last sentence first: pub's solver writes a proof, and only the bottom line is about your package — everything above it exists to justify that line.

    $ flutter pub get
    Resolving dependencies...
    Because every version of flutter_localizations from sdk depends on intl 0.20.2
      and acme_analytics >=2.1.0 depends on intl ^0.21.0, flutter_localizations
      from sdk is incompatible with acme_analytics >=2.1.0.
    And because no versions of acme_analytics match >2.0.0 <2.1.0,
    …
  5. 05

    One line of dependency_overrides turned a failing flutter pub get green. What did you just switch off, and what were the honest alternatives?

    Medium

    You told the solver to stop enforcing a constraint that a package author wrote deliberately, and to build a combination nobody has tested.

    # ---- Wrong: the conflict is silenced, the risk is not ----
    dependencies:
      acme_analytics: ^2.1.0      # needs intl ^0.21.0
      flutter_localizations:
        sdk: flutter              # pins intl 0.20.2 exactly
    …
  6. 06

    A reviewer says build_runner sitting in dependencies is bloating your APK. Are they right, and how do you prove what is actually in the binary?

    Medium

    Not directly — Dart's AOT compiler only ships code reachable from main(), so a package you never import adds nothing to the binary; the real cost of the wrong section is what your consumers have to resolve, and, for plugins, native code that ships regardless.

    # Which bucket is each package in? The lock file already says.
    $ grep -B3 'dependency: "direct dev"' pubspec.lock | head -20
    #   dependency: "direct main"   -> yours, and inherited by your consumers
    #   dependency: "direct dev"    -> dev_dependencies, never inherited
    #   dependency: transitive      -> someone else asked for it
    …
  7. 07

    A teammate points a dependency at a GitHub branch to unblock a fix. What does that cost you, and when would you accept it?

    Medium

    A git dependency trades version solving for a single moving pointer: it takes over that package for the entire graph, satisfies nobody else's range, and can never be published.

    dependencies:
      # hosted (the default) — a range the solver can negotiate
      dio: ^5.9.0
    
      # hosted on a private server — still versioned, still solvable
      acme_ui:
    …
  8. 08

    You bump the Flutter SDK and pub get dies with "requires SDK version >=2.12.0 <3.0.0" on a package nobody on the team owns. What happened, and what are your moves?

    Medium

    That package declared an upper bound on the Dart SDK in its own environment: block, pub treats that bound as hard, and every published version of it is now excluded from the solve — so the failure is about the package's metadata, not about your code.

    # ---- The package you do not own, as published two years ago ----
    # name: legacy_charts
    # environment:
    #   sdk: '>=2.12.0 <3.0.0'     # hard upper bound: no candidate on Dart 3.x
    #
    # $ flutter pub get
    …
  9. 09

    Your team is about to publish its first package to pub.dev — what does the dry run actually check, and what can you never take back?

    Medium

    Publishing is permanent: pub.dev never deletes or overwrites a version, so the dry run is the last cheap check you get before the archive is public forever.

    # pubspec.yaml — the fields the dry run looks at
    name: acme_result              # claimed forever on the first publish
    description: A tiny Result type for Dart with pattern-matching helpers and no dependencies.
    version: 0.1.0                 # pre-1.0: a breaking change bumps the minor
    repository: https://github.com/acme/acme_result
    issue_tracker: https://github.com/acme/acme_result/issues
    …
  10. 10

    You want to add a required named parameter to a public constructor in your package — is that a patch, a minor, or a major?

    Medium

    Major — in Dart "breaking" means anything that can stop a consumer's code from compiling, and a new required parameter breaks every existing call site.

    // ── 1.2.0, already published ────────────────────────────────────────
    class Toast {
      Toast({required this.message, this.seconds = 3});
      final String message;
      final int seconds;
    }
    …
  11. 11

    Seven local packages, and every branch switch means running pub get in each directory — what do pub workspaces or Melos actually fix?

    Medium

    A workspace makes the whole repo one resolution: one pubspec.lock, one .dart_tool/package_config.json, one pub get at the root for every package in it.

    # ── Before: path deps, each package resolved on its own ─────────────
    # packages/auth/pubspec.yaml
    #   dependencies:
    #     acme_core: { path: ../core }
    # packages/auth/pubspec_overrides.yaml   <- generated, gitignored, easy to
    #                                           leave behind and hard to notice
    …
  12. 12

    A transitive dependency you never chose ships a broken patch on Friday and your build starts failing — what do you do first?

    Medium

    First establish whether it can even reach you: with pubspec.lock committed and CI running pub get --enforce-lockfile, a bad publish upstream changes nothing until somebody deliberately upgrades.

    #!/usr/bin/env bash
    set -euo pipefail
    
    # 1) Who pulls it in, and what actually moved?
    dart pub deps --style=compact | grep -B3 broken_json
    git diff HEAD~1 -- pubspec.lock | grep -B2 broken_json
    …
  13. 13

    Why does one camera plugin turn into five packages on pub.dev, and which of them does your app actually depend on?

    Hard

    A federated plugin splits the Dart-facing API from each platform's implementation so a platform can be added by somebody who does not own the plugin — and your app depends only on the app-facing package.

    # ── 1. app-facing package: the API plus the endorsements ────────────
    # bar_scanner/pubspec.yaml
    name: bar_scanner
    dependencies:
      flutter: { sdk: flutter }
      bar_scanner_platform_interface: ^2.0.0
    …
  14. 14

    Your team upgrades dependencies twice a year and each round costs two days of firefighting — how do you make upgrades boring?

    Hard

    Turn one big bang into a stream of small reviewable bumps, and be explicit about the few things you pin on purpose and never float.

    # .github/dependabot.yml
    version: 2
    updates:
      - package-ecosystem: pub
        directory: /
        schedule: { interval: weekly, day: monday }
    …
  15. 15

    Two packages in your monorepo need incompatible major versions of the same dependency — what does pub do, and how do you get out?

    Hard

    Pub resolves one version of every package for the whole resolution, so there is no "both" — the solve fails, and every real fix ends with one of the two constraints moving.

    # What the failure actually looks like
    $ flutter pub get
    # Because acme_billing depends on analytics ^3.0.0 and acme_auth depends on
    # analytics ^2.4.0, version solving failed.
    
    # 1) Read the chain to the end, then see who really pulls it in
    …
  16. 16

    A package at the centre of your app has not shipped in two years and now blocks a Dart SDK bump — fork, vendor, or replace?

    Hard

    Decide by how much of the package you actually use and how much native code it carries, then keep the change reviewable by not touching your own import sites in the same PR.

    # ── The decision, written where the next person will find it ────────
    # pubspec.yaml
    dependencies:
    
      # 1) Pin and wait — an expiry date, not an opinion
      legacy_charts: 2.3.1           # blocks the Dart 3.7 bump; revisit 2026-06-01 (#1204)
    …