242 lines
7.3 KiB
Markdown
242 lines
7.3 KiB
Markdown
# 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, ToggleFavorite
|
|
- `lib/bloc/book_state.dart` - State containing List<Book>
|
|
|
|
**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, DismissError
|
|
- `lib/bloc/scanner_state.dart` - State: isInitialized, isCapturing, isAnalyzing, hasPermissionError, errorMessage, analyzedBook
|
|
- `lib/bloc/scanner_bloc.dart` - Camera and AI analysis business logic
|
|
|
|
**Screen Changes:**
|
|
- `lib/screens/scanner_screen.dart` converted 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, ChangeTab
|
|
- `lib/bloc/library_state.dart` - State: searchQuery, tabIndex
|
|
- `lib/bloc/library_bloc.dart` - Search and tab management logic
|
|
|
|
**Screen Changes:**
|
|
- `lib/screens/library_screen.dart` converted 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, SaveBook
|
|
- `lib/bloc/add_book_state.dart` - State: title, author, annotation, genre, editBook, isSaved
|
|
- `lib/bloc/add_book_bloc.dart` - Form management and save logic
|
|
|
|
**Screen Changes:**
|
|
- `lib/screens/add_book_screen.dart` converted 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):
|
|
```dart
|
|
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):
|
|
```dart
|
|
// 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:
|
|
```dart
|
|
test('UpdateSearchQuery updates search query', () {
|
|
final bloc = LibraryBloc();
|
|
bloc.add(UpdateSearchQuery('test'));
|
|
expect(bloc.state.searchQuery, 'test');
|
|
});
|
|
```
|
|
|
|
### Widget Tests for Screens:
|
|
```dart
|
|
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
|
|
|
|
1. Add unit tests for all BLoCs
|
|
2. Add widget tests for all screens
|
|
3. Consider adding integration tests
|
|
4. Monitor performance and optimize if needed
|
|
5. Document any screen-specific BLoC patterns
|