Skip to content

Instantly share code, notes, and snippets.

@hawkkiller
Last active March 31, 2025 13:01
Show Gist options
  • Select an option

  • Save hawkkiller/c17563201247e53f2c1b0295f37e8903 to your computer and use it in GitHub Desktop.

Select an option

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.
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