Flutter — кроссплатформенная разработка приложений
Flutter — это тулкит Google, который позволяет писать мобильное приложение один раз на языке Dart, а запускать на Android и iOS. Собственный движок отрисовки даёт быстрый, плавный интерфейс, единый на всех платформах. Подходит и для веба, и для десктопа.
1. Что такое Flutter
Flutter — это не язык, а фреймворк: интерфейс собирается из виджетов, а язык программирования — Dart (создан в Google, синтаксис похож на Java и JavaScript). В отличие от React Native, Flutter не использует «нативные» элементы системы — он всё рисует сам через движок Skia/Impeller.
Один код → несколько платформ: Android, iOS, а дополнительно — веб и настольные системы. Горячая перезагрузка (Hot Reload) показывает изменения за доли секунды прямо на запущенном устройстве.
// Первая программа на Dart
void main() {
print("Привет, Flutter!");
final sum = add(2, 3); // 5
}
int add(int a, int b) => a + b;2. Установка и настройка
Скачай SDK с официального сайта flutter.dev, распакуй в удобную папку и добавь flutter/bin в переменную окружения PATH. Проверка окружения — командой flutter doctor.
# Windows
git clone https://github.com/flutter/flutter.git -b stable
set PATH=%PATH%;C:\src\flutter\bin
# Проверка окружения — она подскажет, чего не хватает
flutter doctor
flutter --versionДля Android дополнительно ставят Android Studio (для SDK и эмулятора), для iOS на Mac — Xcode. Проверка покажет недостающие компоненты в виде таблицы статусов.
flutter doctor — первая команда, которую стоит запускать после установки: она диагностирует Android Toolchain, Xcode, Chrome и лицензии SDK.Program Files). Лучшее место — корень диска: C:\flutter.3. Первый проект и структура
Проект создаётся из шаблона, затем запускается на эмуляторе или устройстве:
flutter create my_app
cd my_app
flutter runКлючевые папки проекта:
| Путь | Что внутри |
|---|---|
lib/main.dart | весь код приложения (обычно пишут здесь) |
pubspec.yaml | зависимости и ресурсы проекта |
android/ | обёртка платформы Android |
ios/ | обёртка платформы iOS |
test/ | автотесты на языке Dart |
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(home: HomePage());
}
}pubspec.yaml — аналог package.json из Node.js: пакеты добавляются в секцию dependencies, после чего выполняется flutter pub get.4. Основы языка Dart
Dart — объектно-ориентированный язык со строгой типизацией и возможностью вывода типов. Из ключевых моментов: константы final и const, неизменяемые переменные, безопасность от null.
void main() {
var name = "Анна"; // var — тип выводится автоматически
final city = "Москва"; // final — значение нельзя изменить
const grav = 9.8; // const — константа времени компиляции
var nums = [1, 2, 3];
var map = {'key': 'value'};
String? maybe; // nullable-переменная
int length = maybe?.length ?? 0; // ?. безопасный доступ, ?? — запасное значение
}
// Класс с конструктором
class User {
final String name;
User(this.name);
}=>.5. Виджеты и композиция
Виджеты бывают двух видов: StatelessWidget (неизменяемый, без состояния) и StatefulWidget (хранит состояние и перерисовывается при его изменении).
class Counter extends StatefulWidget {
@override
State<Counter> createState() => _CounterState();
}
class _CounterState extends State<Counter> {
int _count = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("Нажатий: $_count", style: const TextStyle(fontSize: 24)),
ElevatedButton(
onPressed: () => setState(() => _count++),
child: const Text("Плюс"),
),
],
),
),
);
}
}Базовые виджеты, которые нужны почти всегда:
| Виджет | Для чего |
|---|---|
Text | вывод текста |
Container | контейнер с отступами, цветом, рамкой |
Column | вертикальное расположение детей |
Row | горизонтальное расположение детей |
ListView | прокручиваемый список |
Stack | наложение виджетов слоями |
setState пересобирает весь build текущего виджета. Держи тяжёлые вычисления вне build, чтобы не тормозить интерфейс.6. Layout и расположение
Вёрстка — это вложенные контейнеры. Основные инструменты компоновки: Center (центрирование), Padding (отступы), Expanded (растягивание), SizedBox (фиксированный размер и пустой зазор).
Container(
width: 200,
height: 100,
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(8),
),
child: const Center(child: Text("Контейнер")),
)
Row(
children: [
Expanded(child: Container(color: Colors.red, height: 50)),
Expanded(child: Container(color: Colors.yellow, height: 50)),
],
)Гибкость управляется свойствами mainAxisAlignment (главная ось) и crossAxisAlignment (поперечная ось). Для точного попиксельного контроля применяй Alignment(x, y): х и у в диапазоне от −1 до 1.
SizedBox(width: 16) для пустого места между элементами — это идиоматичный способ сделать отступы без отдельных виджетов.7. Навигация и маршруты
Переходы между экранами выполняет Navigator: стек экранов, куда можно положить новый маршрут или вытащить текущий.
// Переход на новый экран с ожиданием результата
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const DetailPage()),
);
// Возврат назад
Navigator.pop(context);
// Маршруты, описанные заранее (подойдёт для больших приложений)
MaterialApp(
routes: {
'/detail': (context) => const DetailPage(),
},
)Именованные маршруты задаются в MaterialApp через routes, переход — Navigator.pushNamed(context, '/detail'). Экрану можно передать параметры через конструктор виджета.
BottomNavigationBar с разными разделами, у каждого — свой стек.8. Управление состоянием
Для одного экрана достаточно setState. Когда состояние нужно разделить между экранами, подключи пакет provider — самый простой из распространённых подходов.
class Cart extends ChangeNotifier {
final List<String> _items = [];
List<String> get items => List.unmodifiable(_items);
void add(String item) {
_items.add(item);
notifyListeners(); // сообщаем подписчикам об изменении
}
}
// Оборачиваем приложение провайдером
ChangeNotifierProvider(
create: (context) => Cart(),
child: const MyApp(),
)Любой виджет читает состояние через хук:
final cart = context.watch<Cart>();
Text("В корзине: ${cart.items.length}")ChangeNotifier логику сети — только состояние и простые методы. Сетевые вызовы выноси в сервисы, а управление эффектами — в отдельный слой.riverpod и bloc — они дают больше гибкости и тестируемости. Начинать же с provider достаточно.9. Работа с сетью
Зависимость http добавляется в pubspec.yaml. Запрос выполняется асинхронно, ответ разбирается через jsonDecode.
import 'dart:convert';
import 'package:http/http.dart' as http;
Future<List<String>> fetchTitles() async {
final response = await http.get(
Uri.parse('https://jsonplaceholder.typicode.com/posts'),
);
if (response.statusCode != 200) {
throw Exception('Ошибка запроса: ${response.statusCode}');
}
final data = jsonDecode(response.body) as List;
return data.map((post) => post['title'] as String).toList();
}Вызвать и показать результат удобно через FutureBuilder, который сам перестраивает вид при завершении запроса:
FutureBuilder<List<String>>(
future: fetchTitles(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView(children: [
for (final title in snapshot.data!) Text(title),
]);
}
return const CircularProgressIndicator();
},
)dio — он добавляет перехватчики, таймауты, отмену запросов и работу с файлами.10. Локальное хранение данных
Произвольные пары «ключ-значение» хранятся через пакет shared_preferences, структурированные данные — в SQLite через sqflite.
// shared_preferences: настройки и мелкие данные
final prefs = await SharedPreferences.getInstance();
await prefs.setInt('counter', 5);
await prefs.setString('token', 'abc123');
final counter = prefs.getInt('counter') ?? 0;// sqflite: полноценная база
final db = await openDatabase('notes.db', version: 1);
await db.execute(
'CREATE TABLE notes (id INTEGER PRIMARY KEY, text TEXT)',
);
await db.insert('notes', {'text': 'Купить продукты'});
final rows = await db.query('notes');Изоморфный пакет hive — быстрая база-коробка без SQL: данные записываются как объекты Dart напрямую.
shared_preferences — используй flutter_secure_storage, который обращается к системным хранилищам (Keychain на iOS, Keystore на Android).11. Сборка APK для Android
Для релиза собери продакшен-сборку:
# Обычный APK
flutter build apk --release
# Готовый файл лежит здесь
# build/app/outputs/flutter-apk/app-release.apk
# App Bundle для Google Play (рекомендуется)
flutter build appbundle --releaseDebug-сборка (с горячей перезагрузкой) собирается командой flutter run. Для подписи релиза в android/app/build.gradle.kts настраивают signingConfig с файлом ключа .jks.
flutter analyze — статический анализатор найдёт ошибки стиля и потенциальные баги.versionCode) в pubspec.yaml перед каждой новой сборкой — иначе Play Store отклонит загрузку старой версии.12. Тесты Flutter-приложения
Тесты бывают трёх уровней: unit-тесты логики, widget-тесты интерфейса и интеграционные на устройстве. Базовые запускаются командой flutter test.
// test/widget_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:my_app/main.dart';
void main() {
testWidgets('счётчик работает', (tester) async {
await tester.pumpWidget(const MyApp());
expect(find.text('Нажатий: 0'), findsOneWidget);
await tester.tap(find.text('Плюс'));
await tester.pump();
expect(find.text('Нажатий: 1'), findsOneWidget);
});
}tester.pump() перерисовывает дерево после события, а find.text ищет виджет по тексту. Так проверяется реальное поведение интерфейса.
13. Анимации
Простые анимации Flutter даёт через встроенные виджеты: AnimatedContainer, AnimatedOpacity, TweenAnimationBuilder. Они плавно изменяют свойства без ручного управления кадрами:
AnimatedContainer(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
width: isOpen ? 200 : 60,
height: 60,
color: isOpen ? Colors.blue : Colors.grey,
)Переход между экранами анимируют через Hero — виджет с одним тегом «перелетает» с одного экрана на другой:
Hero(tag: 'avatar', child: CircleAvatar(...))Transform: он работает на GPU без пересборки дерева.14. Плагины и платформенные возможности
Доступ к камере, геолокации, уведомлениям и другим функциям ОС дают pub-пакеты. Типичный набор:
| Возможность | Пакет |
|---|---|
| камера и галерея | image_picker |
| уведомления | flutter_local_notifications |
| геолокация | geolocator |
| http-запросы | dio / http |
| защищённое хранение | flutter_secure_storage |
Подключение: добавь пакет в pubspec.yaml и выполни flutter pub get. Пример использования:
final picked = await ImagePicker().pickImage(
source: ImageSource.gallery,
);MethodChannel для вызова собственного кода Android/iOS.15. Производительность и профилирование
Если интерфейс «тормозит», сначала измерь: профилировщик DevTools и вкладка Performance покажут, какие виджеты перестраиваются зря. Частые причины:
- тяжёлые вычисления внутри
build— выноси в асинхронные функции и кэшируй результат; - длинный список ребилдится целиком — используй
ListView.builderс ленивым созданием; - константные виджеты: помечай
const— они переиспользуются без перерисовки.
ListView.builder(
itemCount: items.length,
itemBuilder: (context, i) => ListTile(title: Text(items[i])),
)16. Окружения сборки: --dart-define и flavor
Разработка и продакшен часто требуют разных настроек: базовые URL API, ключи аналитики, имена пакетов. Flutter позволяет передавать значения на этапе запуска:
flutter run --dart-define=API_URL=https://dev.api.example.com
flutter build apk --dart-define=API_URL=https://api.example.com
// main.dart
const apiUrl = String.fromEnvironment(
'API_URL',
defaultValue: 'http://localhost:3000',
);Ключи компилируются в сборку статически и не меняются после неё. Для различия типов приложения (dev/stage/prod с разными иконками и именами пакетов) используют flavor: конфигурация в build.gradle.kts, запуск — flutter run --flavor prod.
Не храни настоящие секреты через --dart-define: они попадают в клиент и видны в APK. Секреты живут только на сервере.
17. Локализация приложения
Двух языков достаточно почти любому приложению. Пакет flutter_localizations и файлы ARB (Archival Resource Bundle) организуют переводы:
flutter pub add flutter_localizations --sdk=flutter intl
// pubspec.yaml
flutter:
generate: trueВ app_ru.arb лежат переводы в JSON-формате: {"hello": "Привет, мир!"}, в app_en.arb — английский. Генерация классов: flutter gen-l10n, после чего строки доступны как context.l10n.hello. Язык определяется системой через MaterialApp(locale: ...).
Помимо текста переводи плюральные формы («одна заметка / две заметки / пять заметок») и форматы дат — intl с DateFormat избавляет от ручной работы с региональными стандартами.
18. Темизация: тёмная тема и адаптив
MaterialApp принимает палитру через ThemeData: один объект меняет цвета всего приложения, а виджеты берут цвета через Theme.of(context).
MaterialApp(
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
brightness: Brightness.light,
),
darkTheme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.blue,
brightness: Brightness.dark,
),
),
themeMode: ThemeMode.system,
)ThemeMode.system подстраивается под настройки ОС: тёмная тема включается без единой строки кода. В виджетах используй не жёсткие цвета, а colorScheme.primary, surface, onSurface — тогда обе темы работают согласованно.
Проверяй контраст текста на фоне: для обычного текста WCAG AA требует минимум 4.5:1. Инструменты вроде плагина contrast_aa показывают контраст прямо в редакторе.
colorScheme или выноси в ThemeExtension.19. CI/CD, подпись и публикация
Стабильный релиз-пайплайн: анализ → тесты → сборка → подпись → доставка в стор. Типовой шаг в GitHub Actions:
- uses: subosito/flutter-action@v2
with: { channel: stable }
- run: flutter pub get
- run: flutter analyze
- run: flutter testAndroid-подпись: файл ключа .jks и пароли клади в секреты репозитория, подставляй в build.gradle.kts только на этапе релиз-сборки. iOS — сертификат .p12 и provisioning profile тоже из секретов; экспорт IPA делают через xcodebuild или Fastlane.
Fastlane автоматизирует отгрузку: lane для Play Store (fastlane supply) и App Store (fastlane deliver). После выпуска следи за метриками падений — Crashlytics или Sentry подключённые через плагин.
pubspec.yaml, чтобы не забыть вручную.Что дальше
Освоил основы — расширь проект: работа с камерой и картой (пакеты camera, flutter_map), анимации через AnimatedContainer и Hero. Управление версиями — курс Git, интеграции с API — REST API, синхронизация с сервером — Django или Node.js. Для сравнения с другими подходами смотри Android (Kotlin), iOS (Swift) и React Native.