Files
bookshelf/books_flutter/REFACTORING_SUMMARY.md
2026-02-08 12:05:05 +06:00

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, ToggleFavorite
  • lib/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, 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):

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

  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