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

การจัดการ state ถือเป็นความท้าทายหลักในการพัฒนา Flutter Riverpod และ BLoC ครองอีโคซิสเต็ม โดยแต่ละตัวนำเสนอปรัชญาที่แตกต่างกัน คู่มือนี้เปรียบเทียบทั้งสองโซลูชันผ่านการนำไปใช้จริงเพื่อช่วยตัดสินใจตามความต้องการของโครงการ
คู่มือนี้คาดว่าผู้อ่านมีความคุ้นเคยกับ Flutter และพื้นฐานการจัดการ state ตัวอย่างใช้ Riverpod 2.x และ flutter_bloc 8.x ซึ่งเป็นเวอร์ชันเสถียรปัจจุบัน
ปรัชญาหลักของทั้งสองแนวทาง
Riverpod และ BLoC แก้ปัญหาเดียวกันด้วยกระบวนทัศน์ที่ตรงข้ามกัน การเข้าใจความแตกต่างเชิงแนวคิดเหล่านี้ช่วยให้เลือกเครื่องมือที่เหมาะสมในแต่ละบริบท
Riverpod ใช้แนวทางแบบประกาศและตอบสนอง provider กำหนดแหล่งข้อมูลที่ widget เฝ้าสังเกต framework จัดการวงจรชีวิต การแคช และการพึ่งพาระหว่าง provider โดยอัตโนมัติ
BLoC (Business Logic Component) บังคับใช้สถาปัตยกรรมที่เคร่งครัดแบบ event-driven คอมโพเนนต์ปล่อยเหตุการณ์ออกมา Bloc ประมวลผลและสร้าง state ใหม่ การแยกแยะอย่างชัดเจนนี้ช่วยให้ติดตามการไหลของข้อมูลได้สะดวก
// Riverpod: simple declaration, framework handles the rest
final counterProvider = StateProvider<int>((ref) => 0);
// 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: explicit events/states separation
abstract class CounterEvent {}
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'),
);
}
}การเลือกระหว่างสองแนวทางนี้ขึ้นอยู่กับความชอบของทีมและข้อจำกัดของโครงการ
การตั้งค่าเริ่มต้น
การตั้งค่าเริ่มต้นเผยให้เห็นความแตกต่างด้านความสะดวกในการใช้งานระหว่างสองโซลูชัน Riverpod ให้ความสำคัญกับความเรียบง่าย BLoC มอบโครงสร้างที่มากกว่า
การติดตั้ง Riverpod
Riverpod ต้องการเพียงแพ็กเกจเดียวและ wrapper ที่รากของแอปพลิเคชัน การสร้างโค้ดอัตโนมัติแบบเลือกใช้ช่วยเพิ่มประสิทธิภาพในการพัฒนา
// 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
// StateProvider: simple state without complex logic
final counterProvider = StateProvider<int>((ref) => 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).state++,
child: const Icon(Icons.add),
),
);
}
}Counter ด้วย BLoC
// Typed events for each possible action
sealed class CounterEvent {}
class CounterIncremented extends CounterEvent {}
class CounterDecremented extends CounterEvent {}
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 เริ่มซับซ้อน
StateProvider เหมาะกับค่าพื้นฐานอย่างง่าย สำหรับวัตถุที่ซับซ้อนหรือ business logic StateNotifierProvider หรือ NotifierProvider ให้การควบคุมที่มากกว่า
การจัดการ State แบบ Asynchronous: การเรียก API
การดำเนินการแบบ asynchronous เผยให้เห็นพลังของแต่ละโซลูชัน การจัดการ state ของ loading, error และ data เป็นความท้าทายสำคัญ
ข้อมูล Asynchronous ด้วย Riverpod
// FutureProvider: automatic loading/error/data management
final usersProvider = FutureProvider.autoDispose<List<User>>((ref) async {
final repository = ref.watch(userRepositoryProvider);
// autoDispose releases resources when provider is no longer used
return repository.fetchUsers();
});
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 {}
class UsersInitial extends UsersState {}
class UsersLoading extends UsersState {}
class UsersLoaded extends UsersState {
final List<User> users;
UsersLoaded(this.users);
}
class UsersError extends UsersState {
final String message;
UsersError(this.message);
}
// Events to trigger actions
sealed class UsersEvent {}
class UsersFetchRequested extends UsersEvent {}
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() => const Center(
child: ElevatedButton(
onPressed: _fetchUsers,
child: 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]),
),
};
},
);
}
void _fetchUsers(BuildContext context) {
context.read<UsersBloc>().add(UsersFetchRequested());
}
}BLoC ให้การควบคุมแบบละเอียดในแต่ละการเปลี่ยน state Riverpod ทำให้เป็นอัตโนมัติมากขึ้นผ่าน AsyncValue
พร้อมที่จะพิชิตการสัมภาษณ์ Flutter แล้วหรือยังครับ?
ฝึกฝนด้วยตัวจำลองแบบโต้ตอบ, flashcards และแบบทดสอบเทคนิคครับ
การพึ่งพาระหว่าง State: การประกอบและการฉีด
แอปพลิเคชันจริงเกี่ยวข้องกับ state ที่พึ่งพาซึ่งกันและกัน การจัดการการพึ่งพาเหล่านี้ทำให้สองแนวทางแตกต่างกันอย่างชัดเจน
การประกอบด้วย Riverpod
// Base provider: configuration
final apiClientProvider = Provider<ApiClient>((ref) {
final baseUrl = ref.watch(environmentProvider).apiUrl;
return ApiClient(baseUrl: baseUrl);
});
// Dependent provider: repository
final productRepositoryProvider = Provider<ProductRepository>((ref) {
// Automatic client injection
final client = ref.watch(apiClientProvider);
return ProductRepository(client);
});
// Provider with parameter: product by ID
final productProvider = FutureProvider.autoDispose.family<Product, String>(
(ref, productId) async {
final repository = ref.watch(productRepositoryProvider);
return repository.getProduct(productId);
},
);
// Derived provider: filtered products
final filteredProductsProvider = Provider<List<Product>>((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
การทดสอบเป็นเกณฑ์ตัดสินสำคัญสำหรับโครงการระดับมืออาชีพ โซลูชันทั้งสองโดดเด่นในด้านนี้ด้วยแนวทางที่แตกต่างกัน
การทดสอบด้วย 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;
late ProviderContainer container;
setUp(() {
mockRepository = MockUserRepository();
// Isolated container with override
container = ProviderContainer(
overrides: [
userRepositoryProvider.overrideWithValue(mockRepository),
],
);
});
tearDown(() => container.dispose());
test('returns users from repository', () async {
// Arrange
final expectedUsers = [User(id: '1', name: 'Test')];
when(() => mockRepository.fetchUsers())
.thenAnswer((_) async => expectedUsers);
// 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'));
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 ใช้รูปแบบการทดสอบมาตรฐานของ Flutter
การทดสอบเฉพาะกรณีปกติไม่เพียงพอ การทดสอบต้องครอบคลุมข้อผิดพลาดทางเครือข่าย timeout state ขอบเขต และการเปลี่ยน state ที่ไม่คาดคิด
ประสิทธิภาพและการเพิ่มประสิทธิภาพ 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
final expensiveComputationProvider = Provider<ExpensiveResult>((ref) {
final input = ref.watch(inputProvider);
// Computation automatically cached
return performExpensiveComputation(input);
});
// autoDispose to release unused resources
final searchResultsProvider = FutureProvider.autoDispose
.family<List<Product>, String>((ref, 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
กรณีศึกษาเชิงปฏิบัติ: ระบบยืนยันตัวตนแบบครบถ้วน
ระบบยืนยันตัวตนแสดงรูปแบบจริงของแต่ละโซลูชัน กรณีนี้รวม state ที่คงอยู่ การเรียก API และการนำทาง
การยืนยันตัวตนด้วย Riverpod
// Authentication state with sealed class
sealed class AuthState {
const AuthState();
}
class AuthInitial extends AuthState {
const AuthInitial();
}
class AuthLoading extends AuthState {
const AuthLoading();
}
class AuthAuthenticated extends AuthState {
final User user;
const AuthAuthenticated(this.user);
}
class AuthUnauthenticated extends AuthState {
final String? error;
const AuthUnauthenticated([this.error]);
}
// Notifier to manage auth state
class AuthNotifier extends StateNotifier<AuthState> {
final AuthRepository _repository;
final SecureStorage _storage;
AuthNotifier(this._repository, this._storage) : super(const AuthInitial()) {
_checkAuthStatus();
}
Future<void> _checkAuthStatus() async {
final token = await _storage.getToken();
if (token != null) {
try {
final user = await _repository.getCurrentUser(token);
state = AuthAuthenticated(user);
} catch (_) {
await _storage.deleteToken();
state = const AuthUnauthenticated();
}
} else {
state = const AuthUnauthenticated();
}
}
Future<void> login(String email, String password) async {
state = const AuthLoading();
try {
final result = await _repository.login(email, password);
await _storage.saveToken(result.token);
state = AuthAuthenticated(result.user);
} catch (e) {
state = AuthUnauthenticated(e.toString());
}
}
Future<void> logout() async {
await _storage.deleteToken();
state = const AuthUnauthenticated();
}
}
// Provider with injected dependencies
final authProvider = StateNotifierProvider<AuthNotifier, AuthState>((ref) {
return AuthNotifier(
ref.watch(authRepositoryProvider),
ref.watch(secureStorageProvider),
);
});
// Redirect based on auth state
final routerProvider = Provider<GoRouter>((ref) {
final authState = ref.watch(authProvider);
return GoRouter(
redirect: (context, state) {
final isAuth = authState 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();
}
class AuthInitial extends AuthState {
const AuthInitial();
}
class AuthCheckInProgress extends AuthState {
const AuthCheckInProgress();
}
class AuthLoginInProgress extends AuthState {
const AuthLoginInProgress();
}
class AuthSuccess extends AuthState {
final User user;
const AuthSuccess(this.user);
}
class AuthFailure extends AuthState {
final String error;
const AuthFailure(this.error);
}
class AuthLoggedOut extends AuthState {
const AuthLoggedOut();
}
// Authentication events
sealed class AuthEvent {
const AuthEvent();
}
class AuthCheckRequested extends AuthEvent {
const AuthCheckRequested();
}
class AuthLoginSubmitted extends AuthEvent {
final String email;
final String password;
const AuthLoginSubmitted(this.email, this.password);
}
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 | BLoC | |-------|----------|------| | ความยากในการเรียนรู้ | ปานกลาง | ชันกว่า | | Boilerplate | น้อยที่สุด | มาก | | Type Safety | ยอดเยี่ยม | ยอดเยี่ยม | | ความสามารถในการทดสอบ | ยอดเยี่ยม | ยอดเยี่ยม | | การติดตามได้ | ผ่าน DevTools | Events/States ชัดเจน | | การประกอบ | อัตโนมัติ | ด้วยตนเอง | | การสร้างโค้ด | เป็นทางเลือก | ไม่จำเป็น | | ขนาดทีม | ยืดหยุ่น | ทีมขนาดใหญ่ |
คำแนะนำตามบริบท
การเลือกระหว่าง Riverpod และ BLoC ขึ้นอยู่กับปัจจัยทางบริบทหลายประการ
เลือก Riverpod เมื่อ:
- ทีมให้ความสำคัญกับความกระชับและประสิทธิภาพในการพัฒนา
- โครงการต้องการการประกอบ state ที่ยืดหยุ่น
- นักพัฒนามาจาก React หรือ framework reactive อื่นๆ
- การแคชอัตโนมัติเป็นข้อได้เปรียบที่สำคัญ
เลือก BLoC เมื่อ:
- ทีมชื่นชอบรูปแบบที่เคร่งครัดและคาดเดาได้
- โครงการต้องการการติดตามเหตุการณ์ที่สมบูรณ์
- นักพัฒนา junior ได้ประโยชน์จากสถาปัตยกรรมที่ถูกบังคับ
- การ debug ต้องการประวัติการเปลี่ยนแปลง
บทสรุป
Riverpod และ BLoC ตอบสนองความต้องการการจัดการ state ใน Flutter ได้อย่างมีประสิทธิภาพ Riverpod โดดเด่นด้านความสะดวกและความยืดหยุ่น BLoC โดดเด่นด้านโครงสร้างและความสามารถในการคาดเดา ทั้งสองโซลูชันให้ความสามารถในการทดสอบที่ยอดเยี่ยมและประสิทธิภาพที่เหมาะสม
รายการตรวจสอบการตัดสินใจ
- ✅ ประเมินขนาดและประสบการณ์ของทีม
- ✅ พิจารณาความซับซ้อนของการไหลของข้อมูล
- ✅ วิเคราะห์ความต้องการการติดตามและ debug
- ✅ ทดสอบทั้งสองโซลูชันบนต้นแบบ
- ✅ ตรวจสอบความสอดคล้องกับสถาปัตยกรรมที่มีอยู่
เริ่มฝึกซ้อมเลย!
ทดสอบความรู้ของคุณด้วยตัวจำลองสัมภาษณ์และแบบทดสอบเทคนิคครับ
ทางเลือกที่ดีที่สุดยังคงเป็นโซลูชันที่ทีมเชี่ยวชาญและบำรุงรักษาได้อย่างมีประสิทธิภาพ ความสอดคล้องในการประยุกต์ใช้สำคัญกว่าการเลือกโซลูชันเอง
แท็ก
แชร์
บทความที่เกี่ยวข้อง

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

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

Flutter: สร้างแอปพลิเคชันข้ามแพลตฟอร์มตัวแรก
คู่มือฉบับสมบูรณ์สำหรับการสร้างแอปพลิเคชันมือถือข้ามแพลตฟอร์มด้วย Flutter และ Dart ครอบคลุม Widget การจัดการสถานะ การนำทาง และแนวทางปฏิบัติที่ดีสำหรับผู้เริ่มต้น