← Назад к вопросам
Что такое реактивное программирование в контексте Flutter?
2.7 Senior🔥 111 комментариев
#Архитектура Flutter#Асинхронность
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI28 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Реактивное программирование в Flutter
Реактивное программирование (Reactive Programming) — это парадигма, основанная на потоках данных (streams) и распространении изменений. В Flutter это реализуется через пакет Rx (RxDart) и встроенные Stream API.
Основные концепции
1. Поток данных (Stream)
Stream — это последовательность событий во времени:
// Простой Stream, который emits значения
Stream<int> numberStream = Stream.fromIterable([1, 2, 3, 4, 5]);
// Слушаем Stream
numberStream.listen((number) {
print('Received: $number');
});
// Output:
// Received: 1
// Received: 2
// Received: 3
// Received: 4
// Received: 5
2. Реактивные переменные (BehaviorSubject, PublishSubject)
import 'package:rxdart/rxdart.dart';
// BehaviorSubject всегда помнит последнее значение
final counterSubject = BehaviorSubject<int>(seedValue: 0);
// Emitting значения
counterSubject.add(1);
counterSubject.add(2);
counterSubject.add(3);
// Слушаем
counterSubject.listen((count) => print('Count: $count'));
// Output:
// Count: 0 (seed value)
// Count: 1
// Count: 2
// Count: 3
BLoC паттерн (Business Logic Component)
BLoC — это архитектурный паттерн, который отделяет business logic от UI через Streams:
// 1. Определяем Events (действия пользователя)
abstract class CounterEvent {}
class IncrementEvent extends CounterEvent {}
class DecrementEvent extends CounterEvent {}
// 2. Определяем States (состояния)
abstract class CounterState {}
class CounterInitial extends CounterState {
final int count = 0;
}
class CounterUpdated extends CounterState {
final int count;
CounterUpdated(this.count);
}
// 3. Создаём BLoC
class CounterBloc {
int _count = 0;
// Inputs
final _eventController = StreamController<CounterEvent>();
Stream<CounterEvent> get events => _eventController.stream;
// Outputs
final _stateController = BehaviorSubject<CounterState>(
seedValue: CounterInitial(),
);
Stream<CounterState> get states => _stateController.stream;
CounterBloc() {
events.listen(_handleEvent);
}
void _handleEvent(CounterEvent event) {
if (event is IncrementEvent) {
_count++;
} else if (event is DecrementEvent) {
_count--;
}
_stateController.add(CounterUpdated(_count));
}
void increment() => _eventController.add(IncrementEvent());
void decrement() => _eventController.add(DecrementEvent());
void dispose() {
_eventController.close();
_stateController.close();
}
}
// 4. Используем в UI
class CounterPage extends StatefulWidget {
@override
State<CounterPage> createState() => _CounterPageState();
}
class _CounterPageState extends State<CounterPage> {
late CounterBloc _bloc;
@override
void initState() {
super.initState();
_bloc = CounterBloc();
}
@override
void dispose() {
_bloc.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Counter')),
body: StreamBuilder<CounterState>(
stream: _bloc.states,
builder: (context, snapshot) {
if (!snapshot.hasData) {
return CircularProgressIndicator();
}
final state = snapshot.data as CounterUpdated;
return Center(
child: Text(
'Count: ${state.count}',
style: TextStyle(fontSize: 32),
),
);
},
),
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton(
onPressed: _bloc.increment,
child: Icon(Icons.add),
),
SizedBox(height: 16),
FloatingActionButton(
onPressed: _bloc.decrement,
child: Icon(Icons.remove),
),
],
),
);
}
}
StreamBuilder vs FutureBuilder
// FutureBuilder — для единовременной операции
FutureBuilder<User>(
future: fetchUser(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data!.name);
}
return CircularProgressIndicator();
},
)
// StreamBuilder — для потока событий
StreamBuilder<int>(
stream: timerStream,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text('Seconds: ${snapshot.data}');
}
return CircularProgressIndicator();
},
)
RxDart операторы (Reactive Extensions)
RxDart предоставляет мощные операторы для трансформации потоков:
import 'package:rxdart/rxdart.dart';
// map — трансформация
Stream<String> numberStream = Stream.fromIterable([1, 2, 3]);
numberStream.map((n) => 'Number: $n').listen(print);
// Output: Number: 1, Number: 2, Number: 3
// where — фильтрация
numberStream.where((n) => n > 1).listen(print);
// Output: 2, 3
// debounceTime — ждёт пока поток затихнет
searchController.stream
.debounceTime(Duration(milliseconds: 500))
.listen((query) => performSearch(query));
// throttleTime — пропускает события, если раньше был недавний
clickStream.throttleTime(Duration(seconds: 1)).listen((_) {
print('Button clicked (but not more than once per second)');
});
// combineLatest — объединяет несколько потоков
Rx.combineLatest2(
emailSubject,
passwordSubject,
(email, password) => email.isNotEmpty && password.isNotEmpty,
).listen((isValid) {
setState(() => isButtonEnabled = isValid);
});
// switchMap — переключение между потоками
searchSubject.switchMap((query) =>
searchAPI(query)
).listen((results) => setState(() => this.results = results));
Практический пример: поле поиска
class SearchBloc {
final _searchSubject = BehaviorSubject<String>(seedValue: '');
final _resultsSubject = BehaviorSubject<List<Item>>(seedValue: []);
Stream<List<Item>> get results => _resultsSubject.stream;
SearchBloc() {
_searchSubject
.debounceTime(Duration(milliseconds: 500))
.switchMap((query) => _search(query))
.listen(_resultsSubject.add);
}
void search(String query) => _searchSubject.add(query);
Stream<List<Item>> _search(String query) async* {
if (query.isEmpty) {
yield [];
return;
}
try {
final results = await API.search(query);
yield results;
} catch (e) {
yield [];
}
}
void dispose() {
_searchSubject.close();
_resultsSubject.close();
}
}
// В UI
class SearchPage extends StatefulWidget {
@override
State<SearchPage> createState() => _SearchPageState();
}
class _SearchPageState extends State<SearchPage> {
late SearchBloc _bloc;
@override
void initState() {
super.initState();
_bloc = SearchBloc();
}
@override
void dispose() {
_bloc.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
TextField(
onChanged: _bloc.search,
decoration: InputDecoration(hintText: 'Search...'),
),
Expanded(
child: StreamBuilder<List<Item>>(
stream: _bloc.results,
builder: (context, snapshot) {
if (!snapshot.hasData) {
return SizedBox.shrink();
}
final items = snapshot.data!;
return ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return ListTile(title: Text(items[index].name));
},
);
},
),
),
],
),
);
}
}
Преимущества реактивного подхода
- Разделение логики и UI — BLoC содержит всю бизнес-логику
- Тестируемость — легко тестировать Streams без UI
- Повторное использование — BLoC можно применить в разных UI
- Обработка асинхронности — Stream и Future встроены в язык
- Управление состоянием — чистое и предсказуемое
Лучшие практики
- ✅ Используй BLoC для сложной логики
- ✅ Разделяй Events, States и BLoC в отдельные файлы
- ✅ Всегда dispose Streams и Controllers
- ✅ Используй RxDart операторы для трансформации
- ✅ Тестируй BLoC отдельно от UI
- ❌ Не выводи Streams в UI — оборачивай в BLoC
- ❌ Не забывай про memory leaks от незакрытых Streams
Реактивное программирование — это не обязательно, но оно делает код более организованным и масштабируемым, особенно в крупных приложениях.