Skip to content

Пакеты, библиотеки и зависимости в Dart

Пакеты, библиотеки и зависимости в Dart

После освоения базового синтаксиса Dart и концепций объектно-ориентированного программирования пришло время взглянуть на то, как структурировать более крупные проекты и эффективно использовать существующие решения.

Dart предоставляет надежную экосистему управления пакетами и зависимостями, которая помогает разработчикам организовывать код, повторно использовать решения и интегрировать внешние библиотеки. Давайте разберемся в этой системе.

Что такое библиотеки в Dart?

В Dart библиотека — это единица организации кода, которая предоставляет определенный набор функциональности.

Dart библиотеки

Каждый файл Dart является библиотекой (даже если это явно не указано). Библиотеки позволяют разделить код на логические части, обеспечивая инкапсуляцию и возможность повторного использования.

// Пример объявления библиотеки
library my_utilities;
 
// Функции, классы и переменные библиотеки
int add(int a, int b) {
  return a + b;
}
 
class Calculator {
  double multiply(double a, double b) {
    return a * b;
  }
}

Чтобы использовать библиотеку в другом файле, мы используем директиву import:

// Импорт локальной библиотеки
import 'my_utilities.dart';
 
void main() {
  var result = add(5, 3);
  print('5 + 3 = $result');
 
  var calc = Calculator();
  print('4 * 2.5 = ${calc.multiply(4, 2.5)}');
}

Управление видимостью

Dart позволяет контролировать, какие части библиотеки будут доступны другим:

// Файл: my_library.dart
library my_library;
 
// Публичный класс, доступен везде
class PublicClass {
  void publicMethod() {
    print('Публичный метод');
  }
 
  // Приватный метод, доступен только внутри библиотеки
  void _privateMethod() {
    print('Приватный метод');
  }
}
 
// Приватная функция, доступна только внутри библиотеки
void _privateFunction() {
  print('Приватная функция');
}
 
// Публичная функция, доступна везде
void publicFunction() {
  print('Публичная функция');
}

В Dart соглашение о приватности основано на символе подчеркивания (_):

  • Имена, начинающиеся с _, являются приватными и доступны только внутри библиотеки
  • Остальные имена публичны и доступны для импорта другими библиотеками

dart-libraries.webp

Что такое пакеты в Dart?

Пакет — это набор библиотек и других ресурсов (например, изображений, файлов конфигурации), объединенных для распространения и повторного использования.

Пакеты в Dart бывают двух основных типов:

  1. Пакеты приложений — содержат код конкретного приложения и его зависимости
  2. Пакеты библиотек — предназначены для использования другими пакетами

Структура пакета

Базовая структура пакета Dart выглядит так:

my_package/
│
├── .dart_tool/          # Служебная директория для инструментов Dart
├── lib/                 # Код библиотеки, который можно импортировать
│   ├── src/             # Внутренние реализации (обычно приватные)
│   │   └── utils.dart
│   └── my_package.dart  # Основная точка входа в библиотеку
├── test/                # Тесты
├── example/             # Примеры использования
├── pubspec.yaml         # Метаданные пакета и зависимости
└── README.md            # Документация

Файл pubspec.yaml играет особую роль — он описывает пакет, его метаданные и зависимости:

name: my_package
description: A sample package demonstrating Dart packaging.
version: 1.0.0
 
environment:
  sdk: ">=3.1.0 <4.0.0"
 
dependencies:
  http: ^1.1.0
  path: ^1.8.3
 
dev_dependencies:
  test: ^1.24.0
  lints: ^2.1.1

Работа с зависимостями

Зависимости — это внешние пакеты, которые использует ваш проект. Dart использует инструмент pub для управления зависимостями.

Добавление зависимостей

Чтобы добавить зависимость, нужно указать ее в pubspec.yaml:

dependencies:
  http: ^1.1.0 # Пакет для HTTP-запросов

После этого выполните команду для загрузки зависимостей:

dart pub get

или для Flutter проектов:

flutter pub get

Использование зависимостей

После установки зависимость можно импортировать в код:

import 'package:http/http.dart' as http;
 
Future<void> fetchData() async {
  final response = await http.get(Uri.parse('https://api.example.com/data'));
 
  if (response.statusCode == 200) {
    print('Получены данные: ${response.body}');
  } else {
    print('Ошибка: ${response.statusCode}');
  }
}

Типы зависимостей

В pubspec.yaml можно указать различные типы зависимостей:

dependencies:
  # Зависимость из pub.dev с указанием версии
  http: ^1.1.0
 
  # Локальная зависимость (путь к директории)
  my_local_package:
    path: ../my_local_package
 
  # Зависимость из Git-репозитория
  my_git_package:
    git:
      url: https://github.com/user/repo.git
      ref: main # Ветка, тег или коммит

Версионирование пакетов

Dart использует семантическое версионирование (SemVer) для управления версиями пакетов:

MAJOR.MINOR.PATCH
  • MAJOR: увеличивается при несовместимых изменениях API
  • MINOR: увеличивается при добавлении новой функциональности с обратной совместимостью
  • PATCH: увеличивается при обратно совместимых исправлениях ошибок

Ограничения версий

В pubspec.yaml можно указать различные ограничения версий:

dependencies:
  package1: "1.0.0" # Точная версия
  package2: ">=1.0.0 <2.0.0" # Диапазон версий
  package3: "^1.2.3" # Совместимые версии (≥1.2.3 <2.0.0)
  package4: "~1.2.3" # Близкие версии (≥1.2.3 <1.3.0)

Создание собственных библиотек

Давайте создадим простую библиотеку, которую можно будет использовать в других проектах:

1. Создайте структуру каталогов:

string_utils/
├── lib/
│   ├── src/
│   │   └── formatters.dart
│   └── string_utils.dart
└── pubspec.yaml

2. Создайте pubspec.yaml:

name: string_utils
description: Utilities for string manipulation in Dart.
version: 1.0.0
 
environment:
  sdk: ">=3.1.0 <4.0.0"

3. Реализуйте функциональность в lib/src/formatters.dart:

/// Преобразует строку в формат заголовка (первые буквы слов заглавные)
String toTitleCase(String text) {
  if (text.isEmpty) return text;
 
  return text.split(' ').map((word) {
    if (word.isEmpty) return word;
    return word[0].toUpperCase() + word.substring(1).toLowerCase();
  }).join(' ');
}
 
/// Удаляет лишние пробелы из строки
String removeExtraSpaces(String text) {
  return text.replaceAll(RegExp(r'\s+'), ' ').trim();
}

4. Создайте публичный API в lib/string_utils.dart:

/// Библиотека для манипуляции строками
library string_utils;
 
// Экспортируем функции, которые должны быть доступны пользователям
export 'src/formatters.dart';
 
// Дополнительные публичные функции
String reverseString(String text) {
  return String.fromCharCodes(text.runes.toList().reversed);
}

5. Использование библиотеки:

import 'package:string_utils/string_utils.dart';
 
void main() {
  var text = 'hello  WORLD';
 
  print(toTitleCase(text));         // "Hello World"
  print(removeExtraSpaces(text));   // "hello WORLD"
  print(reverseString(text));       // "DLROW  olleh"
}

Dart pub.dev: экосистема пакетов

pub.dev — официальный репозиторий пакетов для Dart и Flutter. Здесь разработчики могут:

  1. Находить существующие пакеты для решения задач
  2. Публиковать свои пакеты для сообщества
  3. Оценивать качество пакетов с помощью системы баллов
  4. Просматривать документацию и примеры использования

Поиск пакетов

dart pub search http

Публикация пакета

dart pub publish

Популярные пакеты

Некоторые популярные пакеты для Dart:

  • http — для выполнения HTTP-запросов
  • path — для работы с путями к файлам и директориям
  • intl — для интернационализации (форматирование дат, чисел и т.д.)
  • json_serializable — для упрощения сериализации JSON
  • provider — для управления состоянием (в Flutter)

Организация кода в крупных проектах

Для эффективной организации крупных проектов рекомендуется:

  1. Разделяйте код на библиотеки по функциональности:

    lib/
    ├── src/
    │   ├── api/
    │   ├── models/
    │   ├── services/
    │   └── utils/
    └── my_app.dart
    
  2. Используйте экспорты для создания чистого API:

    // lib/models.dart
    export 'src/models/user.dart';
    export 'src/models/product.dart';
  3. Скрывайте внутренние реализации в директории src:

    // Публичный API
    import 'package:my_app/models.dart';
     
    // Внутренняя реализация, лучше не использовать напрямую
    import 'package:my_app/src/models/user.dart';
  4. Создавайте внутренние мини-пакеты для повторного использования кода:

    my_project/
    ├── packages/
    │   ├── auth_client/
    │   └── api_models/
    └── apps/
        ├── mobile_app/
        └── web_app/
    

Примеры использования библиотек

Пример 1: HTTP-запросы с пакетом http

import 'package:http/http.dart' as http;
import 'dart:convert';
 
Future<List<User>> fetchUsers() async {
  final response = await http.get(Uri.parse('https://jsonplaceholder.typicode.com/users'));
 
  if (response.statusCode == 200) {
    List<dynamic> data = jsonDecode(response.body);
    return data.map((json) => User.fromJson(json)).toList();
  } else {
    throw Exception('Failed to load users');
  }
}
 
class User {
  final int id;
  final String name;
  final String email;
 
  User({required this.id, required this.name, required this.email});
 
  factory User.fromJson(Map<String, dynamic> json) {
    return User(
      id: json['id'],
      name: json['name'],
      email: json['email'],
    );
  }
}

Пример 2: Форматирование дат с пакетом intl

import 'package:intl/intl.dart';
 
void showFormattedDates() {
  final now = DateTime.now();
 
  // Форматирование даты
  final dateFormatter = DateFormat('dd.MM.yyyy');
  print('Дата: ${dateFormatter.format(now)}');  // например: 10.05.2025
 
  // Форматирование времени
  final timeFormatter = DateFormat('HH:mm:ss');
  print('Время: ${timeFormatter.format(now)}');  // например: 14:30:45
 
  // Локализованные форматы
  final russianFormatter = DateFormat.yMMMMd('ru');
  print('По-русски: ${russianFormatter.format(now)}');  // например: 10 мая 2025 г.
 
  // Форматирование чисел
  final numberFormatter = NumberFormat('#,###.##', 'ru');
  print('Число: ${numberFormatter.format(1234567.89)}');  // 1 234 567,89
}

Лучшие практики

  1. Минимизируйте публичное API

    • Помещайте внутренние реализации в директорию src
    • Экспортируйте только необходимые компоненты
  2. Четко определяйте версии зависимостей

    • Используйте ограничения версий для предотвращения непредвиденных обновлений
    • Регулярно обновляйте зависимости для безопасности
  3. Документируйте ваш код

    • Используйте комментарии в формате /// для документирования
    • Создавайте примеры использования в директории example/
  4. Тестируйте библиотеки

    • Размещайте тесты в директории test/
    • Стремитесь к высокому покрытию кода тестами
  5. Следуйте соглашениям по именованию

    • Имена пакетов: lower_case_with_underscores
    • Имена файлов: lower_case_with_underscores.dart

Заключение

Система пакетов и библиотек Dart предоставляет мощные инструменты для структурирования кода, повторного использования решений и интеграции внешних компонентов. Понимание этой системы значительно повышает продуктивность разработки.

В этой статье мы рассмотрели:

  • Создание и использование библиотек в Dart
  • Работу с пакетами и зависимостями через pubspec.yaml
  • Версионирование и управление зависимостями
  • Создание и структурирование собственных библиотек
  • Организацию кода в крупных проектах
  • Примеры использования популярных пакетов

Эффективное использование пакетов и библиотек — это навык, который развивается с опытом. Начните с малого, изучайте существующие пакеты и постепенно внедряйте лучшие практики в свои проекты.

Дополнительные материалы

  1. Официальная документация по пакетам Dart
  2. Каталог пакетов pub.dev
  3. Руководство по созданию пакетов
  4. Стиль кода Dart

Домашнее задание по пакетам и библиотекам в Dart

Задача 1: Создание и использование локальной библиотеки

Задача: Создать библиотеку для работы с математическими функциями и использовать ее в отдельном проекте.

// Создайте библиотеку с базовыми математическими операциями
// Включите функции для работы с векторами, матрицами, и т.д.
// Структурируйте код с использованием приватных и публичных компонентов

Задача 2: Интеграция внешних пакетов

Задача: Создать приложение, которое использует несколько внешних пакетов для решения задачи.

// Создайте приложение, которое:
// 1. Получает данные по HTTP
// 2. Анализирует JSON
// 3. Форматирует результаты
// 4. Сохраняет данные локально

Задача 3: Декомпозиция приложения на модули

Задача: Разделить монолитное приложение на модули (библиотеки).

// Возьмите ваше существующее приложение и разделите его на:
// - Модуль работы с API
// - Модуль бизнес-логики
// - Модуль представления (UI для Flutter)
// - Модуль утилит

Задача 4: Создание публикуемого пакета

Задача: Создать пакет, готовый к публикации на pub.dev.

// Создайте пакет, который:
// - Решает конкретную задачу
// - Имеет полную документацию
// - Включает тесты
// - Содержит примеры использования

Задача 5: Управление версиями и зависимостями

Задача: Создать проект с несколькими окружениями и разными зависимостями.

// Создайте проект с отдельными конфигурациями для:
// - Разработки
// - Тестирования
// - Производства
// Управляйте зависимостями для каждого окружения
10 мая 2025 г.
andron13