Packages & pubspec.yaml

Almost no real Flutter app is built entirely from scratch — you'll routinely pull in third-party packages for things like networking, local storage, and state management. Everything about how your project is configured and what it depends on is controlled by a single file at the project root: pubspec.yaml. This lesson covers it thoroughly, since you'll be editing it constantly throughout real Flutter development.

pubspec.yaml: The Project's Manifest

Every Flutter project generated by flutter create comes with a pubspec.yaml at its root. It's written in YAML and describes the project's identity plus everything it depends on.

name: my_app
description: "A new Flutter project."
publish_to: 'none'
version: 1.0.0+1

environment:
  sdk: '>=3.0.0 <4.0.0'

dependencies:
  flutter:
    sdk: flutter
  cupertino_icons: ^1.0.6

dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^3.0.0

flutter:
  uses-material-design: true
KeyPurpose
nameThe package/project name, used internally for imports
descriptionA short human-readable summary of the project
versionYour app's version, in the form major.minor.patch+buildNumber
dependenciesPackages your app needs at runtime, shipped in the final build
dev_dependenciesPackages only needed during development, never shipped in the release build
flutterFlutter-specific configuration, including declared assets and fonts

Adding a Package from pub.dev

pub.dev is the official package repository for Dart and Flutter — the equivalent of npm for JavaScript or PyPI for Python. Adding a package to your project is a two-step process: add a line under dependencies:, then tell Flutter to actually download it.

// Step 1: add the package and version constraint under dependencies:
dependencies:
  flutter:
    sdk: flutter
  http: ^1.2.0
// Step 2: fetch it
flutter pub get

flutter pub get reads pubspec.yaml, resolves compatible versions of every dependency (and their own dependencies), downloads them, and generates/updates pubspec.lock (more on that below). Once that finishes, you can import and use the package in your Dart code:

import 'package:http/http.dart' as http;

void fetchData() async {
  final response = await http.get(Uri.parse('https://example.com/data'));
  print(response.body);
}
Note: http is one of the most commonly used Flutter packages, for making network requests to REST APIs. It gets its own dedicated deep dive later in this track, in the HTTP Requests & APIs lesson — this is just a preview of the "add and use a package" workflow.

Version Constraints: The Caret Syntax

That ^1.2.3 next to a package name is a version constraint written using Dart's caret syntax, based on semantic versioning (semver: major.minor.patch).

http: ^1.2.3
// Means: "any version from 1.2.3 up to, but NOT including, 2.0.0"
// Equivalent to: >=1.2.3 <2.0.0

The idea behind semantic versioning is that a major version bump (the first number) signals breaking changes, while minor and patch bumps are meant to be backward compatible. The caret constraint takes advantage of that convention: it lets flutter pub get automatically pick up compatible bug fixes and new features (1.3.0, 1.4.2, and so on) without ever silently jumping to a version that might break your code (2.0.0).

dependencies vs dev_dependencies

It matters which section you add a package to. Packages under dependencies: are bundled into your actual release build and available at runtime on a user's device. Packages under dev_dependencies: are only available while you're developing — things like testing frameworks and linters — and are stripped out entirely, never shipped to end users.

dependencies:
  flutter:
    sdk: flutter
  http: ^1.2.0              // used by app code at runtime -> ships in the build

dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^3.0.0      // only used during development -> never ships
Rule of thumb: If your app's actual runtime code needs to import it to function, it belongs under dependencies:. If it's a tool you use while writing/testing the app, it belongs under dev_dependencies:.

Declaring Assets in pubspec.yaml

Images, fonts, and other static files also have to be declared in pubspec.yaml, under the flutter: section, before Flutter will bundle and let you use them.

flutter:
  uses-material-design: true
  assets:
    - assets/images/logo.png
    - assets/images/
  fonts:
    - family: Poppins
      fonts:
        - asset: assets/fonts/Poppins-Regular.ttf
        - asset: assets/fonts/Poppins-Bold.ttf
          weight: 700

Once declared, an image can be loaded in your widgets with Image.asset('assets/images/logo.png'). The full details of organizing an assets/ folder, handling different image resolutions, and using custom fonts are covered in the upcoming Images & Assets lesson.

pubspec.lock: Reproducible Builds

Every time you run flutter pub get, Flutter writes (or updates) a pubspec.lock file alongside pubspec.yaml. While pubspec.yaml expresses a range of acceptable versions (like ^1.2.0), pubspec.lock records the exact version that was actually resolved and downloaded for every single dependency, direct and transitive.

Note: Unlike some ignored build artifacts, pubspec.lock should be committed to version control for an application (packages/libraries handle this differently). Committing it guarantees that everyone on your team — and your CI pipeline — installs the exact same dependency versions, avoiding the classic "it works on my machine" problem caused by two developers silently resolving different compatible versions.

Updating Packages: flutter pub upgrade

To update your dependencies to the newest versions still allowed by your version constraints in pubspec.yaml (without editing that file by hand), use:

flutter pub upgrade

This re-resolves everything within your existing caret constraints and rewrites pubspec.lock accordingly. If you deliberately want to move past a major version boundary (say, from ^1.x to 2.x), you'll need to edit the constraint in pubspec.yaml yourself first, since pub upgrade intentionally won't cross that boundary on its own.

Next steps: With your project's dependencies under control, move on to Widgets: Stateless vs Stateful to start building real UI.