JSON и сериализация данных во Flutter
Сериализация — это фундаментальная часть мобильной разработки. Существует несколько подходов с разными trade-offs.
1. Встроенная сериализация через jsonDecode
import 'dart:convert';
// Простой класс пользователя
class User {
final int id;
final String name;
final String email;
User({
required this.id,
required this.name,
required this.email,
});
// Парсинг из JSON
factory User.fromJson(Map<String, dynamic> json) {
return User(
id: json['id'] as int,
name: json['name'] as String,
email: json['email'] as String,
);
}
// Конвертация в JSON
Map<String, dynamic> toJson() {
return {
'id': id,
'name': name,
'email': email,
};
}
}
// Использование
final json = '{"id": 1, "name": "John", "email": "john@example.com"}';
final jsonData = jsonDecode(json);
final user = User.fromJson(jsonData);
Context в Flutter/Dart — контекст построения виджета
Context (BuildContext) — это объект, который содержит информацию о позиции виджета в дереве виджетов (widget tree). Он предоставляет доступ к различным сервисам Flutter и является ключевым компонентом при разработке Flutter приложений.
Основная идея
Context служит для:
Получение Context
// Context доступен в методе build
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
// context доступен здесь
return Text('Hello');
}
}
Пример использования FutureBuilder
FutureBuilder — это виджет, который позволяет асинхронно загружать данные и обновлять UI в зависимости от состояния Future. Это один из наиболее часто используемых паттернов в Flutter для работы с асинхронными операциями.
Базовый пример
class UserProfilePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return FutureBuilder<User>(
future: fetchUser(userId: "123"),
builder: (context, snapshot) {
// Проверяем состояние Future
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
} else if (snapshot.hasError) {
return Center(child: Text("Error: ${snapshot.error}"));
} else if (snapshot.hasData) {
return UserCard(user: snapshot.data!);
} else {
return Center(child: Text("No data"));
}
},
);
}
}
State в управлении состоянием
State (состояние) — это данные, которые определяют текущее поведение и внешний вид приложения в конкретный момент времени. Это ключевое понятие в управлении состоянием (state management).
Определение и суть
State — это совокупность данных, которая:
Примеры state:
Типы State
1. UI State (состояние интерфейса)
Данные, связанные с представлением:
class UIState {
final bool isLoading;
final String? errorMessage;
final String selectedTab;
final bool isDarkMode;
UIState({
this.isLoading = false,
this.errorMessage,
this.selectedTab = home,
this.isDarkMode = false,
});
}
GetX vs BLoC: Выбор State Management'а
Введение
Это классический вопрос в Flutter сообществе, и ответ не абсолютный — всё зависит от контекста. После 10+ лет я выбрал бы ни то, ни другое, а вместо этого — Provider или Riverpod. Но давайте разберёмся со обоими.
GetX: Обзор
GetX — это инструмент all-in-one для state management, navigation, dependency injection и ещё кучи всего.
// GetX example
class CounterController extends GetxController {
final count = 0.obs;
void increment() => count++;
}
class CounterPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Obx(() => Text('Count: ${Get.find<CounterController>().count}')),
floatingActionButton: FloatingActionButton(
onPressed: () => Get.find<CounterController>().increment(),
child: Icon(Icons.add),
),
);
}
}
// main.dart
Get.put(CounterController()); // DI
Дерево виджетов в Flutter
Widget Tree (дерево виджетов) — это иерархическая структура, в которой все UI элементы приложения организованы в виде дерева. Каждый виджет может иметь дочерние виджеты, создавая древовидную структуру от корня (MyApp) к листьям (Text, Image).
Структура дерева
MyApp
├── MaterialApp
│ ├── home: HomePage
│ │ ├── Scaffold
│ │ │ ├── appBar: AppBar
│ │ │ │ └── title: Text
│ │ │ ├── body: Column
│ │ │ │ ├── Row
│ │ │ │ │ ├── Icon
│ │ │ │ │ └── Text
│ │ │ │ └── ListView
│ │ │ │ └── ListTile (repeated)
│ │ │ └── floatingActionButton: FloatingActionButton
│ │ │ └── Icon
Элементы дерева
1. Widget — объявление UI (неизменяемо):
// Widget описывает КАК должен выглядеть UI
text = Text('Hello', style: TextStyle(fontSize: 18));
2. Element — экземпляр Widget в дереве:
Абстракция
Абстракция — это принцип объектно-ориентированного программирования (ООП), который скрывает сложные детали реализации и предоставляет только необходимый интерфейс для использования. Абстракция позволяет работать с объектами на высоком уровне, не волнуясь о том, как они устроены внутри.
Суть абстракции
Это как использование автомобиля: вы не обязаны знать, как работает двигатель, трансмиссия и электрика. Вам нужно знать только:
Аналлогично в программировании: пользователь класса не должен знать о его внутренней реализации.
Абстракция через интерфейсы и классы
Без абстракции (ПЛОХО)
Что такое ООП (Объектно-ориентированное программирование)
ООП — это парадигма программирования, которая организует код вокруг объектов — сущностей, содержащих данные (свойства) и методы (поведение). Это один из самых популярных подходов к разработке, особенно в Dart и Flutter.
Четыре столпа ООП
Это скрытие внутреннего состояния объекта и предоставление контролируемого доступа через public интерфейс.
class BankAccount {
// Private переменная
double _balance = 0;
// Public метод для безопасного доступа
void deposit(double amount) {
if (amount > 0) {
_balance += amount;
}
}
// Getter для получения значения
double get balance => _balance;
}
void main() {
final account = BankAccount();
account.deposit(100);
print(account.balance); // 100
}
initState(), didChangeDependencies() и dispose()
Это методы жизненного цикла StatefulWidget. Понимание их порядка и использования — критично для правильного управления ресурсами.
Жизненный цикл StatefulWidget
Архитектурные паттерны во Flutter
Архитектура определяет качество, масштабируемость и maintainability проекта. Рассмотрю три основных подхода.
1. MVC (Model-View-Controller) — старый подход
Одна из первых архитектур, но во Flutter редко используется.
┌─────────────┐
│ Controller │
│ (Logic) │
└──────┬──────┘
│
├──→ Model (State, Data)
└──→ View (UI)
Проблемы:
- Controller быстро становится жирным
- Сложно тестировать UI
- Tight coupling между компонентами
2. MVP (Model-View-Presenter) — лучше, но громоздко
┌─────────────────────────────────┐
│ Presenter │
│ (Business Logic & UI Logic) │
└─────────────────────────────────┘
↓ implements ↑ updates
┌─────────────┐ ┌──────────┐
│ View │ │ Model │
│ (UI только)│ │ (Data) │
└─────────────┘ └──────────┘
Способы управления состоянием во Flutter
Во Flutter существует множество подходов к управлению состоянием, каждый из которых подходит для разных сценариев. Выбор правильного способа зависит от сложности приложения, размера команды и требований проекта.
1. setState() — встроенный механизм
setState() — это самый простой и встроенный способ управления локальным состоянием в StatefulWidget:
class Counter extends StatefulWidget {
@override
State<Counter> createState() => _CounterState();
}
class _CounterState extends State<Counter> {
int count = 0;
void incrementCounter() {
setState(() {
count++;
});
}
@override
Widget build(BuildContext context) {
return Text("Count: $count");
}
}
Плюсы: простота, встроенность, хорош для малых компонентов Минусы: плохо масштабируется, сложно тестировать, пересчитывает весь виджет
2. Provider — рекомендуемое решение
Stateless Widget vs Stateful Widget: Когда что использовать
Это один из самых фундаментальных вопросов во Flutter. Понимание разницы между stateless и stateful виджетами критично для написания правильного и эффективного кода.
Основная разница
Stateless Widget:
Stateful Widget:
Stateless Widget: Простой и чистый
Пример:
class WelcomeScreen extends StatelessWidget {
final String userName;
const WelcomeScreen({required this.userName});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Welcome')),
body: Center(
child: Text('Hello, $userName!'),
),
);
}
}
Когда вызывается State.dispose()?
Метод dispose() — это критически важный метод в жизненном цикле StatefulWidget'а. Понимание его вызова необходимо для предотвращения утечек памяти и правильного управления ресурсами.
Основное назначение dispose()
Метод dispose() вызывается, когда State удаляется из дерева виджетов и никогда больше не будет перестроено. Это последняя возможность очистить ресурсы.
class MyScreen extends StatefulWidget {
@override
State<MyScreen> createState() => MyScreenState();
}
Container в Flutter: назначение и практическое применение
Container — это один из самых часто используемых виджетов в Flutter. Это не просто контейнер, это многофункциональный строительный блок для создания интерфейсов.
Что такое Container
Container — это convenience widget (удобный виджет), который объединяет несколько других виджетов для выполнения типичных операций вёрстки:
// Container фактически эквивалентен этой комбинации:
Align(
alignment: Alignment.center,
child: ConstrainedBox(
constraints: BoxConstraints.expand(),
child: DecoratedBox(
decoration: BoxDecoration(...),
child: Padding(
padding: EdgeInsets.all(16),
child: child,
),
),
),
)
// Но вместо этого мы просто пишем:
Container(
alignment: Alignment.center,
constraints: BoxConstraints.expand(),
decoration: BoxDecoration(...),
padding: EdgeInsets.all(16),
child: child,
)
Основные назначения Container
StatelessWidget в Flutter
StatelessWidget — это базовый тип виджета в Flutter, который представляет неизменяемую часть пользовательского интерфейса. Это один из двух основных типов widgets в Flutter, наряду со StatefulWidget.
Определение и концепция
StatelessWidget — это виджет, который не имеет состояния. Его состояние не может изменяться во время жизненного цикла. Если нужно что-то изменить, нужно создать новый экземпляр виджета.
// StatelessWidget — неизменяемый, как константа
const StatelessWidget = FrozenUI();
Структура StatelessWidget
class MyButton extends StatelessWidget {
final String label;
final VoidCallback onPressed;
const MyButton({
required this.label,
required this.onPressed,
});
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: onPressed,
child: Text(label),
);
}
}
Как работает build() метод во Flutter
Что такое build()?
build() — это метод, который описывает как выглядит виджет в данный момент. Это функция, которая возвращает дерево виджетов.
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
color: Colors.blue,
child: Text("Hello"),
);
}
}
build() вызывается в следующих ситуациях:
Жизненный цикл StatelessWidget
class MyStatelessWidget extends StatelessWidget {
const MyStatelessWidget();
Примитивные типы данных в Dart
В Dart есть несколько примитивных типов данных, которые используются для работы с базовыми значениями. Давайте разберём каждый.
1. int (целые числа)
Описание: целые числа любого размера
int age = 25;
int temperature = -10;
int hugeNumber = 9223372036854775807; // 64-bit integer
int zero = 0;
// Литералы
int decimal = 123;
int hexadecimal = 0xFF; // 255
int binary = 0b1010; // 10
Операции:
int a = 10;
int b = 3;
print(a + b); // 13 (сложение)
print(a - b); // 7 (вычитание)
print(a * b); // 30 (умножение)
print(a ~/ b); // 3 (целочисленное деление)
print(a % b); // 1 (остаток от деления)
print(a.abs()); // 10 (абсолютное значение)
print(a.isEven); // false (чётное ли)
print(a.isOdd); // true (нечётное ли)
2. double (вещественные числа)
Описание: числа с плавающей точкой (64-bit)
Где искать библиотеки для Flutter
Найти правильную библиотеку — это навык, который экономит часы разработки. Рассмотрю мой процесс и инструменты.
1. pub.dev (основной источник)
Это официальный репозиторий Dart/Flutter пакетов.
Сайт: https://pub.dev
Фильтры при поиске:
- Null Safety (обязательно ✅)
- Verified Publisher (для популярных пакетов)
- Like Count (чем больше лайков, тем надежнее)
- pub points (оценка качества кода, документации, тестов)
Критерии выбора:
❌ Библиотеки с 0 pub points (no docs, no tests)
✅ Библиотеки с 130+ pub points (хорошее качество)
❌ Последнее обновление > 1 года назад
✅ Активная разработка и maintenance
2. GitHub (для более сложных поисков)
Когда использовать:
Как искать на GitHub:
Паттерны, которые я использую чаще всего
В моей повседневной разработке на Flutter я применяю несколько ключевых паттернов, которые помогают создавать масштабируемый и поддерживаемый код.
1. Паттерн Repository (самый используемый)
Это основной паттерн архитектуры, который я применяю в каждом проекте. Repository абстрагирует источник данных (API, БД, кеш) от бизнес-логики.
abstract class UserRepository {
Future<List<User>> getUsers();
Future<User> getUserById(String id);
Future<void> updateUser(User user);
}
О себе как Flutter разработчика
Я профессиональный разработчик с 10+ лет опыта в разработке мобильных приложений. Специализируюсь на создании высокопроизводительных и масштабируемых приложений для iOS и Android с использованием Flutter.
Опыт и навыки
В течение своей карьеры я работал над проектами различной сложности — от стартапов и MVP до крупных корпоративных приложений с миллионами пользователей. Мой опыт включает полный цикл разработки, от проектирования архитектуры до публикации в маркетплейсах.
Технологический стек
Основной язык — Dart, основной фреймворк — Flutter. Хорошо знаю Swift и Kotlin для работы с платформенным кодом. Использую различные инструменты: Firebase, Stripe, Socket.io для real-time коммуникации.
Примеры проектов
Интеграция REST API во Flutter приложение
Работать с REST API — это ежедневная задача во Flutter разработке. Рассмотрю правильный подход от получения данных до кэширования.
1. Базовое использование http пакета
import 'package:http/http.dart' as http;
import 'dart:convert';
Выбор State Manager для нового приложения
Выбор правильного state manager — критически важное решение, которое влияет на архитектуру и масштабируемость приложения. Давайте разберемся, какой выбрать для проекта с нуля.
Мой выбор: Riverpod
Для нового приложения я выберу Riverpod, вот почему:
1. Лучшая производительность и оптимизации:
final userProvider = FutureProvider<User>((ref) async {
return fetchUser();
});
final userGreetingProvider = Provider<String>((ref) {
final user = ref.watch(userProvider);
return user.when(
data: (u) => 'Hello, ${u.name}!',
loading: () => 'Loading...',
error: (err, st) => 'Error',
);
});
2. Встроенная поддержка async операций:
final postsProvider = FutureProvider<List<Post>>((ref) async {
final response = await dio.get('/posts');
return (response.data as List)
.map((p) => Post.fromJson(p))
.toList();
});
Для чего нужны Stream?
Stream — это один из самых мощных и фундаментальных инструментов в Dart и Flutter для асинхронной обработки данных. Это абстракция, которая позволяет работать с последовательностями событий, поступающих во времени.
Основное назначение Stream
Stream предоставляет способ обработки асинхронных последовательностей данных. Вместо того чтобы получить результат сразу, вы подписываетесь на Stream и получаете значения по мере их появления. Это особенно полезно когда:
Основные типы Stream
Single-subscription Stream — может иметь только одного слушателя:
Stream<int> numbersStream = Stream<int>.periodic(
Duration(seconds: 1),
(count) => count,
).take(5);
StreamBuilder в Flutter: работа с асинхронными потоками данных
StreamBuilder — это виджет, позволяющий строить UI на основе Stream данных. Это одна из самых важных техник реактивного программирования в Flutter.
Что такое StreamBuilder
StreamBuilder — это виджет, который "слушает" Stream и перестраивается каждый раз, когда в Stream поступляют новые данные. Это идеально подходит для реактивного обновления UI.
Async, Await и Future в Dart
Future — асинхронное значение
Future — это объект, который представляет будущее значение, которое может быть доступно со временем или вызвать ошибку:
// Future завершится одним из трёх состояний:
// 1. Успешно (resolved) с значением
// 2. Ошибка (rejected) с исключением
// 3. В ожидании (pending) — ещё не завершён
Future<String> fetchUserName() {
// Эта функция вернёт значение через 2 секунды
return Future.delayed(
Duration(seconds: 2),
() => "John Doe",
);
}
// Использование Future
final future = fetchUserName();
// future содержит обещание получить имя позже
Обработка Future с .then()
fetchUserName().then((name) {
print("User: $name");
}).catchError((error) {
print("Error: $error");
}).whenComplete(() {
print("Request finished");
});
// Цепочка операций
Future<int> getUserAge(String name) {
return Future.delayed(
Duration(seconds: 1),
() => 30,
);
}
Тестирование во Flutter
Три типа тестов
Фреймворк Flutter поддерживает три основных вида тестирования:
┌─────────────────────────────────────────────┐
│ Unit Tests (Юнит-тесты) │
│ Тестирование отдельных функций, классов │
│ Быстро | Не нужен эмулятор | Просто │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ Widget Tests (Виджет-тесты) │
│ Тестирование UI компонентов и виджетов │
│ Средняя скорость | Изолированные тесты │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ Integration Tests (E2E тесты) │
│ Полное приложение от начала до конца │
│ Медленно | Нужен эмулятор | Реальные BL │
└─────────────────────────────────────────────┘
1. Unit Tests (Юнит-тесты)
Тестируем функции, классы, логику БЕЗ UI:
import "package:flutter_test/flutter_test.dart";
Сетевые запросы во Flutter
Встроенный пакет http
http — это встроенный пакет Flutter для выполнения HTTP запросов. Это простой и лёгкий способ:
import "package:http/http.dart" as http;
// GET запрос
Future<void> fetchUserData() async {
try {
final response = await http.get(
Uri.parse("https://api.example.com/users/1"),
headers: {
"Authorization": "Bearer token",
"Content-Type": "application/json",
},
);
if (response.statusCode == 200) {
print(response.body);
} else {
print("Error: ${response.statusCode}");
}
} catch (e) {
print("Error: $e");
}
}
// POST запрос
Future<void> createUser() async {
final response = await http.post(
Uri.parse("https://api.example.com/users"),
headers: {"Content-Type": "application/json"},
body: jsonEncode({
"name": "John",
"email": "john@example.com",
}),
);
if (response.statusCode == 201) {
print("User created");
}
}
Что такое однопоточность
Однопоточность (single-threaded) — это модель выполнения, при которой приложение выполняет только один процесс за раз в главном потоке. Dart и Flutter используют однопоточную модель с асинхронным программированием.
Основные концепции
Однопоточность означает:
void main() {
// Dart выполняет код последовательно
print("1"); // выполнится первым
print("2"); // выполнится вторым
print("3"); // выполнится третьим
// Результат: 1, 2, 3
}
Как это работает в Dart
Event Loop (Цикл событий):
// Dart использует Event Loop для управления асинхронностью
Git Merge
Git merge — операция объединения двух или более веток (branches) в одну. Это один из основных инструментов командной разработки, позволяющий интегрировать изменения из разных веток в основную ветку проекта.
Основной концепт
Вы работаете в отдельной ветке feature/auth, создаёте коммиты с новой функциональностью, а затем хотите объединить эти изменения в ветку main. Для этого используется git merge:
git checkout main
git merge feature/auth
Fast-forward merge создаёт новый коммит, объединяющий истории обеих веток.
Типы Merge
1. Fast-Forward Merge
Это самый простой случай. Если main не имел изменений с момента создания feature/auth, Git просто перемещает указатель main на последний коммит feature/auth:
main: A → B → C
feature/auth: → D → E
После merge: A → B → C → D → E
Команда:
git merge feature/auth # Fast-forward
2. Three-Way Merge
Используется, когда обе ветки имеют расходящиеся истории:
Пример использования FutureBuilder
FutureBuilder — это один из самых полезных виджетов в Flutter для работы с асинхронными операциями.
Основной пример: загрузка данных пользователя
import 'package:flutter/material.dart';
didUpdateWidget: Отслеживание изменений свойств
didUpdateWidget — это важный lifecycle метод в StatefulWidget, который вызывается когда родительский виджет перестраивается и передаёт новые параметры (properties) в StatefulWidget. Это ключевой метод для реагирования на изменения входных данных.
Когда вызывается didUpdateWidget
class MyStatefulWidget extends StatefulWidget {
final String title; // Входное свойство
const MyStatefulWidget({required this.title});
@override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
Future и Stream в Dart: Полное руководство
Future и Stream — это два основных способа работы с асинхронным кодом в Dart. Они решают разные задачи и используются в зависимости от характера данных.
Future — одно значение в будущем
Future представляет результат асинхронной операции, которая произойдёт в будущем (или не произойдёт, если произойдёт ошибка). Это как обещание вернуть ровно одно значение.
// Простой Future
Future<String> fetchData() async {
await Future.delayed(Duration(seconds: 2));
return "Hello, Future!";
}
// Использование
void main() async {
final data = await fetchData();
print(data); // Hello, Future!
}
// Альтернативный способ без async/await
void main() {
fetchData().then((data) {
print(data); // Hello, Future!
}).catchError((error) {
print("Error: $error");
});
}
Состояния Future:
Виды виджетов в Flutter
Виджет — это базовая строительная единица Flutter приложения. Каждый элемент UI (кнопка, текст, картинка) — это виджет. Flutter имеет сотни встроенных виджетов, разделённых на категории.
1. Stateless vs Stateful
StatelessWidget — виджет без состояния, неизменяемый:
class MyButton extends StatelessWidget {
const MyButton({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () => print('Clicked'),
child: Text('Press me'),
);
}
}
StatefulWidget — виджет со состоянием, может меняться:
class Counter extends StatefulWidget {
const Counter({Key? key}) : super(key: key);
@override
State<Counter> createState() => _CounterState();
}
class _CounterState extends State<Counter> {
int count = 0;
Неявные анимации в Flutter
Неявные анимации — это встроенный механизм Flutter для плавного переходу между состояниями без явного управления анимационными контроллерами. Это один из самых удобных способов добавления анимаций в приложение.
Определение и основная концепция
Неявная анимация автоматически создает переход между старым и новым значением свойства, когда изменяется параметр виджета. Вместо того чтобы вручную управлять AnimationController и Ticker, Flutter сам управляет анимацией за кулисами.
Примеры неявных анимаций: TweenAnimationBuilder, AnimatedContainer, AnimatedOpacity, AnimatedPositioned, AnimatedAlign и многие другие.
Как работают неявные анимации
stateChanged()
↓
Change widget parameter
↓
Flutter detects the change
↓
Automatically create animation from old to new value
↓
Rebuild with interpolated values during animation
Пример 1: AnimatedContainer
Это самый популярный виджет для неявных анимаций.
Варианты локального хранения данных во Flutter
Во Флаттере есть несколько способов сохранения данных локально на устройстве. Выбор зависит от типа и размера данных, скорости доступа и требований к структуре.
1. SharedPreferences — простые ключ-значение
SharedPreferences — это самый простой способ сохранения примитивных данных (строки, числа, булевы значения). Данные хранятся в виде ключ-значение.
import package:shared_preferences/shared_preferences.dart;
// Сохранение
final prefs = await SharedPreferences.getInstance();
await prefs.setString(username, John);
await prefs.setInt(age, 30);
await prefs.setBool(isLoggedIn, true);
// Загрузка
final String? username = prefs.getString(username);
final int? age = prefs.getInt(age);
final bool isLoggedIn = prefs.getBool(isLoggedIn) ?? false;
// Удаление
await prefs.remove(username);
Cupertino в Flutter
Что такое Cupertino
Cupertino — это набор компонентов Flutter, которые следуют дизайн-языку Apple (iOS design language). Название происходит от Cupertino, Калифорния — место, где находится штаб-квартира Apple.
Это одна из двух основных UI библиотек в Flutter:
Material vs Cupertino
// Material Design (Android-like)
import 'package:flutter/material.dart';
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('Home')),
body: Center(child: Text('Hello')),
floatingActionButton: FloatingActionButton(
onPressed: () {},
child: Icon(Icons.add),
),
),
);
}
}
// Cupertino Design (iOS-like)
import 'package:flutter/cupertino.dart';
Как организуешь взаимодействие между блоками?
Взаимодействие между BLoC'ами — это критичная часть архитектуры больших Flutter приложений. Существует несколько подходов, каждый имеет свои плюсы и минусы.
1. Event-driven архитектура (рекомендуемый подход)
Один BLoC генерирует события, которые обрабатывает другой BLoC:
// UserBloc — генерирует события при изменении пользователя
class UserBloc extends Bloc<UserEvent, UserState> {
final UserRepository userRepository;
UserBloc(this.userRepository) : super(UserInitial()) {
on<FetchUserEvent>((event, emit) async {
emit(UserLoading());
try {
final user = await userRepository.getUser(event.userId);
emit(UserLoaded(user: user));
} catch (e) {
emit(UserError(message: e.toString()));
}
});
}
}
Как объявить non-nullable переменную и при этом установить для неё значение?
Это один из ключевых вопросов о null safety в Dart. Non-nullable переменные требуют инициализации значением, не null.
Основные способы объявления
1. Инициализация при объявлении (самый простой способ)
// ✅ Правильно - инициализируем сразу
String name = "John";
int age = 25;
bool isActive = true;
List<String> tags = ["flutter", "dart"];
Это гарантирует, что переменная всегда имеет значение.
2. Инициализация в конструкторе класса
Для полей класса это самый распространённый способ:
class User {
late String name; // Объявляем non-nullable
late int age;
User(this.name, this.age); // Инициализируем в конструкторе
}
final user = User("Alice", 30);
print(user.name); // "Alice" - гарантированно не null
Даже лучше — использовать инициализацию параметров:
AppBar в Flutter: Назначение и возможности
AppBar — это один из самых важных виджетов в Flutter, который представляет собой материальный дизайн верхнюю панель приложения. Это не просто декоративный элемент, а функциональный компонент, который обеспечивает ключевые возможности для взаимодействия пользователя с приложением.
Основные функции AppBar
1. Отображение заголовка приложения AppBar служит местом для размещения названия текущего экрана или приложения. Это помогает пользователю понять, где он находится в приложении.
AppBar(
title: Text("Мой профиль"),
)
2. Навигация AppBar автоматически добавляет кнопку "назад" (leading button) при использовании в контексте маршрутизации. Это позволяет пользователям легко вернуться на предыдущий экран.
3. Действия и меню Применять несколько кнопок действий (actions) в правой части AppBar. Это могут быть кнопки поиска, сохранения, удаления и другие операции.
Чистая архитектура в Flutter
Чистая архитектура (Clean Architecture) — это подход к организации кода, разработанный Robert Martin. Цель: сделать код независимым от фреймворков, баз данных, UI и легко тестируемым.
Основные принципы
Все слои организованы как слои луковицы, где каждый слой зависит только от более внутренних:
┌─────────────────────────────────────────┐
│ Presentation (UI, State Management) │
├─────────────────────────────────────────┤
│ Application (Use Cases, Controllers) │
├─────────────────────────────────────────┤
│ Domain (Entities, Repository interfaces)│
├─────────────────────────────────────────┤
│ Infrastructure (API, Database) │
└─────────────────────────────────────────┘
Слои архитектуры
Domain Layer — самый независимый слой, содержит бизнес-логику:
Provider vs InheritedWidget
Это классический вопрос в Flutter. Оба решения работают для передачи данных вниз по дереву виджетов, но выбор зависит от контекста и требований проекта.
Коротко: что выберу я
Я выберу Provider практически всегда. Вот почему: Provider построен на InheritedWidget и предоставляет удобный API, лучшую производительность, переиспользуемость и ecosystem пакетов.
Но давайте разберёмся подробнее.
InheritedWidget — низкоуровневый инструмент
InheritedWidget — это базовый механизм Flutter для передачи данных:
class MyData extends InheritedWidget {
final String value;
MyData({
required this.value,
required Widget child,
}) : super(child: child);
@override
bool updateShouldNotify(MyData oldWidget) {
return oldWidget.value != value;
}
static MyData? of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<MyData>();
}
}
// Использование
MyData.of(context)?.value
Расскажи про структуры данных
Это важный вопрос для Flutter разработчика. Расскажи про структуры данных включает в себя несколько ключевых аспектов, которые я понимаю и могу объяснить.
Основные концепции
Вопрос касается важной части разработки на Flutter и требует глубокого понимания фреймворка.
Практическое применение
В реальных проектах часто используются эти концепции для создания эффективного и масштабируемого кода.
Лучшие практики
При работе с этим материалом следует придерживаться лучших практик и принципов чистой архитектуры.
Приоритизация задач: системный подход разработчика
Мой процесс приоритизации
Я использую комбинацию методов для эффективной приоритизации, потому что разные типы задач требуют разного подхода. Это помогает балансировать между срочностью, важностью и долгосрочной ценностью проекта.
1. Матрица Eisenhower (Срочность × Важность)
┌─────────────────────────┬──────────────────────┐
│ ВАЖНО + СРОЧНО │ ВАЖНО + НЕ СРОЧНО │
│ (Do First) │ (Schedule) │
│ - Production bugs │ - Рефакторинг │
│ - Критичные features │ - Оптимизация │
│ - Security issues │ - Документация │
├─────────────────────────┼──────────────────────┤
│ НЕ ВАЖНО + СРОЧНО │ НЕ ВАЖНО + НЕ СР. │
│ (Delegate/Minimize) │ (Eliminate) │
│ - Срочные запросы │ - Nice-to-have │
│ - Текущие проблемы │ - Золотое покрытие │
└─────────────────────────┴──────────────────────┘
2. Оценка влияния и затрат
Деревья в Flutter
В Flutter существует несколько типов деревьев, каждое отвечает за разную часть архитектуры приложения.
1. Widget Tree
Это логическое описание UI структуры:
MyApp()
├── MaterialApp
│ └── Scaffold
│ ├── AppBar
│ └── Body
Stроится в методе build() и пересчитывается при каждом обновлении.
2. Element Tree
Внутреннее представление Widget Tree. Элементы управляют жизненным циклом виджетов:
3. Render Tree
Самый низкий уровень — RenderObject's:
RenderBox (Scaffold)
├── RenderAppBar
├── RenderColumn
│ ├── RenderImage
│ └── RenderText
Отвечает за:
4. BuildContext Tree
Связь между Widget и Element:
StatefulWidget — основной паттерн для изменяемого состояния
StatefulWidget — это базовый класс в Flutter для создания виджетов, которые могут менять своё состояние в процессе жизненного цикла приложения. В отличие от StatelessWidget, который рендерится один раз и не обновляется, StatefulWidget позволяет отслеживать и обновлять данные в реальном времени.
Основные назначения StatefulWidget
1. Управление изменяемым состоянием
setState() для пересчёта виджетаclass Counter extends StatefulWidget {
@override
State<Counter> createState() => _CounterState();
}
class _CounterState extends State<Counter> {
int count = 0;
void increment() {
setState(() {
count++;
});
}
@override
Widget build(BuildContext context) {
return Text(count.toString());
}
}
Является ли Flutter однопоточным?
Да, Flutter использует однопоточную архитектуру на основе модели событий. Это важный концепт, который нужно понимать для эффективной разработки.
Архитектура Flutter
Flutter построен на Event Loop (цикл событий), подобно JavaScript:
Flutter Engine
├── UI Thread (Main Isolate)
│ ├── Event Queue
│ ├── Microtask Queue
│ └── Render Loop (60-120 FPS)
├── I/O Thread
├── GPU Thread
└── Platform Channels (Native)
Main Isolate — это основной поток, где выполняется весь Dart код приложения.
Как это работает
Event Loop обрабатывает события в очередности:
Rendering происходит после обработки события:
Singleton паттерн — единственный экземпляр класса
Singleton — это порождающий паттерн проектирования, который гарантирует, что класс имеет только один экземпляр, и предоставляет глобальную точку доступа к этому экземпляру.
Основная идея
Singleton используется когда:
Простая реализация в Dart
class Database {
// Приватный конструктор
Database._internal();
// Статический экземпляр
static Database? _instance;
// Фабричный конструктор
factory Database() {
// Ленивая инициализация
_instance ??= Database._internal();
return _instance!;
}
void connect() {
print('Database connected');
}
}
По каким критериям выбираю архитектуру
Выбор архитектуры — это не просто техническое решение, а стратегическое. Я смотрю на несколько факторов, чтобы сделать правильный выбор.
1. Размер и сложность проекта
Маленькое приложение (< 10 экранов, < 20k строк кода)
// Подойдёт простая архитектура
// Stateful widgets + Provider + ValueNotifier
class CounterScreen extends StatefulWidget { ... }
class CounterProvider extends ChangeNotifier { ... }
final counterProvider = ChangeNotifierProvider(
(ref) => CounterProvider(),
);
Оверинжиниринг с BLoC здесь не нужен.
Среднее приложение (10-50 экранов, 50-200k строк)
// Подойдёт Provider или Riverpod
// Чистая архитектура без излишеств
final userProvider = FutureProvider((ref) async {
return ref.watch(userRepositoryProvider).getUser();
});
Баланс между простотой и структурированностью.
Большое приложение (> 50 экранов, > 200k строк)
Какие знаешь библиотеки для хранения данных
В Flutter существует множество библиотек для хранения данных на разных уровнях: от простого кеша до полнофункциональных баз данных. Рассмотрю самые популярные и полезные.
1. SharedPreferences — простое хранилище
Для: Простые данные (строки, числа, булевы значения)
import 'package:shared_preferences/shared_preferences.dart';
class UserPreferences {
static const _themeKey = 'theme';
static Future<void> saveTheme(String theme) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setString(_themeKey, theme);
}
static Future<String?> getTheme() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getString(_themeKey);
}
}
Плюсы:
Минусы:
2. Hive — быстрая NoSQL БД
setState в Flutter: Как работает механизм обновления состояния
setState — одна из самых используемых функций в Flutter, но многие разработчики не понимают, как она работает под капотом. Это не просто "пересчитай виджет" — это сложный механизм, связанный с жизненным циклом и сборкой кадров.
1. Что делает setState?
setState() — это метод State класса, который:
class CounterWidget extends StatefulWidget {
@override
State<CounterWidget> createState() => _CounterWidgetState();
}
class _CounterWidgetState extends State<CounterWidget> {
int counter = 0;
void increment() {
setState(() {
counter++; // Меняешь состояние
});
// После блока setState() запускается перестроение
}
@override
Widget build(BuildContext context) {
return Text('Counter: $counter');
}
}