Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

15 — Flutter Implementation Guide (Library Module)

Concrete build guide for the library client on top of the app architecture in 00-shared/11. Follows the shared repo/conventions; module specifics only.


1. Module layout (feature-first)

lib/
  features/library/
    data/
      models/book.dart              # BookDoc → Book
      models/borrow_record.dart     # BorrowRecordDoc → BorrowRecord
      library_repository.dart       # dio → endpoints (12_API_Mapping.md)
      library_local_cache.dart      # catalog/borrows TTL caches
    domain/
      fine_calculator.dart          # ceil((now-due)/day) * 5  (mirror library.service.ts:211-217)
    presentation/
      catalog/ (catalog_cubit.dart, catalog_screen.dart, book_row.dart)
      book_detail/ (book_detail_cubit.dart, book_detail_screen.dart)
      book_form/   (book_form_cubit.dart, book_form_screen.dart)
      issue/       (issue_cubit.dart, issue_sheet.dart)
      my_borrows/  (borrows_cubit.dart, my_borrows_screen.dart, borrow_record_card.dart)
      return_/     (return_cubit.dart, return_sheet.dart)
      overdue/     (overdue_cubit.dart, overdue_screen.dart)
      widgets/     (availability_badge.dart, fine_chip.dart, overdue_tag.dart,
                    copy_stepper.dart, fine_summary_panel.dart)

2. Models (parse raw docs; never derive server truth)

enum BookStatus { available, borrowed, damaged, lost }        // book.schema.ts:7-12
enum BorrowStatus { active, returned, overdue, lost }          // borrow-record.schema.ts:7-12
enum FineStatus { pending, paid, waived }                      // :14-18

class Book { final String id, title, author, isbn; final String? publisher,
  category, edition, shelfLocation, description; final int totalCopies,
  availableCopies; final BookStatus status; }
  bool get isAvailable => availableCopies > 0;

class BorrowRecord { final String id, bookId, studentId; final String? issuedBy;
  final DateTime borrowedAt, dueDate; final DateTime? returnedAt;
  final BorrowStatus status; final double fineAmount; final FineStatus? fineStatus;
  final String? notes; final Book? book; }                     // bookId populated: borrow-record.repository.ts:31,42
  bool get isOverdue => status == BorrowStatus.active && DateTime.now().isAfter(dueDate);
  • Keep enums as strings matching server values — no renaming.
  • fineAmount is double/num (plain units; no currency — library.service.ts:25).

3. Repository (one class, typed errors)

class LibraryRepository {
  Future<Paged<Book>> searchBooks({int page = 1, int limit = 20, String? q});
  Future<Book> getBook(String id);
  Future<Book> createBook(CreateBookDto dto);
  Future<Book> updateBook(String id, UpdateBookDto dto);
  Future<void> deleteBook(String id);
  Future<BorrowRecord> issueBook({required String bookId, required String studentId,
                                  required DateTime dueDate, String? notes});
  Future<BorrowRecord> returnBook({required String borrowRecordId,
                                   double? fineAmount, String? notes});
  Future<List<BorrowRecord>> borrowHistory(String studentId);   // controller.ts:73-77
  Future<List<BorrowRecord>> activeBorrows(String studentId);   // controller.ts:79-83
  Future<BorrowRecord> payFine(String borrowRecordId);          // controller.ts:85-89
}
  • Unwrap data/meta from the envelope (response-envelope.interceptor.ts:44-52); throw ApiException(code, message, details, requestId) on success:false.
  • ISO dates: parse dueDate/borrowedAt/returnedAt with UTC-safe parsing; send dueDate.toIso8601String() (@IsDateString, issue-book.dto.ts:14-15).
  • No request body ever includes tenantId (JWT-scoped).

4. Cubits

Implement the seven Cubits from 13_State_Management.md: CatalogCubit, BookDetailCubit, BookFormCubit, IssueCubit, BorrowsCubit, ReturnCubit, OverdueCubit (+ inline FinePayCubit). Rules:

  • No optimistic writes — every mutation emits server-confirmed docs.
  • 409 = refresh, never retry; noCopies/capReached are named states.
  • Double-tap guard — submit states are final until the response returns.
  • Overdue/fine-preview derived via FineCalculator (unit-testable pure function).
  • Cross-cubit refresh table (13 §10): issue → refresh detail + borrows; return → detail + borrows + overdue; pay → borrows.

5. Caching

DataKeyTTLNotes
Catalog pagelib:catalog:{tenant}:{q}:{page}5 minstale-while-revalidate; pull-to-refresh bypasses
Book detaillib:book:{id}1 minfocus re-fetch
Active borrowslib:active:{studentId}1 minfocus re-fetch
Historylib:history:{studentId}5 minfocus re-fetch
Writesnever cached

6. Error UX mapping

CodeHandling
400 VALIDATION_ERRORmap details[].message → field errors (forms)
409 DUPLICATE_RESOURCEcontext banners: ISBN dup (inline), no copies, 5-cap, not-active
404empty states / close form + snackbar
401refresh token once → retry → sessionExpired
429countdown banner, no retry
5xx / networkgeneric + requestId + retry action

7. Permissions-aware UI (RBAC planned — OQ-1)

Gate from the user's role permissions (matrix in 02 §6): books.read (module visible), books.create (FAB), books.update (edit), books.delete (menu), books.issue (issue CTA), books.return (return button), fines.pay (pay action). Until the server enforces, the client hides what the role lacks; when guards land, keep the same gates.

8. Date & fine handling

  • Due dates: native date picker; client min = tomorrow; default +14 days.
  • FineCalculator.daysOverdue(dueDate) = ceil((now − dueDate).inDays fractional) — must mirror Math.ceil(diffMs / dayMs) (library.service.ts:215).
  • Fine preview is always labeled estimate; after return, render the server's fineAmount verbatim.
  • Timezone: compare dates in the tenant's local day boundary (proposed); today use UTC instants to stay consistent with the server's new Date() comparisons.

9. Testing

  • Unit: FineCalculator (due-today → 0; +1 ms → 5; +3.5 d → 20), model parsing, enum mapping, BookRow badge states.
  • Cubit: issue 409 paths (noCopies, capReached), return notActive, fine pay gating, catalog filter logic.
  • Widget: sheet non-dismissible during submit; FAB hidden without permission.
  • Integration: repository against the API with mocked envelope + error bodies.
  • E2E (needs Mongo + Redis): the scenarios in 14_QA_Checklist.md QA-1/2/4/6.

10. Known ceilings (ponytail notes)

  • Overdue screen is derived data; replace with the server endpoint when the scan ships (planned) (IMPLEMENTATION_PLAN.md:228).
  • No QR/barcode scan yet — (forward-looking); keep the issue flow keyed on bookId so a scanner can later inject the same id.
  • Analytics (proposed) — hook names from 05 §Analytics when the SDK lands.