Block Pattern

Part 10 – Flutter BLoC Tutorial: Best Practices & Clean Architecture Guide

📘 Part 10 – Best Practices & Architecture in Flutter BLoC


🧱 1. Use a Clean Folder Structure

Organizing your code clearly improves maintainability. Here’s a suggested structure:

/lib
├── /blocs
│ └── auth/
│ ├── auth_bloc.dart
│ ├── auth_event.dart
│ └── auth_state.dart
├── /cubits
│ └── theme_cubit.dart
├── /models
├── /repositories
├── /screens
├── /widgets
└── main.dart

✅ Keep logic, data, and UI separate. Avoid putting everything in one file.


🧠 2. Prefer Cubit for Simple Logic

If your feature has:

  • A single state change trigger

  • No complex event handling

➡️ Use Cubit to keep it clean and minimal.

class ThemeCubit extends Cubit<ThemeMode> {
ThemeCubit() : super(ThemeMode.light);
void toggleTheme() => emit(
state == ThemeMode.light ? ThemeMode.dark : ThemeMode.light
);
}

🔄 3. Use BLoC for Complex Flows

Use BLoC when:

  • Multiple types of events must be handled

  • You need strong separation of concerns

  • Async operations like network calls are involved

on<LoginEvent>((event, emit) async {
emit(AuthLoading());
try {
final user = await authRepo.login(event.email, event.password);
emit(Authenticated(user));
} catch (_) {
emit(AuthFailure());
}
});

🧪 4. Write Tests for Your BLoCs

📦 Add bloc_test to dev_dependencies:

dev_dependencies:
bloc_test: ^9.0.0

✅ Example Test:

blocTest<CounterCubit, int>(
'emits [1] when increment is called',
build: () => CounterCubit(),
act: (cubit) => cubit.increment(),
expect: () => [1],
);

🔍 5. Use BlocObserver for Debugging

Track all BLoC state changes globally:

class MyObserver extends BlocObserver {
@override
void onTransition(Bloc bloc, Transition transition) {
print('${bloc.runtimeType} transitioned: $transition');
super.onTransition(bloc, transition);
}
}

void main() {
Bloc.observer = MyObserver();
runApp(MyApp());
}


🚀 6. Integrate Dependency Injection

Avoid tightly coupling your BLoCs to services.

Use packages like:

  • get_it

  • injectable

final sl = GetIt.instance;

sl.registerLazySingleton<AuthRepository>(() => AuthRepositoryImpl());
sl.registerFactory(() => AuthBloc(sl<AuthRepository>()));


🧠 Summary: Best Practices

Practice Why it Matters
Use clean folder structure Easier to scale and maintain
Prefer Cubit for simple logic Less boilerplate, faster to implement
Use BLoC for event-heavy flows Cleaner separation of logic
Write tests for BLoCs/Cubits Avoid regressions and bugs
Use BlocObserver for debugging Track issues in development
Inject dependencies properly More modular and testable code

 

Leave a Reply

Your email address will not be published. Required fields are marked *