Last active
March 31, 2025 13:01
-
-
Save hawkkiller/c17563201247e53f2c1b0295f37e8903 to your computer and use it in GitHub Desktop.
A good looking BLoC that conforms to design principles and manages the state correctly.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import 'package:bloc/bloc.dart'; | |
| class UserProfile { | |
| UserProfile({ | |
| required this.name, | |
| this.age, | |
| }); | |
| final String name; | |
| final int? age; | |
| UserProfile copyWith({ | |
| String? name, | |
| int? age, | |
| }) => | |
| UserProfile( | |
| name: name ?? this.name, | |
| age: age ?? this.age, | |
| ); | |
| } | |
| abstract interface class UserProfileRepository { | |
| Future<UserProfile> fetchUserProfile(); | |
| Future<void> updateUserProfile(UserProfile userProfile); | |
| } | |
| sealed class UserProfileEvent { | |
| const UserProfileEvent(); | |
| } | |
| final class UserProfileEventFetch extends UserProfileEvent { | |
| const UserProfileEventFetch(); | |
| } | |
| final class UserProfileEventUpdate extends UserProfileEvent { | |
| const UserProfileEventUpdate(this.userProfile); | |
| final UserProfile userProfile; | |
| } | |
| sealed class UserProfileState { | |
| const UserProfileState({ | |
| this.userProfile, | |
| }); | |
| final UserProfile? userProfile; | |
| } | |
| /// State when BLoC is idling (doing nothing) | |
| final class UserProfileStateIdle extends UserProfileState { | |
| const UserProfileStateIdle({super.userProfile}); | |
| @override | |
| int get hashCode => Object.hashAll([userProfile]); | |
| @override | |
| bool operator ==(Object other) => | |
| identical(this, other) || other is UserProfileStateIdle && userProfile == other.userProfile; | |
| } | |
| /// State when BLoC is processing (fetching or updating user profile) | |
| final class UserProfileStateProcessing extends UserProfileState { | |
| const UserProfileStateProcessing({super.userProfile}); | |
| @override | |
| int get hashCode => Object.hashAll([userProfile]); | |
| @override | |
| bool operator ==(Object other) => | |
| identical(this, other) || other is UserProfileStateProcessing && userProfile == other.userProfile; | |
| } | |
| /// State when BLoC has successfully loaded user profile | |
| final class UserProfileStateSuccess extends UserProfileState { | |
| const UserProfileStateSuccess({required this.userProfile}); | |
| // override this field to be non-nullable | |
| // because when state is loaded, it should have a value | |
| @override | |
| final UserProfile userProfile; | |
| @override | |
| int get hashCode => Object.hashAll([userProfile]); | |
| @override | |
| bool operator ==(Object other) => | |
| identical(this, other) || other is UserProfileStateSuccess && userProfile == other.userProfile; | |
| } | |
| /// State when BLoC has encountered an error | |
| final class UserProfileStateError extends UserProfileState { | |
| const UserProfileStateError(this.error, {super.userProfile}); | |
| /// Actual error object | |
| /// | |
| /// This can be used by the UI to display error message. | |
| final Object error; | |
| @override | |
| int get hashCode => Object.hashAll([error, userProfile]); | |
| @override | |
| bool operator ==(Object other) => | |
| identical(this, other) || | |
| other is UserProfileStateError && error == other.error && userProfile == other.userProfile; | |
| } | |
| final class UserProfileBloc extends Bloc<UserProfileEvent, UserProfileState> { | |
| UserProfileBloc({ | |
| required this.userProfileRepository, | |
| }) : super(const UserProfileStateIdle()) { | |
| on<UserProfileEvent>( | |
| (event, emit) => switch (event) { | |
| UserProfileEventFetch() => _fetchUserProfile(event, emit), | |
| UserProfileEventUpdate() => _updateUserProfile(event, emit), | |
| }, | |
| // BE SURE TO INCLUDE THIS LINE TO MAKE THE PROCESSING SEQUENTIAL | |
| // OR ADD IT TO GLOBAL TRANSFORMER | |
| transformer: (events, mapper) => events.asyncExpand(mapper), | |
| ); | |
| } | |
| final UserProfileRepository userProfileRepository; | |
| Future<void> _fetchUserProfile( | |
| UserProfileEventFetch event, | |
| Emitter<UserProfileState> emit, | |
| ) async { | |
| emit(UserProfileStateProcessing(userProfile: state.userProfile)); | |
| try { | |
| final userProfile = await userProfileRepository.fetchUserProfile(); | |
| emit(UserProfileStateSuccess(userProfile: userProfile)); | |
| } on Object catch (error, stackTrace) { | |
| emit(UserProfileStateError(error, userProfile: state.userProfile)); | |
| // It is important to report unknown error | |
| // Otherwise, it will be lost and you won't know what happened | |
| onError(error, stackTrace); | |
| } | |
| } | |
| Future<void> _updateUserProfile( | |
| UserProfileEventUpdate event, | |
| Emitter<UserProfileState> emit, | |
| ) async { | |
| final previousProfile = state.userProfile; | |
| emit(UserProfileStateProcessing(userProfile: event.userProfile)); | |
| try { | |
| await userProfileRepository.updateUserProfile(event.userProfile); | |
| emit(UserProfileStateSuccess(userProfile: event.userProfile)); | |
| } on Object catch (error, stackTrace) { | |
| emit(UserProfileStateError(error, userProfile: previousProfile)); | |
| // It is important to report unknown error | |
| // Otherwise, it will be lost and you won't know what happened | |
| onError(error, stackTrace); | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment