7.3 KiB
7.3 KiB
Refactoring Summary: BLoC Architecture Improvements
Overview
All screens have been refactored to follow best practices with dedicated BLoCs and separate event/state files. This improves code organization, testability, and maintainability.
Architecture Pattern
Each screen now follows this structure:
lib/bloc/
├── [feature]_event.dart # Event definitions
├── [feature]_state.dart # State definitions
└── [feature]_bloc.dart # Business logic
lib/screens/
└── [feature]_screen.dart # UI only (stateless)
Changes Made
1. BookBloc (Refactored)
Files Created:
lib/bloc/book_event.dart- Events: AddBook, UpdateBook, DeleteBook, ToggleFavoritelib/bloc/book_state.dart- State containing List
Changes:
- Separated events and state from main BLoC file
- BLoC handles global book collection management
- Used across all screens for book data access
2. ScannerBloc (New)
Files Created:
lib/bloc/scanner_event.dart- Events: InitializeCamera, CaptureAndAnalyze, SwitchCamera, DismissErrorlib/bloc/scanner_state.dart- State: isInitialized, isCapturing, isAnalyzing, hasPermissionError, errorMessage, analyzedBooklib/bloc/scanner_bloc.dart- Camera and AI analysis business logic
Screen Changes:
lib/screens/scanner_screen.dartconverted from StatefulWidget to StatelessWidget- Removed all setState() calls and local state management
- Uses BlocProvider for state management
- Uses BlocListener for side effects (errors, navigation)
- Uses BlocBuilder for reactive UI
Business Logic Moved to BLoC:
- Camera initialization and permission handling
- Image capture process
- AI service selection (OpenAI first, Gemini fallback)
- Error state management
- Temporary file cleanup
3. LibraryBloc (New)
Files Created:
lib/bloc/library_event.dart- Events: UpdateSearchQuery, ChangeTablib/bloc/library_state.dart- State: searchQuery, tabIndexlib/bloc/library_bloc.dart- Search and tab management logic
Screen Changes:
lib/screens/library_screen.dartconverted from StatefulWidget to StatelessWidget- Removed local state (_search, _tabIndex)
- Uses LibraryBloc for UI state
- Uses BookBloc for book data
- Nested BlocBuilders for optimal rebuilds
Business Logic Moved to BLoC:
- Search query management
- Tab selection state
- Book filtering logic (still in UI, but uses BLoC state)
4. AddBookBloc (New)
Files Created:
lib/bloc/add_book_event.dart- Events: InitializeForm, UpdateTitle, UpdateAuthor, UpdateAnnotation, UpdateGenre, ApplyScannedBook, SaveBooklib/bloc/add_book_state.dart- State: title, author, annotation, genre, editBook, isSavedlib/bloc/add_book_bloc.dart- Form management and save logic
Screen Changes:
lib/screens/add_book_screen.dartconverted outer widget to StatelessWidget- Created internal StatefulWidget for TextController lifecycle
- Uses BlocProvider with callbacks to BookBloc
- Uses BlocListener to update controllers and handle navigation
- Uses BlocBuilder for reactive form state
Business Logic Moved to BLoC:
- Form field state management
- Edit vs Add mode detection
- Scanned book data application
- Book creation/update logic with proper field mapping
- Save completion state
5. BookDetailsScreen (No Changes)
Status: Already stateless and has minimal business logic
- Displays book data passed as parameter
- Navigates to edit screen
- Calls BookBloc for delete operation
- No dedicated BLoC needed as it's a simple presentation screen
Benefits
✅ Separation of Concerns
- UI components only handle presentation
- Business logic isolated in BLoCs
- Clear boundaries between layers
✅ Testability
- BLoCs can be unit tested independently
- No UI dependencies in business logic
- Events and states are simple data classes
✅ Maintainability
- Each file has single responsibility
- Easy to locate and modify logic
- Consistent pattern across all screens
✅ Scalability
- Easy to add new events and states
- BLoCs can be reused across screens
- State changes are predictable and traceable
✅ Reduced Boilerplate
- No manual setState() management
- Automatic UI rebuilds on state changes
- Side effects handled declaratively
File Structure
lib/
├── bloc/
│ ├── book_event.dart # Book collection events
│ ├── book_state.dart # Book collection state
│ ├── book_bloc.dart # Book collection logic
│ ├── scanner_event.dart # Scanner events
│ ├── scanner_state.dart # Scanner state
│ ├── scanner_bloc.dart # Scanner logic
│ ├── library_event.dart # Library UI events
│ ├── library_state.dart # Library UI state
│ ├── library_bloc.dart # Library UI logic
│ ├── add_book_event.dart # Add/Edit book events
│ ├── add_book_state.dart # Add/Edit book state
│ └── add_book_bloc.dart # Add/Edit book logic
├── screens/
│ ├── library_screen.dart # Stateless - uses LibraryBloc + BookBloc
│ ├── scanner_screen.dart # Stateless - uses ScannerBloc
│ ├── add_book_screen.dart # Stateless wrapper + Stateful content
│ └── book_details_screen.dart # Stateless - no dedicated BLoC
└── ...
Migration Guide
Before (StatefulWidget with setState):
class MyScreen extends StatefulWidget {
@override
State<MyScreen> createState() => _MyScreenState();
}
class _MyScreenState extends State<MyScreen> {
String _value = '';
void _updateValue(String newValue) {
setState(() => _value = newValue);
}
@override
Widget build(BuildContext context) {
return Text(_value);
}
}
After (StatelessWidget with BLoC):
// Event
class UpdateValue extends MyEvent {
final String value;
UpdateValue(this.value);
}
// State
class MyState {
final String value;
const MyState({this.value = ''});
MyState copyWith({String? value}) => MyState(value: value ?? this.value);
}
// BLoC
class MyBloc extends Bloc<MyEvent, MyState> {
MyBloc() : super(const MyState()) {
on<UpdateValue>((event, emit) => emit(state.copyWith(value: event.value)));
}
}
// Screen
class MyScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => MyBloc(),
child: BlocBuilder<MyBloc, MyState>(
builder: (context, state) => Text(state.value),
),
);
}
}
Testing Recommendations
Unit Tests for BLoCs:
test('UpdateSearchQuery updates search query', () {
final bloc = LibraryBloc();
bloc.add(UpdateSearchQuery('test'));
expect(bloc.state.searchQuery, 'test');
});
Widget Tests for Screens:
testWidgets('LibraryScreen displays books', (tester) async {
await tester.pumpWidget(
MultiBlocProvider(
providers: [
BlocProvider(create: (_) => BookBloc()),
BlocProvider(create: (_) => LibraryBloc()),
],
child: MaterialApp(home: LibraryScreen()),
),
);
expect(find.byType(BookCard), findsWidgets);
});
Next Steps
- Add unit tests for all BLoCs
- Add widget tests for all screens
- Consider adding integration tests
- Monitor performance and optimize if needed
- Document any screen-specific BLoC patterns