FlutterBLoCRiverpodState Management

Flutter BLoC vs Riverpod у 2025: практичний фреймворк для вибору

12 квітня 2026 р.

Codevia Engineering

Після запуску кількох Flutter-застосунків у продакшн з BLoC і Riverpod — ось практичний фреймворк для вибору між ними на основі реальних компромісів, з якими ми стикались у роботі.

Коротка відповідь, яку ніхто не хоче чути

Жодне з рішень не є universally кращим. Відповідь залежить від команди, складності проєкту та того, наскільки серйозно ви ставитеся до тестування. Якщо потрібен один рядок: BLoC — для складної бізнес-логіки з кількома інженерами, Riverpod — для менших застосунків або команд, які обирають менше церемоній.

Решта статті пояснює чому.

Що таке BLoC насправді

BLoC — Business Logic Component. Це патерн управління станом, побудований навколо стрімів і розділення бізнес-логіки від UI. Пакет flutter_bloc забезпечує реалізацію.

Основна ментальна модель: UI відправляє Events, BLoC обробляє їх і емітує States, а віджети реагують на зміни стану.

// Event
abstract class AuthEvent {}
class LoginRequested extends AuthEvent {
  final String email;
  final String password;
  LoginRequested({required this.email, required this.password});
}

// State
abstract class AuthState {}
class AuthInitial extends AuthState {}
class AuthLoading extends AuthState {}
class AuthAuthenticated extends AuthState {
  final User user;
  AuthAuthenticated(this.user);
}
class AuthError extends AuthState {
  final String message;
  AuthError(this.message);
}

Переваги BLoC:

  • Явна event-driven архітектура. Кожна зміна стану має іменовану причину.
  • Відмінна тестованість. Можна юніт-тестувати BLoC без торкання UI.
  • Чітке розділення відповідальностей. UI не знає нічого про те, як змінюється стан.
  • Пакет flutter_bloc_test спрощує тестування стрімів.

Недоліки BLoC:

  • Значний boilerplate, особливо для простого стану.
  • Event-класи збільшують кількість файлів і когнітивне навантаження.
  • Cubit існує для зменшення boilerplate, але команди часто непослідовно поєднують BLoC і Cubit.

Що таке Riverpod насправді

Riverpod — повне переосмислення Provider. Він compile-safe, підтримує генерацію коду (riverpod_generator) і використовує концепцію Providers — типізованих реактивних сховищ стану.

// Простий async provider для завантаження профілю
@riverpod
Future<UserProfile> userProfile(UserProfileRef ref, String userId) async {
  final repository = ref.watch(userRepositoryProvider);
  return repository.fetchProfile(userId);
}

// У віджеті
class ProfilePage extends ConsumerWidget {
  final String userId;
  const ProfilePage({required this.userId, super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final profile = ref.watch(userProfileProvider(userId));

    return profile.when(
      data: (user) => ProfileContent(user: user),
      loading: () => const CircularProgressIndicator(),
      error: (e, _) => ErrorView(message: e.toString()),
    );
  }
}

Переваги Riverpod:

  • Менше boilerplate для типових патернів (завантажити дані, трансформувати, відобразити).
  • Генерація коду через анотацію @riverpod бере на себе рутинні частини.
  • Providers є composable — один provider може залежати від іншого.
  • Кращий support для обчислюваного/похідного стану без явних маперів.
  • ref.invalidate() і ref.refresh() дають явний контроль над кешуванням.

Реальні виробничі сценарії

Сценарій 1: Форма з валідацією та відправкою

Тут BLoC сяє. Є явні events (FieldChanged, StepAdvanced, FormSubmitted) і явні states (FormEditing, FormSubmitting, FormSuccess, FormError).

Переможець: BLoC

Сценарій 2: Завантаження даних з кешуванням

Профіль користувача, який завантажується раз і доступний на кількох екранах. З Riverpod — один provider, будь-який екран викликає ref.watch. Кешування автоматичне. Інвалідація при logout — ref.invalidate(userProfileProvider).

Переможець: Riverpod

Чинник команди

Це найнедооцінена змінна.

Якщо у команді 3+ Flutter-розробників і потрібні чіткі межі відповідальності — детальність BLoC стає перевагою. Кожен файл іменований, кожен event явний. Нові розробники можуть читати BLoC і розуміти, що робить фіча, не запускаючи застосунок.

Якщо ви соло або невелика 2-особова команда і рухаєтесь швидко — Riverpod знімає достатньо церемоній, щоб суттєво прискорити розробку.

Наш поточний default у Codevia

Ми використовуємо BLoC (варіант Cubit) для складних, насичених бізнес-логікою фіч і Riverpod для простого завантаження даних та обчислюваного стану.

Ми дотримуємось суворого правила: один BlocConsumer або BlocListener на кубіт на сторінку. Це запобігає антипатерну, коли один кубіт має listeners розкидані по вкладених віджетах.

Фреймворк прийняття рішень

  1. Чи є у фічі складна бізнес-логіка з кількома джерелами подій? → BLoC
  2. Чи є стан переважно результатом async-операції? → Riverpod
  3. Велика команда (4+ людей) з потребою чіткого ownership? → BLoC
  4. Невелика команда або MVP? → Riverpod
  5. Є вже один варіант у кодобазі? → Використовуйте його послідовно

Висновок

Не обирайте на основі зірочок на GitHub чи виступів на конференціях. Обирайте на основі:

  • Розміру та досвіду команди
  • Складності фічей і щільності подій
  • Наскільки ви цінуєте явну причинність проти лаконічного коду

Якщо сумніваєтесь — починайте з Riverpod. Якщо стикнетесь з труднощами у відстеженні переходів стану — вводьте BLoC для конкретних фіч. Можна поєднувати на рівні фіч — але не на рівні віджетів.