Files
gps-speedo/.planning/codebase/CONVENTIONS.md
T
Mikkeli Matlock a686e6b0fb docs: map existing codebase
- STACK.md - Technologies and dependencies
- ARCHITECTURE.md - System design and patterns
- STRUCTURE.md - Directory layout
- CONVENTIONS.md - Code style and patterns
- TESTING.md - Test structure
- INTEGRATIONS.md - External services
- CONCERNS.md - Technical debt and issues
2026-02-09 18:51:11 +09:00

161 lines
7.6 KiB
Markdown

# Coding Conventions
**Analysis Date:** 2026-02-09
## Naming Patterns
**Files:**
- snake_case for file names: `gps_service.dart`, `color_themes.dart`, `speed_units.dart`
- Underscore prefix for internal/private files: `gps_data_manager.dart` in `services/` directory
**Classes:**
- PascalCase: `GpsService`, `ColorTheme`, `ColorThemes`, `GpsDataManager`, `ProcessedGpsData`
- State classes use underscore prefix: `_SpeedometerScreenState`, `_OverlaySpeedometerState`
- Widget classes standard PascalCase: `SpeedoApp`, `SpeedometerScreen`, `OverlaySpeedometer`
**Functions:**
- camelCase for all functions and methods: `initialize()`, `requestPermissions()`, `getFormattedSpeed()`, `_onPositionUpdate()`, `_onGpsError()`
- Private/internal functions use underscore prefix: `_updateData()`, `_scheduleStaleDataTimer()`, `_buildPortraitLayout()`
- Single-purpose private methods are common: `_cycleUnit()`, `_cycleTheme()`, `_handleOverlayClose()`
**Variables:**
- camelCase for instance and local variables: `_currentUnit`, `_currentThemeIndex`, `displaySpeed`, `speedText`
- Underscore prefix for private fields: `_gpsSubscription`, `_currentData`, `_isOverlayActive`, `_errorMessage`
- Boolean variables include descriptive names: `_isInitialized`, `_isSpeedValid`, `_isHeadingValid`, `_isInBackground`
**Types:**
- PascalCase for enums: `SpeedUnit` with enum values in lowercase: `kmh`, `mph`, `knots`
- Data classes: `ProcessedGpsData`, `ColorTheme` with named constructors using `const`
**Constants:**
- UPPER_CASE with underscore prefix for private constants: `_staleDataThreshold`
- camelCase for enum members: `kmh`, `mph`, `knots`, `themes`
## Code Style
**Formatting:**
- 2-space indentation throughout all Dart files
- Lines typically kept under 100 characters but pragmatically longer when needed (examples: line 671-672 in main.dart for complex expressions)
- Single newline between method definitions
- Double newline between class definitions
**Linting:**
- Uses `flutter_lints: ^6.0.0` from pubspec.yaml
- Configured via `analysis_options.yaml` with `include: package:flutter_lints/flutter.yaml`
- Explicit `exclude` pattern for backup directories: `backup_v1.1/**`
**Language Features:**
- Uses `const` constructors for immutable data classes: `const ProcessedGpsData(...)`, `const ColorTheme(...)`
- Enum with properties: `SpeedUnit` enum includes label and multiplier
- Named parameters with required keyword: `required this.speed`, `required this.label`
- Null safety enabled by default (Dart 3.9+)
## Import Organization
**Order:**
1. Dart SDK imports: `import 'dart:async'`, `import 'dart:math'`
2. Flutter framework imports: `import 'package:flutter/material.dart'`, `import 'package:flutter/foundation.dart'`
3. Third-party package imports: `import 'package:geolocator/geolocator.dart'`, `import 'package:permission_handler/permission_handler.dart'`
4. Relative imports (local files): `import 'speed_units.dart'`, `import 'services/gps_data_manager.dart'`
**Path Aliases:**
- No path aliases configured - uses explicit relative imports
- Deep imports work from lib root: `import 'services/gps_data_manager.dart'` from main.dart, `import '../speed_units.dart'` from services/gps_data_manager.dart
**Import Annotations:**
- Uses `as` for disambiguation: `import 'dart:math' as math;` to access `math.pi`
- Comments inline with imports for clarity: `import 'package:flutter/foundation.dart'; // For kDebugMode`
## Error Handling
**Patterns:**
- Try-catch blocks with specific error handling: 15 try-catch blocks across codebase
- Error messages propagated through `setState()`: `setState(() => _errorMessage = 'Failed to initialize GPS manager')`
- Silent error handling in production code: catch blocks with `customDebugPrint()` only - see `_showFloatingWindow()` line 480
- Stack trace logging: `catch (e, stackTrace)` pattern used consistently in GPS manager and main app
- Error display in UI: `_errorMessage` variable checked in build methods and displayed when non-empty
**Error Message Patterns:**
- Prefixed error states in GPS manager: `'GPS OFF'`, `'NO PERM'`, `'GPS ERR'` for UI display
- Detailed debug messages for troubleshooting: `[GpsDataManager] ❌ GPS manager initialization exception: $e`
## Logging
**Framework:**
- Uses `print()` for initialization and critical operations (GPS manager setup, main app lifecycle)
- Uses custom `customDebugPrint()` helper for conditional debug output
- Logging conditionally enabled in debug mode via `if (kDebugMode)` check
**Pattern - customDebugPrint():**
```dart
void customDebugPrint(String message) {
if (kDebugMode) {
print(message);
}
}
```
**Patterns:**
- Heavy use of emoji prefixes for log levels: ✅ success, ❌ error, ⚠️ warning, 📡 data/status, 🚀 start, 🔴 end
- Consistent tagging system: `[GpsDataManager]`, `[Main]`, `[Overlay]` for source identification
- Log immediately on entry and exit: `print('[Main] 🚀 Starting GPS manager initialization...')` and `print('[Main] ✅ GPS manager initialization completed')`
- Detailed state information in logs: `customDebugPrint('[Main] 📡 Received GPS data: ${gpsData.speed.toStringAsFixed(1)} m/s, ${gpsData.displayHeading}')`
## Comments
**When to Comment:**
- Block comments explain WHY, not WHAT: `// Aggressive heartbeat to keep main app process active for overlay communication`
- Inline comments for non-obvious logic: `// Use actual physical pixels for overlay sizing`
- TODO comments mark future work: `// TODO: Re-enable: if (_currentGpsData.speed < 1.0...` (line 501)
- Feature notes: `// Tap to bring main app to front functionality removed per NOTES.txt` (line 1092)
**JSDoc/TSDoc:**
- Not used. Dart conventions not followed strictly for documentation.
- Comments are inline and contextual, not formal doc comments.
## Function Design
**Size:**
- Methods vary from 3 lines to 70+ lines
- Widget build methods are large (100+ lines) as they contain layout logic
- Helper methods tend to be 10-20 lines: `_cycleUnit()`, `_cycleTheme()`, `_formatHeading()`
**Parameters:**
- Named parameters preferred over positional: `{required this.speed, required this.heading}`
- Optional parameters with defaults: `void customDebugPrint(String message)`
- Named parameters in method calls: `await FlutterOverlayWindow.showOverlay(enableDrag: true, overlayTitle: "Speedometer"...)`
**Return Values:**
- Void for side-effect operations: `void _cycleUnit()`, `void _cycleTheme()`, `void _updateData()`
- Stream returns for reactive data: `Stream<ProcessedGpsData> get dataStream => _dataController.stream`
- Widget returns for UI methods: `Widget _buildSpeedDisplay(...)`, `Widget _buildPortraitLayout(...)`
- Future returns for async operations: `Future<void> _enableWakelock()`, `Future<void> _initializeGpsManager()`
- String returns for formatting: `String getFormattedSpeed(SpeedUnit unit)`, `String formatHeading(double heading)`
## Module Design
**Exports:**
- No explicit pub exports used
- Public API via static methods: `GpsService.isLocationServiceEnabled()`, `ColorThemes.getTheme()`
- Singleton pattern for manager: `GpsDataManager.instance`
**Barrel Files:**
- Not used - each module imported directly by name
- Example: `import 'services/gps_data_manager.dart'` (not `import 'services/index.dart'`)
**Singleton Pattern:**
- Used in `GpsDataManager`: lazy initialization with null coalescing
```dart
static GpsDataManager? _instance;
static GpsDataManager get instance => _instance ??= GpsDataManager._internal();
GpsDataManager._internal();
```
**Service Classes:**
- Stateless utility class: `GpsService` contains only static methods
- Stateful manager: `GpsDataManager` contains mutable state and streams
---
*Convention analysis: 2026-02-09*