Dart package and library management

When the project scale grows, splitting code into multiple files and modules is essential.

This chapter introduces Dart's package management system pub, the dependency configuration file pubspec.yaml, import/export syntax, and how to create custom libraries.


pubspec.yaml Configuration

pubspec.yaml is the core configuration file for every Dart project, declaring the project's metadata and dependencies.

A typical pubspec.yaml file structure is as follows:

Example

# File path: pubspec.yaml
name
: my_dart_app              # Project name (required, lowercase + underscore)
description
: A Dart example project# Project description
version
: 1.0.0                 # Version number
# publish_to: none # If you don't want to publish to pub.dev, uncomment this line

environment
:
  sdk
: '>=3.0.0 <4.0.0'      # Dart SDK version range

dependencies
:
 # Third-party packages that the project depends on at runtime
  http
: ^1.1.0                # HTTP client
  path
: ^1.8.0                # Path utilities

dev_dependencies
:
 # Dependencies needed only during development (testing, linting, etc.)
  test
: ^1.24.0               # Testing framework
  lints
: ^2.0.0               # Official lint rules

# Optional: executable entry point
# executables:
#   my_app: main

Explanation of version number syntax:

SyntaxMeaningExample
^1.1.0Compatible with 1.1.0 to 2.0.0 (exclusive)Most commonly used, recommended
1.1.0Exact versionNot very flexible
>=1.1.0 <1.5.0Version rangeUse when precise control is needed
anyAny versionNot recommended

The caret symbol (^) is Dart's default version constraint method. ^1.1.0 is equivalent to >=1.1.0 <2.0.0. This means minor and patch versions can be upgraded automatically, but major versions cannot be upgraded (major version upgrades may include breaking changes).

Install dependencies

$ dart pub get           # 安装依赖
$ dart pub upgrade       # 升级依赖到最新兼容版本
$ dart pub outdated      # 查看哪些依赖有更新

Find dependencies on pub.dev

pub.dev is the official package repository for Dart and Flutter, similar to npm (JavaScript) or PyPI (Python).

You canhttps://pub.devSearch and browse tens of thousands of Dart packages.

Examples of common third-party packages:

Package namePurposeDescription
httpHTTP requestOfficially maintained HTTP client
pathPath operationsCross-platform path processing tool
testUnit TestingOfficial testing framework
json_serializableJSON serializationAutomatically generate JSON conversion code
dioHTTP clientA more powerful third-party HTTP library than http.
riverpodState managementPopular state management solution for Flutter projects

Example

Add the http package and use it:

// First, add to dependencies in pubspec.yaml:
//   http: ^1.1.0
// Then run: dart pub get

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

void main() async {
  // Send a GET request
  var url = Uri.parse('https://www.example.com');
  var response = await http.get(url);

  print('Status code: ${response.statusCode}');
  print('Response body length: ${response.body.length} characters');
}

import syntax

import is used to bring in code from other libraries into a Dart file.

Dart supports multiple import styles, each suited to different scenarios.

Import Dart built-in libraries

Example

// The dart: prefix indicates Dart's built-in core libraries
import 'dart:math';    // Math library (random numbers, trigonometric functions, etc.)
import 'dart:convert'; // Encoding conversion library (JSON, Base64, etc.)
import 'dart:io';      // I/O library (files, network, etc.)

void main() {
  // Using functions from dart:math
  print('Pi: $pi');
  print('2 to the 10th power: ${pow(2, 10)}');

  // Using functions from dart:convert
  var jsonStr = '{"name": "example", "age": 10}';
  var decoded = jsonDecode(jsonStr);
  print('Parsed JSON: $decoded');
  print('Username: ${decoded['name']}');
}
圆周率: 3.141592653589793
2 的 10 次方: 1024
解析后的 JSON: {name: example, age: 10}
用户名: example

Import third-party packages

Example

// The package: prefix indicates third-party packages on pub.dev
import 'package:http/http.dart';
import 'package:path/path.dart' as p;

Import local files

Example

// Relative path import: import other Dart files in the same project
import 'src/utils.dart';          // Subdirectory under the same directory
import '../models/user.dart';    // File in the parent directory
import 'constants.dart';         // File in the same directory

Modifiers of import

ModifiersSyntaxPurpose
Prefix (as)import 'lib.dart' as myLib;Give the library an alias to avoid naming conflicts
Import only part (show)import 'lib.dart' show foo, bar;Import only the specified names
Exclude part (hide)import 'lib.dart' hide foo;Import everything except the specified names
Lazy loading (deferred as)import 'lib.dart' deferred as lib;Load on demand to reduce startup time

Example

Practical usage of various import modifiers:

import 'dart:math';              // Standard import
import 'dart:math' as math;      // Prefix import: use math.pow() instead of pow()
import 'dart:math' show pi, sqrt;  // Import only pi and sqrt
import 'dart:math' hide Random;    // Import everything except Random

void main() {
  // Standard import
  print('sin(0) = ${sin(0)}');

  // Prefix import: needs to be accessed via prefix
  print('cos(0) = ${math.cos(0)}');

  // show import: only pi and sqrt can be used
  print('π = $pi');
  print('sqrt(16) = ${sqrt(16)}');
  // print(sin(0)); // Error: sin is not imported

  // hide import: Random is unavailable, everything else is available
  print('max(3, 7) = ${max(3, 7)}');
  // Random(); // Error: Random is excluded
}
sin(0) = 0.0
cos(0) = 1.0
π = 3.141592653589793
sqrt(16) = 4.0
max(3, 7) = 7

show and hide are not just for convenience—they are also tools for code quality. Using show makes dependencies clearer: you can see at a glance which symbols from the library this file uses.


export syntax

export is used to re-expose the public APIs of other libraries, mainly for creating aggregate libraries.

Suppose you have the following file structure:

lib/
├── my_package.dart          # 入口文件(聚合导出)
├── src/
│   ├── models/
│   │   └── user.dart        # User 类
│   ├── services/
│   │   └── api_service.dart # API 服务
│   └── utils/
│       └── helpers.dart     # 工具函数

Example

Using export to create an aggregate library entry point:

// File: lib/my_package.dart
// Aggregate export: uniformly exposes all public APIs

export 'src/models/user.dart';
export 'src/services/api_service.dart';
// Only export part of the contents in helpers
export 'src/utils/helpers.dart' show formatDate, validateEmail;

This way, users only need to import one file:

Example

// Users only need to import the entry file
import 'package:my_package/my_package.dart';

void main() {
  // Can directly use all exported classes
  var user = User('example');
  var api = ApiService();
}

Creation of custom libraries

Creating a custom library requires no special syntax—every Dart file is a library.

However, you can use the library keyword to explicitly declare a library name, and use part/part of to split large libraries.

Using library to declare a library name

Example

// File: lib/calculator.dart
// library declares the library name (optional, but helpful for documentation)
library calculator;

/// Addition operation
int add(int a, int b) => a + b;

/// Subtraction operation
int subtract(int a, int b) => a - b;

/// Multiplication operation
int multiply(int a, int b) => a * b;

/// Division operation, throws an exception when the divisor is 0
double divide(int a, int b) {
  if (b == 0) {
    throw ArgumentError(Divisor cannot be 0);
  }
  return a / b;
}

Use part to split large libraries

When a library's code is too long, you can use part to split it into multiple physical files, but they still logically belong to the same library.

Example

Main file declares part:

// File: lib/user_system.dart
// Main library file
library user_system;

// Declare the other files that make up this library
part 'src/user_model.dart';
part 'src/user_service.dart';
part 'src/user_validator.dart';

// Library-level public API
String libraryVersion = '1.0.0';

Example

part file declares part of:

// File: lib/src/user_model.dart
// part of declares which library this file belongs to
part of '../user_system.dart';

// Can access libraryVersion in the main library
class User {
  String name;
  User(this.name);

  void printVersion() {
    print('EXAMPLE User module version: $libraryVersion');
  }
}

part/part of is not commonly used in modern Dart development; most teams prefer import/export to organize code. The downside of part is that part files share all private members, breaking encapsulation. Unless there is a clear need, import/export is recommended.


Dart core library overview

Library nameImport methodMain Features
dart:coreAutomatic importBasic types, collections, exceptions, etc.
dart:mathimport 'dart:math';Mathematical constants and functions
dart:convertimport 'dart:convert';JSON, UTF-8, Base64 encoding/decoding
dart:ioimport 'dart:io';File, network, and process operations
dart:asyncimport 'dart:async';Asynchronous tools such as Future, Stream, etc.
dart:collectionimport 'dart:collection';More collection types (Queue, etc.)
dart:developerimport 'dart:developer';Debugging and performance analysis tools

dart:core is automatically imported, so you don't need to write import 'dart:core' to use basic types like int, String, List, Map, etc.

other extensions