การจัดการ State ใน Flutter: Riverpod vs BLoC - คู่มือเปรียบเทียบฉบับสมบูรณ์
การเปรียบเทียบเชิงลึกระหว่าง Riverpod 3.x และ BLoC 9.x สำหรับการจัดการ state ใน Flutter สถาปัตยกรรม automatic retry ความสามารถในการทดสอบ และกรณีการใช้งานเพื่อเลือกโซลูชันที่ดีที่สุด

การจัดการ state ถือเป็นความท้าทายหลักในการพัฒนา Flutter Riverpod และ BLoC ครองอีโคซิสเต็ม โดยแต่ละตัวนำเสนอปรัชญาที่แตกต่างกัน คู่มือนี้เปรียบเทียบทั้งสองโซลูชันผ่านการนำไปใช้จริงเพื่อช่วยตัดสินใจตามความต้องการของโครงการ
คู่มือนี้คาดว่าผู้อ่านมีความคุ้นเคยกับ Flutter และพื้นฐานการจัดการ state ตัวอย่างใช้ Riverpod 3.x และ flutter_bloc 9.x ซึ่งเป็นเวอร์ชันเสถียรในปัจจุบัน ปี 2026
ปรัชญาหลักของทั้งสองแนวทาง
Riverpod และ BLoC แก้ปัญหาเดียวกันด้วยกระบวนทัศน์ที่ตรงข้ามกัน การเข้าใจความแตกต่างเชิงแนวคิดเหล่านี้ช่วยให้เลือกเครื่องมือที่เหมาะสมในแต่ละบริบท
Riverpod ใช้แนวทางแบบประกาศและตอบสนอง provider กำหนดแหล่งข้อมูลที่ widget เฝ้าสังเกต framework จัดการวงจรชีวิต การแคช และการพึ่งพาระหว่าง provider โดยอัตโนมัติ เวอร์ชัน 3.0 นำมาซึ่งการปรับปรุงที่สำคัญ: automatic retry สำหรับ provider ที่ล้มเหลว, pause/resume listener ตามการมองเห็นของ widget และการรองรับ offline persistence แบบทดลอง
BLoC (Business Logic Component) บังคับใช้สถาปัตยกรรมที่เคร่งครัดแบบ event-driven คอมโพเนนต์ปล่อยเหตุการณ์ออกมา Bloc ประมวลผลและสร้าง state ใหม่ การแยกแยะอย่างชัดเจนนี้ช่วยให้ติดตามการไหลของข้อมูลได้สะดวก เวอร์ชัน 9.x เปิดตัว EmittableStateStreamableSource เพื่อความยืดหยุ่นในการทดสอบที่ดีขึ้น
// Riverpod 3.x: Notifier replaces StateNotifier as the standard pattern
class Counter extends _$Counter {
int build() => 0;
void increment() => state++;
}
// Usage in a widget
class CounterWidget extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
// Reactive read: automatic rebuild if value changes
final count = ref.watch(counterProvider);
return Text('$count');
}
}// BLoC 9.x: explicit events/states separation
sealed class CounterEvent {}
final class IncrementPressed extends CounterEvent {}
class CounterBloc extends Bloc<CounterEvent, int> {
CounterBloc() : super(0) {
// Each event has its dedicated handler
on<IncrementPressed>((event, emit) => emit(state + 1));
}
}
// Usage in a widget
class CounterWidget extends StatelessWidget {
Widget build(BuildContext context) {
return BlocBuilder<CounterBloc, int>(
builder: (context, count) => Text('$count'),
);
}
}การเลือกระหว่างสองแนวทางนี้ขึ้นอยู่กับความชอบของทีมและข้อจำกัดของโครงการ สำหรับภาพรวมของตัวเลือก state management รวมถึง GetX โปรดดู การเปรียบเทียบ state management Flutter ฉบับสมบูรณ์สำหรับปี 2026
การตั้งค่าเริ่มต้น
การตั้งค่าเริ่มต้นเผยให้เห็นความแตกต่างด้านความสะดวกในการใช้งานระหว่างสองโซลูชัน Riverpod ให้ความสำคัญกับความเรียบง่าย BLoC มอบโครงสร้างที่มากกว่า
การติดตั้ง Riverpod
Riverpod ต้องการเพียงแพ็กเกจเดียวและ wrapper ที่รากของแอปพลิเคชัน การสร้างโค้ดด้วย riverpod_generator เป็นแนวทางที่แนะนำในเวอร์ชัน 3.x
// Riverpod configuration: single wrapper at root
import 'package:flutter_riverpod/flutter_riverpod.dart';
void main() {
runApp(
// ProviderScope wraps the entire application
const ProviderScope(
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
home: HomeScreen(),
);
}
}การติดตั้ง BLoC
BLoC ต้องการแพ็กเกจหลายตัวและการตั้งค่าที่ละเอียดกว่า โดยใช้ BlocProvider สำหรับแต่ละ Bloc ที่ใช้
// BLoC configuration: explicit providers for each Bloc
import 'package:flutter_bloc/flutter_bloc.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
// MultiBlocProvider for multiple Blocs
return MultiBlocProvider(
providers: [
BlocProvider(create: (_) => AuthBloc()),
BlocProvider(create: (_) => ThemeBloc()),
],
child: MaterialApp(
home: HomeScreen(),
),
);
}
}การตั้งค่า BLoC ต้องการโค้ดเริ่มต้นมากกว่า แต่ทำให้การพึ่งพาชัดเจนตั้งแต่แรกเริ่ม
การจัดการ State อย่างง่าย: Counter และ Toggle
กรณีง่ายๆ แสดงให้เห็นความสะดวกในการใช้งานประจำวันของแต่ละโซลูชัน Riverpod โดดเด่นด้านความกระชับ BLoC คงโครงสร้างแบบ event-driven ของตน
Counter ด้วย Riverpod (รูปแบบ Notifier)
Riverpod 3.x ยกเลิก StateProvider และ StateNotifierProvider แทนที่ด้วย API Notifier ที่รวมเป็นหนึ่ง รูปแบบใหม่นี้สะอาดกว่าและทำงานร่วมกับ code generation ได้อย่างราบรื่น
// Notifier pattern: the standard in Riverpod 3.x
class Counter extends _$Counter {
int build() => 0;
void increment() => state++;
void decrement() => state--;
void reset() => state = 0;
}
class CounterScreen extends ConsumerWidget {
const CounterScreen({super.key});
Widget build(BuildContext context, WidgetRef ref) {
// watch for reactive value
final count = ref.watch(counterProvider);
return Scaffold(
body: Center(child: Text('Count: $count')),
floatingActionButton: FloatingActionButton(
// read for actions (no rebuild)
onPressed: () => ref.read(counterProvider.notifier).increment(),
child: const Icon(Icons.add),
),
);
}
}Counter ด้วย BLoC
// Typed events for each possible action
sealed class CounterEvent {}
final class CounterIncremented extends CounterEvent {}
final class CounterDecremented extends CounterEvent {}
final class CounterReset extends CounterEvent {}
// Bloc with handlers for each event
class CounterBloc extends Bloc<CounterEvent, int> {
CounterBloc() : super(0) {
on<CounterIncremented>((event, emit) => emit(state + 1));
on<CounterDecremented>((event, emit) => emit(state - 1));
on<CounterReset>((event, emit) => emit(0));
}
}
class CounterScreen extends StatelessWidget {
const CounterScreen({super.key});
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: BlocBuilder<CounterBloc, int>(
builder: (context, count) => Text('Count: $count'),
),
),
floatingActionButton: FloatingActionButton(
// Event dispatch to modify state
onPressed: () => context.read<CounterBloc>().add(CounterIncremented()),
child: const Icon(Icons.add),
),
);
}
}สำหรับกรณีง่ายๆ Riverpod ลดโค้ด boilerplate ได้อย่างมาก BLoC จะมีความหมายเมื่อ logic เริ่มซับซ้อนหรือเมื่อการติดตาม event มีความสำคัญ
StateProvider และ StateNotifierProvider ยังคงใช้งานได้แต่ต้อง import จาก flutter_riverpod/legacy.dart สำหรับโค้ดใหม่ ให้ใช้ Notifier หรือ AsyncNotifier
การจัดการ State แบบ Asynchronous: การเรียก API
การดำเนินการแบบ asynchronous เผยให้เห็นพลังของแต่ละโซลูชัน การจัดการ state ของ loading, error และ data เป็นความท้าทายสำคัญ Riverpod 3.x เปิดตัว automatic retry พร้อม exponential backoff สำหรับ provider ที่ล้มเหลว
ข้อมูล Asynchronous ด้วย Riverpod
// AsyncNotifier: automatic loading/error/data management with retry
class Users extends _$Users {
Future<List<User>> build() async {
// autoDispose releases resources when provider is no longer used
// Automatic retry on failure (exponential backoff: 200ms to 6.4s)
final repository = ref.watch(userRepositoryProvider);
return repository.fetchUsers();
}
Future<void> refresh() async {
state = const AsyncLoading();
state = await AsyncValue.guard(() => build());
}
}
class UsersScreen extends ConsumerWidget {
const UsersScreen({super.key});
Widget build(BuildContext context, WidgetRef ref) {
final usersAsync = ref.watch(usersProvider);
// when handles all 3 possible states
return usersAsync.when(
loading: () => const Center(child: CircularProgressIndicator()),
error: (error, stack) => Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Error: $error'),
ElevatedButton(
// invalidate forces reload
onPressed: () => ref.invalidate(usersProvider),
child: const Text('Retry'),
),
],
),
),
data: (users) => ListView.builder(
itemCount: users.length,
itemBuilder: (context, index) => UserTile(user: users[index]),
),
);
}
}ข้อมูล Asynchronous ด้วย BLoC
// Explicit states for each loading phase
sealed class UsersState {}
final class UsersInitial extends UsersState {}
final class UsersLoading extends UsersState {}
final class UsersLoaded extends UsersState {
final List<User> users;
UsersLoaded(this.users);
}
final class UsersError extends UsersState {
final String message;
UsersError(this.message);
}
// Events to trigger actions
sealed class UsersEvent {}
final class UsersFetchRequested extends UsersEvent {}
final class UsersRefreshRequested extends UsersEvent {}
class UsersBloc extends Bloc<UsersEvent, UsersState> {
final UserRepository _repository;
UsersBloc(this._repository) : super(UsersInitial()) {
on<UsersFetchRequested>(_onFetchRequested);
on<UsersRefreshRequested>(_onRefreshRequested);
}
Future<void> _onFetchRequested(
UsersFetchRequested event,
Emitter<UsersState> emit,
) async {
emit(UsersLoading());
try {
final users = await _repository.fetchUsers();
emit(UsersLoaded(users));
} catch (e) {
emit(UsersError(e.toString()));
}
}
Future<void> _onRefreshRequested(
UsersRefreshRequested event,
Emitter<UsersState> emit,
) async {
// Keep current state during refresh
final currentState = state;
try {
final users = await _repository.fetchUsers();
emit(UsersLoaded(users));
} catch (e) {
// Restore previous state on error
if (currentState is UsersLoaded) {
emit(currentState);
} else {
emit(UsersError(e.toString()));
}
}
}
}// Widget with pattern matching on states
class UsersScreen extends StatelessWidget {
const UsersScreen({super.key});
Widget build(BuildContext context) {
return BlocBuilder<UsersBloc, UsersState>(
builder: (context, state) {
return switch (state) {
UsersInitial() => Center(
child: ElevatedButton(
onPressed: () => context.read<UsersBloc>().add(UsersFetchRequested()),
child: const Text('Load'),
),
),
UsersLoading() => const Center(child: CircularProgressIndicator()),
UsersError(:final message) => Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Error: $message'),
ElevatedButton(
onPressed: () => context
.read<UsersBloc>()
.add(UsersFetchRequested()),
child: const Text('Retry'),
),
],
),
),
UsersLoaded(:final users) => ListView.builder(
itemCount: users.length,
itemBuilder: (context, index) => UserTile(user: users[index]),
),
};
},
);
}
}BLoC ให้การควบคุมแบบละเอียดในแต่ละการเปลี่ยน state Riverpod ทำให้เป็นอัตโนมัติมากขึ้นผ่าน AsyncValue และ logic retry ในตัว
พร้อมที่จะพิชิตการสัมภาษณ์ Flutter แล้วหรือยังครับ?
ฝึกฝนด้วยตัวจำลองแบบโต้ตอบ, flashcards และแบบทดสอบเทคนิคครับ
การพึ่งพาระหว่าง State: การประกอบและการฉีด
แอปพลิเคชันจริงเกี่ยวข้องกับ state ที่พึ่งพาซึ่งกันและกัน การจัดการการพึ่งพาเหล่านี้ทำให้สองแนวทางแตกต่างกันอย่างชัดเจน
การประกอบด้วย Riverpod
// Base provider: configuration
ApiClient apiClient(Ref ref) {
final baseUrl = ref.watch(environmentProvider).apiUrl;
return ApiClient(baseUrl: baseUrl);
}
// Dependent provider: repository
ProductRepository productRepository(Ref ref) {
// Automatic client injection
final client = ref.watch(apiClientProvider);
return ProductRepository(client);
}
// Provider with parameter: product by ID
Future<Product> product(Ref ref, String productId) async {
final repository = ref.watch(productRepositoryProvider);
return repository.getProduct(productId);
}
// Derived provider: filtered products
List<Product> filteredProducts(Ref ref) {
final products = ref.watch(productsProvider).valueOrNull ?? [];
final filter = ref.watch(productFilterProvider);
return products.where((p) => p.category == filter.category).toList();
}
// Usage with parameter
class ProductDetailScreen extends ConsumerWidget {
final String productId;
const ProductDetailScreen({super.key, required this.productId});
Widget build(BuildContext context, WidgetRef ref) {
// family allows passing parameters
final productAsync = ref.watch(productProvider(productId));
return productAsync.when(
loading: () => const ProductSkeleton(),
error: (e, _) => ErrorWidget(error: e),
data: (product) => ProductDetails(product: product),
);
}
}การประกอบด้วย BLoC
// Repository injected into the Bloc
class ProductBloc extends Bloc<ProductEvent, ProductState> {
final ProductRepository _repository;
final CartBloc _cartBloc;
late final StreamSubscription _cartSubscription;
ProductBloc({
required ProductRepository repository,
required CartBloc cartBloc,
}) : _repository = repository,
_cartBloc = cartBloc,
super(ProductInitial()) {
on<ProductFetchRequested>(_onFetchRequested);
on<ProductAddedToCart>(_onAddedToCart);
// Listen to cart changes
_cartSubscription = _cartBloc.stream.listen((cartState) {
// React to cart changes
if (cartState is CartUpdated) {
add(ProductCartSyncRequested(cartState.items));
}
});
}
Future<void> _onFetchRequested(
ProductFetchRequested event,
Emitter<ProductState> emit,
) async {
emit(ProductLoading());
try {
final product = await _repository.getProduct(event.productId);
// Check if product is in cart
final isInCart = _cartBloc.state.contains(product.id);
emit(ProductLoaded(product, isInCart: isInCart));
} catch (e) {
emit(ProductError(e.toString()));
}
}
Future<void> close() {
_cartSubscription.cancel();
return super.close();
}
}
// Configuration with dependency injection
class ProductsPage extends StatelessWidget {
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => ProductBloc(
repository: context.read<ProductRepository>(),
cartBloc: context.read<CartBloc>(),
)..add(ProductFetchRequested()),
child: const ProductsView(),
);
}
}Riverpod จัดการการพึ่งพาแบบประกาศ BLoC ต้องจัดการ subscription ระหว่าง Bloc ด้วยตนเอง
ความสามารถในการทดสอบและการ Mock
การทดสอบเป็นเกณฑ์ตัดสินสำคัญสำหรับโครงการระดับมืออาชีพ โซลูชันทั้งสองโดดเด่นในด้านนี้ด้วยแนวทางที่แตกต่างกัน ทักษะการทดสอบมักถูกประเมินในการสัมภาษณ์ Flutter
การทดสอบด้วย Riverpod
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
class MockUserRepository extends Mock implements UserRepository {}
void main() {
group('UserProvider Tests', () {
late MockUserRepository mockRepository;
setUp(() {
mockRepository = MockUserRepository();
});
test('returns users from repository', () async {
// Arrange
final expectedUsers = [User(id: '1', name: 'Test')];
when(() => mockRepository.fetchUsers())
.thenAnswer((_) async => expectedUsers);
// ProviderContainer.test auto-disposes after test
final container = ProviderContainer.test(
overrides: [
userRepositoryProvider.overrideWithValue(mockRepository),
],
);
// Act
final users = await container.read(usersProvider.future);
// Assert
expect(users, expectedUsers);
verify(() => mockRepository.fetchUsers()).called(1);
});
test('handles repository errors', () async {
when(() => mockRepository.fetchUsers())
.thenThrow(Exception('Network error'));
final container = ProviderContainer.test(
overrides: [
userRepositoryProvider.overrideWithValue(mockRepository),
],
);
expect(
() => container.read(usersProvider.future),
throwsException,
);
});
});
}การทดสอบด้วย BLoC
import 'package:bloc_test/bloc_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
class MockUserRepository extends Mock implements UserRepository {}
void main() {
group('UsersBloc Tests', () {
late MockUserRepository mockRepository;
setUp(() {
mockRepository = MockUserRepository();
});
// blocTest simplifies state sequence testing
blocTest<UsersBloc, UsersState>(
'emits [Loading, Loaded] when fetch succeeds',
build: () {
when(() => mockRepository.fetchUsers())
.thenAnswer((_) async => [User(id: '1', name: 'Test')]);
return UsersBloc(mockRepository);
},
act: (bloc) => bloc.add(UsersFetchRequested()),
expect: () => [
isA<UsersLoading>(),
isA<UsersLoaded>().having(
(s) => s.users.length,
'users count',
1,
),
],
);
blocTest<UsersBloc, UsersState>(
'emits [Loading, Error] when fetch fails',
build: () {
when(() => mockRepository.fetchUsers())
.thenThrow(Exception('Network error'));
return UsersBloc(mockRepository);
},
act: (bloc) => bloc.add(UsersFetchRequested()),
expect: () => [
isA<UsersLoading>(),
isA<UsersError>(),
],
);
});
}แพ็กเกจ bloc_test ให้ syntax เฉพาะสำหรับการทดสอบลำดับ state Riverpod 3.x เพิ่ม ProviderContainer.test() สำหรับ dispose อัตโนมัติและ NotifierProvider.overrideWithBuild() สำหรับ mock เฉพาะ method build
การทดสอบเฉพาะกรณีปกติไม่เพียงพอ การทดสอบต้องครอบคลุมข้อผิดพลาดทางเครือข่าย timeout state ขอบเขต และการเปลี่ยน state ที่ไม่คาดคิด สำหรับแนวทางปฏิบัติการทดสอบที่ครอบคลุม ดูคู่มือการทดสอบ Flutter
ประสิทธิภาพและการเพิ่มประสิทธิภาพ Rebuild
ประสิทธิภาพส่งผลโดยตรงต่อประสบการณ์ผู้ใช้ โซลูชันทั้งสองให้กลไกการเพิ่มประสิทธิภาพที่แตกต่างกัน
การเพิ่มประสิทธิภาพด้วย Riverpod
// select to rebuild only if targeted value changes
class UserNameWidget extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
// Rebuilds only if user.name changes
final name = ref.watch(userProvider.select((user) => user.name));
return Text(name);
}
}
// Provider with automatic caching
ExpensiveResult expensiveComputation(Ref ref) {
final input = ref.watch(inputProvider);
// Computation automatically cached
return performExpensiveComputation(input);
}
// keepAlive with timer for temporary caching
Future<List<Product>> searchResults(Ref ref, String query) async {
// Temporary keepAlive during typing
final link = ref.keepAlive();
// Timer to release after inactivity
final timer = Timer(const Duration(seconds: 30), link.close);
ref.onDispose(timer.cancel);
return searchProducts(query);
}การเพิ่มประสิทธิภาพด้วย BLoC
// buildWhen limits rebuilds conditionally
class UserNameWidget extends StatelessWidget {
Widget build(BuildContext context) {
return BlocBuilder<UserBloc, UserState>(
// Rebuilds only if name changes
buildWhen: (previous, current) {
if (previous is UserLoaded && current is UserLoaded) {
return previous.user.name != current.user.name;
}
return true;
},
builder: (context, state) {
if (state is UserLoaded) {
return Text(state.user.name);
}
return const SizedBox.shrink();
},
);
}
}
// BlocSelector to extract a specific value
class UserAvatarWidget extends StatelessWidget {
Widget build(BuildContext context) {
return BlocSelector<UserBloc, UserState, String?>(
// Select only the avatar URL
selector: (state) => state is UserLoaded ? state.user.avatarUrl : null,
builder: (context, avatarUrl) {
if (avatarUrl == null) return const DefaultAvatar();
return NetworkImage(avatarUrl);
},
);
}
}โซลูชันทั้งสองให้การเพิ่มประสิทธิภาพแบบละเอียด Riverpod ด้วย select BLoC ด้วย buildWhen และ BlocSelector สำหรับเทคนิคประสิทธิภาพขั้นสูง ดูคู่มือเพิ่มประสิทธิภาพ Flutter
กรณีศึกษาเชิงปฏิบัติ: ระบบยืนยันตัวตนแบบครบถ้วน
ระบบยืนยันตัวตนแสดงรูปแบบจริงของแต่ละโซลูชัน กรณีนี้รวม state ที่คงอยู่ การเรียก API และการนำทาง
การยืนยันตัวตนด้วย Riverpod
// Authentication state with sealed class
sealed class AuthState {
const AuthState();
}
final class AuthInitial extends AuthState {
const AuthInitial();
}
final class AuthLoading extends AuthState {
const AuthLoading();
}
final class AuthAuthenticated extends AuthState {
final User user;
const AuthAuthenticated(this.user);
}
final class AuthUnauthenticated extends AuthState {
final String? error;
const AuthUnauthenticated([this.error]);
}
// Notifier to manage auth state
class Auth extends _$Auth {
Future<AuthState> build() async {
final storage = ref.watch(secureStorageProvider);
final repository = ref.watch(authRepositoryProvider);
final token = await storage.getToken();
if (token != null) {
try {
final user = await repository.getCurrentUser(token);
return AuthAuthenticated(user);
} catch (_) {
await storage.deleteToken();
return const AuthUnauthenticated();
}
}
return const AuthUnauthenticated();
}
Future<void> login(String email, String password) async {
state = const AsyncLoading();
final repository = ref.read(authRepositoryProvider);
final storage = ref.read(secureStorageProvider);
try {
final result = await repository.login(email, password);
await storage.saveToken(result.token);
state = AsyncData(AuthAuthenticated(result.user));
} catch (e) {
state = AsyncData(AuthUnauthenticated(e.toString()));
}
}
Future<void> logout() async {
final storage = ref.read(secureStorageProvider);
await storage.deleteToken();
state = const AsyncData(AuthUnauthenticated());
}
}
// Redirect based on auth state
GoRouter router(Ref ref) {
final authState = ref.watch(authProvider);
return GoRouter(
redirect: (context, state) {
final isAuth = authState.valueOrNull is AuthAuthenticated;
final isAuthRoute = state.matchedLocation.startsWith('/auth');
if (!isAuth && !isAuthRoute) return '/auth/login';
if (isAuth && isAuthRoute) return '/home';
return null;
},
routes: [...],
);
}การยืนยันตัวตนด้วย BLoC
// Exhaustive states for authentication
sealed class AuthState {
const AuthState();
}
final class AuthInitial extends AuthState {
const AuthInitial();
}
final class AuthCheckInProgress extends AuthState {
const AuthCheckInProgress();
}
final class AuthLoginInProgress extends AuthState {
const AuthLoginInProgress();
}
final class AuthSuccess extends AuthState {
final User user;
const AuthSuccess(this.user);
}
final class AuthFailure extends AuthState {
final String error;
const AuthFailure(this.error);
}
final class AuthLoggedOut extends AuthState {
const AuthLoggedOut();
}
// Authentication events
sealed class AuthEvent {
const AuthEvent();
}
final class AuthCheckRequested extends AuthEvent {
const AuthCheckRequested();
}
final class AuthLoginSubmitted extends AuthEvent {
final String email;
final String password;
const AuthLoginSubmitted(this.email, this.password);
}
final class AuthLogoutRequested extends AuthEvent {
const AuthLogoutRequested();
}
class AuthBloc extends Bloc<AuthEvent, AuthState> {
final AuthRepository _repository;
final SecureStorage _storage;
AuthBloc({
required AuthRepository repository,
required SecureStorage storage,
}) : _repository = repository,
_storage = storage,
super(const AuthInitial()) {
on<AuthCheckRequested>(_onCheckRequested);
on<AuthLoginSubmitted>(_onLoginSubmitted);
on<AuthLogoutRequested>(_onLogoutRequested);
}
Future<void> _onCheckRequested(
AuthCheckRequested event,
Emitter<AuthState> emit,
) async {
emit(const AuthCheckInProgress());
final token = await _storage.getToken();
if (token == null) {
emit(const AuthLoggedOut());
return;
}
try {
final user = await _repository.getCurrentUser(token);
emit(AuthSuccess(user));
} catch (_) {
await _storage.deleteToken();
emit(const AuthLoggedOut());
}
}
Future<void> _onLoginSubmitted(
AuthLoginSubmitted event,
Emitter<AuthState> emit,
) async {
emit(const AuthLoginInProgress());
try {
final result = await _repository.login(event.email, event.password);
await _storage.saveToken(result.token);
emit(AuthSuccess(result.user));
} catch (e) {
emit(AuthFailure(e.toString()));
}
}
Future<void> _onLogoutRequested(
AuthLogoutRequested event,
Emitter<AuthState> emit,
) async {
await _storage.deleteToken();
emit(const AuthLoggedOut());
}
}การนำไปใช้ทั้งสองครอบคลุมฟังก์ชันการทำงานเดียวกันด้วยสไตล์ที่แตกต่างกัน BLoC ทำให้แต่ละการเปลี่ยนแปลงชัดเจน Riverpod ทำให้ syntax เรียบง่าย
ตารางเปรียบเทียบสรุป
| เกณฑ์ | Riverpod 3.x | BLoC 9.x |
|---|---|---|
| ความยากในการเรียนรู้ | ปานกลาง | ชันกว่า |
| Boilerplate | น้อยที่สุด (พร้อม codegen) | มาก |
| Type Safety | ยอดเยี่ยม | ยอดเยี่ยม |
| ความสามารถในการทดสอบ | ยอดเยี่ยม | ยอดเยี่ยม |
| Automatic Retry | มีในตัว | ต้องทำเอง |
| Offline Persistence | รองรับแบบทดลอง | แพ็กเกจภายนอก |
| การติดตามได้ | ผ่าน DevTools | Events/States ชัดเจน |
| การประกอบ | อัตโนมัติ | ด้วยตนเอง |
| การสร้างโค้ด | แนะนำ | ไม่จำเป็น |
| ขนาดทีม | ยืดหยุ่น | ทีมขนาดใหญ่ |
คำแนะนำตามบริบท
การเลือกระหว่าง Riverpod และ BLoC ขึ้นอยู่กับปัจจัยทางบริบทหลายประการ
เลือก Riverpod เมื่อ:
- ทีมให้ความสำคัญกับความกระชับและประสิทธิภาพในการพัฒนา
- โครงการต้องการการประกอบ state ที่ยืดหยุ่น
- นักพัฒนามาจาก React หรือ framework reactive อื่นๆ
- Automatic retry และการกู้คืนจากข้อผิดพลาดมีความสำคัญ
- Offline persistence เป็นข้อกำหนด
เลือก BLoC เมื่อ:
- ทีมชื่นชอบรูปแบบที่เคร่งครัดและคาดเดาได้
- โครงการต้องการการติดตาม event ที่สมบูรณ์สำหรับการตรวจสอบ
- นักพัฒนา junior ได้ประโยชน์จากสถาปัตยกรรมที่ถูกบังคับ
- การ debug ต้องการประวัติการเปลี่ยนแปลงโดยละเอียด
- อุตสาหกรรมต้องการ audit trail (fintech, healthcare)
แหล่งข้อมูล
- Riverpod 3.0 What's New: automatic retry, pause/resume, offline persistence
- Riverpod 3.0 Migration Guide: Notifier แทนที่ StateNotifier
- flutter_bloc 9.0.0 Changelog: EmittableStateStreamableSource, การปรับปรุงการทดสอบ
- BLoC Documentation: pattern อย่างเป็นทางการและ best practice
สิ่งที่ควรจดจำเกี่ยวกับ Riverpod vs BLoC ในปี 2026
Riverpod และ BLoC ตอบสนองความต้องการการจัดการ state ใน Flutter ได้อย่างมีประสิทธิภาพ Riverpod 3.x โดดเด่นด้านความสะดวก การจัดการข้อผิดพลาดอัตโนมัติ และการประกอบที่ยืดหยุ่น BLoC 9.x นำในด้านโครงสร้าง ความสามารถในการคาดเดา และการติดตามระดับองค์กร ทั้งสองโซลูชันให้ความสามารถในการทดสอบที่ยอดเยี่ยมและประสิทธิภาพที่เหมาะสม
รายการตรวจสอบการตัดสินใจ
- ประเมินขนาดและประสบการณ์ของทีม
- พิจารณาความซับซ้อนของการไหลของข้อมูล
- วิเคราะห์ความต้องการการติดตามและ debug
- ทดสอบทั้งสองโซลูชันบนต้นแบบ
- ตรวจสอบความสอดคล้องกับสถาปัตยกรรมที่มีอยู่
เริ่มฝึกซ้อมเลย!
ทดสอบความรู้ของคุณด้วยตัวจำลองสัมภาษณ์และแบบทดสอบเทคนิคครับ
ทางเลือกที่ดีที่สุดยังคงเป็นโซลูชันที่ทีมเชี่ยวชาญและบำรุงรักษาได้อย่างมีประสิทธิภาพ ความสอดคล้องในการประยุกต์ใช้สำคัญกว่าการเลือกโซลูชันเอง สำหรับ pattern เฉพาะ Dart เช่น isolate และ concurrency ดูคู่มือ isolate และ concurrency ของ Dart
คุณหาบั๊กใน Flutter เจอไหม
โค้ดจริงหนึ่งชิ้น บั๊กที่ซ่อนอยู่หนึ่งจุด วันละหนึ่งครั้ง ลองได้โดยไม่ต้องมีบัญชี

เขียนโดย
Anthony Fillion-Mailletผู้ก่อตั้ง SharpSkill
เป็นนักพัฒนาฟูลสแตกมากว่า 10 ปี ดูแล SharpSkill และรับผิดชอบทุกสิ่งที่เผยแพร่ที่นี่
อัปเดตเมื่อ 21 สิงหาคม 2569
แท็ก
แชร์
บทความที่เกี่ยวข้อง

การจัดการ State ใน Flutter ปี 2026: Riverpod vs Bloc vs GetX
เปรียบเทียบเชิงปฏิบัติของโซลูชันการจัดการ state ใน Flutter ปี 2026 ครอบคลุม Riverpod 3.4, Bloc 9.1 และ GetX พร้อมตัวอย่างโค้ดจริง การวิเคราะห์ประสิทธิภาพ และกลยุทธ์การ migration

Dart Isolates และ Concurrency 2026: compute, Async และคำถามสัมภาษณ์
คู่มือฉบับสมบูรณ์เกี่ยวกับ Dart isolates, ฟังก์ชัน compute ใน Flutter และ pattern ของ concurrency สำหรับการพัฒนาแอปที่ตอบสนองรวดเร็วในปี 2026

20 คำถามสัมภาษณ์งาน Flutter ที่พบบ่อยที่สุดสำหรับนักพัฒนาแอปมือถือ
เตรียมตัวสัมภาษณ์งาน Flutter ด้วยคำถาม 20 ข้อที่ถูกถามบ่อยที่สุด ครอบคลุม Widget, State Management, Dart, สถาปัตยกรรม และแนวทางปฏิบัติที่ดีที่สุด พร้อมคำอธิบายอย่างละเอียดและตัวอย่างโค้ด