Пакети та плагіни у Flutter
Flutter має багату екосистему пакетів та плагінів, які розширюють функціональність застосунків. Пакети доступні на pub.dev — офіційному репозиторії Dart.
Управління пакетами
pubspec.yaml
Файл pubspec.yaml містить конфігурацію проєкту та список залежностей.
name: my_app
description: Мій Flutter застосунок
version: 1.0.0+1
environment:
sdk: '>=3.0.0 <4.0.0'
dependencies:
flutter:
sdk: flutter
# Стандартні пакети
http: ^1.1.0
provider: ^6.0.0
shared_preferences: ^2.2.0
# Пакет з git
my_package:
git:
url: https://github.com/user/my_package.git
ref: main
# Локальний пакет
local_package:
path: ../local_package
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^3.0.0
mockito: ^5.4.0
build_runner: ^2.4.0
flutter:
uses-material-design: true
assets:
- assets/images/
- assets/fonts/
fonts:
- family: CustomFont
fonts:
- asset: assets/fonts/CustomFont-Regular.ttfКоманди для роботи з пакетами
# Встановлення залежностей
flutter pub get
# Оновлення залежностей
flutter pub upgrade
# Перевірка застарілих пакетів
flutter pub outdated
# Додавання пакета
flutter pub add http
# Видалення пакета
flutter pub remove http
# Очищення кешу
flutter pub cache cleanCodegen: генерація коду (Freezed, json_serializable, build_runner)
У Flutter/Dart дуже популярний підхід, коли “рутинний” код генерується автоматично: fromJson/toJson, copyWith, ==/hashCode, sealed-класи, іммутабельні моделі.
build_runner
Зазвичай генерація запускається через build_runner:
flutter pub run build_runner build
# або у watch-режимі
flutter pub run build_runner watch
# якщо є конфлікти/старі згенеровані файли
flutter pub run build_runner build --delete-conflicting-outputsjson_serializable
Підхід: описуємо модель та анотації, генератор створює .g.dart.
dependencies:
json_annotation: ^4.9.0
dev_dependencies:
build_runner: ^2.4.0
json_serializable: ^6.8.0Freezed
Freezed корисний для іммутабельних моделей та станів (особливо у BLoC/StateNotifier): зручний copyWith, union types, pattern matching.
dependencies:
freezed_annotation: ^2.4.0
dev_dependencies:
build_runner: ^2.4.0
freezed: ^2.5.0Популярні пакети
HTTP запити
http
import 'package:http/http.dart' as http;
import 'dart:convert';
class ApiService {
final String baseUrl = 'https://api.example.com';
Future<List<User>> getUsers() async {
final response = await http.get(Uri.parse('$baseUrl/users'));
if (response.statusCode == 200) {
final List<dynamic> data = jsonDecode(response.body);
return data.map((json) => User.fromJson(json)).toList();
} else {
throw Exception('Помилка завантаження');
}
}
Future<User> createUser(User user) async {
final response = await http.post(
Uri.parse('$baseUrl/users'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode(user.toJson()),
);
if (response.statusCode == 201) {
return User.fromJson(jsonDecode(response.body));
} else {
throw Exception('Помилка створення');
}
}
}dio
import 'package:dio/dio.dart';
class DioApiService {
final Dio _dio = Dio(BaseOptions(
baseUrl: 'https://api.example.com',
connectTimeout: Duration(seconds: 5),
receiveTimeout: Duration(seconds: 3),
));
DioApiService() {
_dio.interceptors.add(InterceptorsWrapper(
onRequest: (options, handler) {
options.headers['Authorization'] = 'Bearer $token';
return handler.next(options);
},
onResponse: (response, handler) {
return handler.next(response);
},
onError: (error, handler) {
print('Помилка: ${error.message}');
return handler.next(error);
},
));
}
Future<List<User>> getUsers() async {
try {
final response = await _dio.get('/users');
return (response.data as List)
.map((json) => User.fromJson(json))
.toList();
} on DioException catch (e) {
throw Exception('Помилка: ${e.message}');
}
}
Future<void> uploadFile(File file) async {
final formData = FormData.fromMap({
'file': await MultipartFile.fromFile(file.path),
});
await _dio.post('/upload', data: formData);
}
}Стан застосунку
provider
import 'package:provider/provider.dart';
// Модель стану
class CounterProvider extends ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners();
}
void decrement() {
_count--;
notifyListeners();
}
}
// Налаштування
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => CounterProvider()),
ChangeNotifierProvider(create: (_) => UserProvider()),
],
child: MyApp(),
),
);
}
// Використання
class CounterScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Consumer<CounterProvider>(
builder: (context, counter, child) {
return Text('Лічильник: ${counter.count}');
},
);
}
}
// Або з context
class AnotherWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final counter = context.watch<CounterProvider>();
return ElevatedButton(
onPressed: () => context.read<CounterProvider>().increment(),
child: Text('Збільшити: ${counter.count}'),
);
}
}riverpod
import 'package:flutter_riverpod/flutter_riverpod.dart';
// Провайдери
final counterProvider = StateNotifierProvider<CounterNotifier, int>((ref) {
return CounterNotifier();
});
class CounterNotifier extends StateNotifier<int> {
CounterNotifier() : super(0);
void increment() => state++;
void decrement() => state--;
}
// Провайдер з залежностями
final userProvider = FutureProvider<User>((ref) async {
final apiService = ref.watch(apiServiceProvider);
return apiService.getUser();
});
// Налаштування
void main() {
runApp(
ProviderScope(child: MyApp()),
);
}
// Використання
class CounterWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(counterProvider);
return Column(
children: [
Text('Лічильник: $count'),
ElevatedButton(
onPressed: () => ref.read(counterProvider.notifier).increment(),
child: Text('Збільшити'),
),
],
);
}
}Локальне сховище
shared_preferences
import 'package:shared_preferences/shared_preferences.dart';
class PreferencesService {
static const String _keyTheme = 'theme_mode';
static const String _keyUser = 'user_data';
Future<void> setThemeMode(String mode) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setString(_keyTheme, mode);
}
Future<String?> getThemeMode() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getString(_keyTheme);
}
Future<void> saveUser(User user) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setString(_keyUser, jsonEncode(user.toJson()));
}
Future<User?> getUser() async {
final prefs = await SharedPreferences.getInstance();
final data = prefs.getString(_keyUser);
if (data != null) {
return User.fromJson(jsonDecode(data));
}
return null;
}
Future<void> clearAll() async {
final prefs = await SharedPreferences.getInstance();
await prefs.clear();
}
}hive
import 'package:hive_flutter/hive_flutter.dart';
// Модель
@HiveType(typeId: 0)
class User extends HiveObject {
@HiveField(0)
String name;
@HiveField(1)
String email;
@HiveField(2)
int age;
User({required this.name, required this.email, required this.age});
}
// Ініціалізація
Future<void> main() async {
await Hive.initFlutter();
Hive.registerAdapter(UserAdapter());
await Hive.openBox<User>('users');
runApp(MyApp());
}
// Використання
class UserRepository {
final Box<User> _box = Hive.box<User>('users');
List<User> getAll() => _box.values.toList();
User? get(String key) => _box.get(key);
Future<void> add(User user) async {
await _box.add(user);
}
Future<void> put(String key, User user) async {
await _box.put(key, user);
}
Future<void> delete(String key) async {
await _box.delete(key);
}
// Реактивне оновлення
ValueListenable<Box<User>> get listenable => _box.listenable();
}
// Віджет з реактивним оновленням
class UserListWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ValueListenableBuilder<Box<User>>(
valueListenable: Hive.box<User>('users').listenable(),
builder: (context, box, _) {
final users = box.values.toList();
return ListView.builder(
itemCount: users.length,
itemBuilder: (context, index) {
return ListTile(title: Text(users[index].name));
},
);
},
);
}
}Навігація
go_router
import 'package:go_router/go_router.dart';
final router = GoRouter(
initialLocation: '/',
routes: [
GoRoute(
path: '/',
builder: (context, state) => HomeScreen(),
),
GoRoute(
path: '/user/:id',
builder: (context, state) {
final id = state.pathParameters['id']!;
return UserScreen(userId: id);
},
),
GoRoute(
path: '/settings',
builder: (context, state) => SettingsScreen(),
routes: [
GoRoute(
path: 'profile',
builder: (context, state) => ProfileSettingsScreen(),
),
],
),
],
errorBuilder: (context, state) => ErrorScreen(),
redirect: (context, state) {
final isLoggedIn = authService.isLoggedIn;
final isLoginRoute = state.matchedLocation == '/login';
if (!isLoggedIn && !isLoginRoute) {
return '/login';
}
if (isLoggedIn && isLoginRoute) {
return '/';
}
return null;
},
);
// Використання
void main() {
runApp(
MaterialApp.router(
routerConfig: router,
),
);
}
// Навігація
context.go('/user/123');
context.push('/settings');
context.pop();Зображення
cached_network_image
import 'package:cached_network_image/cached_network_image.dart';
CachedNetworkImage(
imageUrl: 'https://example.com/image.jpg',
placeholder: (context, url) => CircularProgressIndicator(),
errorWidget: (context, url, error) => Icon(Icons.error),
fit: BoxFit.cover,
width: 200,
height: 200,
)
// Кешування з налаштуваннями
CachedNetworkImage(
imageUrl: imageUrl,
cacheManager: CacheManager(
Config(
'customCacheKey',
stalePeriod: Duration(days: 7),
maxNrOfCacheObjects: 100,
),
),
)Іконки
flutter_svg
import 'package:flutter_svg/flutter_svg.dart';
// З активів
SvgPicture.asset(
'assets/icons/icon.svg',
width: 24,
height: 24,
colorFilter: ColorFilter.mode(Colors.blue, BlendMode.srcIn),
)
// З мережі
SvgPicture.network(
'https://example.com/icon.svg',
placeholderBuilder: (context) => CircularProgressIndicator(),
)
// З рядка
SvgPicture.string(
'<svg>...</svg>',
)Анімації
flutter_animate
import 'package:flutter_animate/flutter_animate.dart';
Text('Привіт!')
.animate()
.fadeIn(duration: 500.ms)
.slide()
.then()
.shake()
// Каскадні анімації
Column(
children: [
Text('Перший'),
Text('Другий'),
Text('Третій'),
],
)
.animate(interval: 100.ms)
.fadeIn()
.slideX()
// Повторювані анімації
Icon(Icons.favorite)
.animate(onPlay: (controller) => controller.repeat())
.scale(duration: 1.s)Форми
flutter_form_builder
import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:form_builder_validators/form_builder_validators.dart';
class MyForm extends StatelessWidget {
final _formKey = GlobalKey<FormBuilderState>();
@override
Widget build(BuildContext context) {
return FormBuilder(
key: _formKey,
child: Column(
children: [
FormBuilderTextField(
name: 'email',
decoration: InputDecoration(labelText: 'Email'),
validator: FormBuilderValidators.compose([
FormBuilderValidators.required(),
FormBuilderValidators.email(),
]),
),
FormBuilderDropdown<String>(
name: 'country',
decoration: InputDecoration(labelText: 'Країна'),
items: ['Україна', 'Польща', 'Німеччина']
.map((country) => DropdownMenuItem(
value: country,
child: Text(country),
))
.toList(),
validator: FormBuilderValidators.required(),
),
FormBuilderDateTimePicker(
name: 'date',
decoration: InputDecoration(labelText: 'Дата'),
inputType: InputType.date,
),
FormBuilderCheckbox(
name: 'agree',
title: Text('Я погоджуюсь'),
validator: FormBuilderValidators.equal(true),
),
ElevatedButton(
onPressed: () {
if (_formKey.currentState?.saveAndValidate() ?? false) {
final data = _formKey.currentState!.value;
print(data);
}
},
child: Text('Надіслати'),
),
],
),
);
}
}Інтернаціоналізація
intl та flutter_localizations
// pubspec.yaml
dependencies:
flutter_localizations:
sdk: flutter
intl: ^0.18.0
// lib/l10n/app_uk.arb
{
"@@locale": "uk",
"appTitle": "Мій застосунок",
"greeting": "Привіт, {name}!",
"@greeting": {
"placeholders": {
"name": {}
}
},
"itemCount": "{count, plural, =0{Немає елементів} =1{1 елемент} other{{count} елементів}}",
"@itemCount": {
"placeholders": {
"count": {}
}
}
}
// main.dart
MaterialApp(
localizationsDelegates: [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
supportedLocales: [
Locale('uk'),
Locale('en'),
],
home: MyHomePage(),
)
// Використання
Text(AppLocalizations.of(context)!.appTitle)
Text(AppLocalizations.of(context)!.greeting('Іван'))
Text(AppLocalizations.of(context)!.itemCount(5))Створення власного пакета
Структура пакета
my_package/
├── lib/
│ ├── my_package.dart
│ └── src/
│ ├── my_widget.dart
│ └── my_service.dart
├── test/
│ └── my_package_test.dart
├── example/
│ └── lib/
│ └── main.dart
├── pubspec.yaml
├── README.md
├── CHANGELOG.md
└── LICENSEpubspec.yaml пакета
name: my_package
description: Опис мого пакета
version: 1.0.0
homepage: https://github.com/user/my_package
environment:
sdk: '>=3.0.0 <4.0.0'
flutter: '>=3.0.0'
dependencies:
flutter:
sdk: flutter
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^3.0.0Публікація
# Перевірка перед публікацією
flutter pub publish --dry-run
# Публікація
flutter pub publishНайкращі практики
Перевіряйте сумісність — дивіться на підтримувані версії Flutter/Dart.
Використовуйте перевірені пакети — з високим рейтингом та активною підтримкою.
Фіксуйте версії — використовуйте конкретні версії або діапазони.
Регулярно оновлюйте — перевіряйте
flutter pub outdated.Читайте документацію — кожен пакет має власні особливості.
Висновок
Екосистема пакетів Flutter постійно розвивається. Правильний вибір та використання пакетів може значно прискорити розробку та покращити якість застосунку. Завжди перевіряйте надійність пакета перед його використанням у продакшн-проєктах.