mobile bluesky app made with flutter
lazurite.stormlightlabs.org/
mobile
bluesky
flutter
1---
2title: Code Patterns
3updated: 2026-05-07
4---
5
6This document captures recurring architectural and implementation patterns in the Lazurite codebase.
7
8## Code Organization
9
10```sh
11lib/
12 ├── core/ # cross-cutting app/platform concerns (router, theme, logging, network, database)
13 ├── features/ # feature-first modules
14 │ └── {feature} # one vertical slice per domain
15 │ ├── data/ # repositories, DTO mapping, persistence/network access
16 │ ├── bloc or cubit/ # state orchestration and business flow
17 │ └── presentation/ # screens, widgets, and view-specific helpers
18 └── shared/ # reusable widgets/helpers/utils not tied to one feature
19test/ # mirrors production layout (core/features/shared) for unit/widget tests
20docs/ # specs, tasks, operational docs, and developer references
21```
22
23## State and UI Patterns
24
25- Prefer immutable `Equatable` state with explicit `copyWith` semantics.
26- Model state transitions in `Bloc`/`Cubit` layers; keep presentation widgets focused on rendering and user interactions.
27- Use `sealed class` events and typed state classes to keep transitions explicit and testable.
28- For repeated UI states, use shared widgets instead of ad-hoc `Center(...)` blocks:
29 - `LoadingState`
30 - `ErrorState`
31 - `EmptyState`
32
33## Extracted Reuse Patterns
34
35### Utilities
36
37`lib/shared/utils/format_utils.dart` is the canonical source for display primitives (initials, compact counts, relative time labels).
38The extraction replaced repeated local formatters from feed/profile/search/notification surfaces with one tested implementation (`test/shared/utils/format_utils_test.dart`).
39
40### Dialogs, Sheets, and Snackbars
41
42Transient UI interaction patterns were consolidated into shared helpers and widgets:
43
44- `showConfirmationDialog` for confirm/cancel flows
45- `showOptionsSheet` for action menus
46- `showAppSnackBar` for user feedback
47
48This keeps behavior and semantics consistent across features and is covered with focused widget tests in `test/shared/presentation/...`.
49
50### Theme Access and Tokens
51
52Theme access should go through `lib/core/theme/theme_extensions.dart` (`context.theme`, `context.colorScheme`, `context.textTheme`) rather than repeated `Theme.of(...)` lookups.
53
54For layout rhythm, reuse spacing tokens from `lib/core/theme/spacing.dart`.
55
56For visual consistency in image treatment, use shared filters from `lib/core/theme/color_filters.dart`.
57
58### Reusable Presentation Widgets
59
60Repeated avatar/name/icon logic from the audit was extracted into shared primitives. Prefer:
61
62- `ProfileAvatar` (`lib/shared/presentation/widgets/profile_avatar.dart`) for all profile/list/feed avatar rendering, including fallback and moderation-aware masking behavior.
63- `ActorNameWidget` (`lib/shared/presentation/widgets/actor_name_widget.dart`) for standardized display-name + handle rows (used in feed cards, embedded records, and conversation items).
64- `NotificationIconMapper` (`lib/shared/presentation/helpers/notification_icon_mapper.dart`) for mapping notification reason to icon + color style, instead of local switch blocks.
65
66### Navigation and Haptics
67
68Navigation/haptic side effects also follow shared helper entry points.
69Use `navigateToProfile` and `navigateToPost` for common entity navigation, and route tactile feedback through `HapticHelper` instead of direct `HapticFeedback` calls in feature widgets.
70
71## Other Recurring Codebase Patterns
72
73- Feature-first vertical slices under `lib/features/{feature}/{data,bloc|cubit,presentation}` keep business logic close to owning UI while preserving cross-feature boundaries.
74- Dependency injection is explicit at composition boundaries (`RepositoryProvider`/`BlocProvider` in app and screen roots), which makes screens and blocs easy to test in isolation.
75- Repository interfaces return typed results (often with cursor pagination) instead of raw maps, keeping API edges explicit and testable.
76- Defensive fallback behavior is preferred for unstable integrations; for example, repository logic that falls back on alternative endpoints when one API path fails is covered by contract tests.
77- Shared moderation-aware rendering is treated as a first-class cross-feature concern (`ModerationUI`, `ModerationBadgeRow`, `ModeratedBlurOverlay`, `ProfileAvatar` moderation hooks).
78- Use fully-qualified package imports (`package:lazurite/...`) for internal modules.
79
80## Moderation-Aware Rendering Pattern
81
82- For actor/content media that can be moderated, request moderation UI state via moderation service helpers and pass it into rendering widgets.
83- `ProfileAvatar` supports moderated fallback/masking through `ModerationUI`.
84- Compose moderation badges/overlays (`ModerationBadgeRow`, `ModeratedBlurOverlay`) at feature widget boundaries.
85
86## Nullable `copyWith` Pattern (Required)
87
88For nullable fields in immutable state, **do not** use `field ?? this.field` when `null` is a meaningful explicit update.
89
90Use a sentinel default to distinguish:
91
92- keep current value
93- set value to `null`
94
95```dart
96static const Object _unset = Object();
97
98State copyWith({
99 Object? nullableField = _unset,
100}) {
101 return State(
102 nullableField: identical(nullableField, _unset)
103 ? this.nullableField
104 : nullableField as String?,
105 );
106}
107```
108
109### Review Checklist
110
111- For clearable nullable fields, avoid `??` fallback in `copyWith`.
112- Add tests proving nullable fields can be explicitly cleared.
113
114## Testing Patterns
115
116- Per-file setup builders are standard (`buildSubject(...)`, plus helpers like `openSheet(...)`) so test bodies focus on behavior, not setup.
117- Widget tests usually mount through `MaterialApp`/`Scaffold` for component tests, and `MaterialApp.router` + `GoRouter` for navigation tests.
118- Interaction flow follows a consistent shape:
119 `pumpWidget` -> input (`tap`, `enterText`) -> `pump`/`pumpAndSettle` -> assertions on visible UI and side effects.
120- BLoC/Cubit widget tests rely on `mocktail` + `bloc_test` (`MockBloc`, `MockCubit`, `when`, `whenListen`, `verify`, `verifyNever`), with `registerFallbackValue` in `setUpAll` when needed.
121- Router behavior is verified by capturing the pushed route URI and asserting path/query params
122- Complex service/repository tests use fit-for-purpose doubles:
123 - `MockClient` from `http/testing.dart` for HTTP-level contracts
124 - Small local fake implementations when protocol surfaces are complex
125- Async UI timing is made explicit where animations/debounce are relevant (`pump(const Duration(...))` in sheet/search tests), and responsive behavior is exercised with `setSurfaceSize` in router/shell tests.
126- Defensive assertions are common: boundary-value checks in utility tests, null/empty/error-path repository tests, and `expect(tester.takeException(), isNull)` guards in navigation/router tests.