This file provides guidance to AI coding agents when working with code in this repository.
This is the Blitzortung Lightning Monitor Android application - a real-time lightning visualization app built in Kotlin that displays lightning strike data from the blitzortung.org network. The app features map-based strike visualization, proximity alerts, background monitoring, and localization support for 10+ languages.
Project documentation: https://blitzortung.tryb.de
./gradlew build# Run all unit tests
./gradlew testDebugUnitTest
# Run specific test class
./gradlew testDebugUnitTest --tests "org.blitzortung.android.alert.AlertResultTest"
# Run tests with coverage report
./gradlew testDebugUnitTest jacocoTestReport
# Coverage report: app/build/reports/jacoco/jacocoTestReport/html/index.html# Run lint
./gradlew lint
# Run SonarQube analysis (requires SONAR_TOKEN)
./gradlew sonar
# Update the verification metadata
./gradlew --write-verification-metadata sha256 help# Build APK
./gradlew assembleRelease
# Build Android App Bundle (AAB)
./gradlew bundleRelease# Install debug build
./gradlew installDebug
# Install and launch
adb install -r app/build/outputs/apk/debug/app-debug.apk
adb shell am start -n org.blitzortung.android.app/.app.Main- Language: Kotlin 2.2.10, JVM 17
- Min SDK: 21, Target SDK: 35
- Build System: Gradle 8.13
- DI Framework: Dagger 2.57.1
- Testing: JUnit 4, AssertJ, MockK, Robolectric
- Map Library: OSMDroid 6.1.20
- UI: ViewBinding, Material Components, AndroidX libraries
The app follows a component-based architecture with Dagger 2 dependency injection coordinating the major subsystems:
BOApplication (Dagger root)
├── Main Activity (foreground UI)
│ ├── MapFragment with overlays
│ ├── UI components (status, alerts, histogram, etc.)
│ └── Controllers (history, notifications, buttons)
└── AppService (background monitoring)
└── Minimal UI (notifications only)
Shared Singletons:
├── MainDataHandler / ServiceDataHandler (data flow)
├── LocationHandler (GPS tracking)
├── AlertHandler (proximity alerts)
└── DataProviderFactory (API clients)
Component: AppComponent (singleton scope)
Modules:
AppModule: Application context, SharedPreferences, PackageInfo, NotificationManager, Vibrator, WakeLockServiceModule: Handler (main thread), AlarmManager, Period (timing utilities)ActivityBindingModule: Auto-injects Main activity and AppServiceSettingsModule: Auto-injects SettingsFragment
All major components (@Inject annotated) are wired through Dagger. The BOApplication class initializes the component graph on startup.
The app uses a publish-subscribe pattern via ConsumerContainer<T> for component communication:
Event Types:
DataEvent: Strike data updates (ResultEvent, RequestStartedEvent, StatusEvent)AlertEvent: Alert state changes (AlertResultEvent, AlertCancelEvent)LocationEvent: GPS location updates
Key Producers:
MainDataHandler/ServiceDataHandler: BroadcastsDataEventwhen new strike data arrivesAlertHandler: BroadcastsAlertEventwhen proximity alerts trigger/cancelLocationHandler: BroadcastsLocationEventwhen device location changes
Consumers register via requestUpdates(consumer: (Event) -> Unit) and receive callbacks when events are broadcast. The last event is cached and immediately sent to new consumers.
Two interchangeable data provider implementations:
- JsonRpcDataProvider (default): JSON-RPC over HTTP with incremental updates
- BlitzortungHttpDataProvider: HTTP GET with GZIP compression, requires auth
Factory: DataProviderFactory selects provider based on user preference
- Manages data fetch lifecycle and coordinates updates
- Caching:
DataCachewith 5-minute TTL to avoid redundant API calls - Sequencing:
SequenceValidatorprevents stale data from overwriting fresh data - Animation mode: Special handling for historical playback
- Responds to location changes by updating grid parameters
- Publishes
DataEventto all registered consumers
Typical flow: Timer triggers → updateData() → check cache → FetchDataTask (background) → API call → cache result → broadcast ResultEvent → consumers update UI/alerts
AlertHandler: Coordinates alert monitoring
- Subscribes to
DataEvent(strike updates) andLocationEvent(position changes) - Delegates computation to
AlertDataHandler - Broadcasts
AlertEventto UI components andAlertSignal(vibration/sound)
AlertDataHandler: Core alert logic
- Divides 360° into sectors (default 8: N, NE, E, SE, S, SW, W, NW)
- Each sector has range buckets (10km, 25km, 50km, 100km, 250km, 500km)
- For each strike: calculates bearing + distance, assigns to sector/range
- Tracks closest strike in configurable time window (default 10 minutes)
- Returns
AlertResultwith sector data and closest strike distance/bearing
AlertResult: Contains sector-wise strike counts and closest strike metadata for UI display
Overlays (drawn in order):
FadeOverlay: Applies alpha transparency based on strike ageStrikeListOverlay: Renders individual strikes with age-based coloring (red→yellow→blue)OwnLocationOverlay: Shows user's current position
Color Handlers:
StrikeColorHandler: Maps strike timestamp to color gradient- Grid boundary rendering when in grid mode
Overlays subscribe to events for automatic updates:
DataEvent→ add/expire strikesLocationEvent→ update position markerZoomEvent→ adjust rendering detail
Main Activity (foreground):
onResume(): Registers all consumers, starts location updates, enables automatic data refreshonPause(): Unregisters consumers, stops location updates, optionally starts AppService for background alerts
AppService (background):
- Runs as foreground service with notification
- Minimal UI updates (no map rendering)
- Scheduled via AlarmManager for periodic data fetches
- Only active when background alerts are enabled
org.blitzortung.android/
├── alert/ Alert computation, sector handling, alert events
├── app/ Main activity, AppService, boot receiver, UI controllers
│ ├── components/ Version checking, changelog
│ ├── controller/ History, notifications, button column
│ ├── permission/ Runtime permission requesters
│ └── view/ Custom views (AlertView, HistogramView, LegendView, etc.)
├── dagger/ DI component and modules
├── data/ Data models, caching, data handlers, fetch tasks
│ ├── beans/ Strike, Station, GridElement, Location
│ ├── cache/ DataCache implementation
│ └── provider/ DataProvider interface, factory, implementations
├── dialogs/ Info, log, alert, quick settings dialogs
├── jsonrpc/ JSON-RPC client for API communication
├── location/ LocationHandler, location providers
├── map/ MapFragment, overlays, color handlers
├── protocol/ Event interfaces, ConsumerContainer
├── settings/ SettingsFragment, preference handling
├── util/ Time formatting, measurement systems, period utilities
└── widget/ App widget provider
Test Framework: JUnit 4 + Robolectric for Android components
Key Test Utilities:
MockK: Mocking framework (preferred over Mockito for Kotlin)AssertJ: Fluent assertionsRobolectric: Android framework emulation for unit tests
Test Patterns:
- Mock external dependencies (SharedPreferences, Context, API clients)
- Use
@Beforefor common setup - Test public interface behavior, not implementation details
Running Single Tests: Use --tests flag with fully qualified test name:
./gradlew testDebugUnitTest --tests "org.blitzortung.android.data.ParametersTest"Many components implement OnSharedPreferenceChangeListener to react to settings changes. Register in onResume(), unregister in onPause().
All activities/fragments use ViewBinding (enabled in build.gradle). Access views via binding.viewId instead of findViewById().
- Main thread: UI updates, Handler callbacks
- Background threads: Network I/O (FetchDataTask), WorkManager tasks
- Use
Handler.post()to marshal callbacks to main thread
Location providers are abstracted via LocationProvider interface. Implementations:
GPSLocationProvider: Device GPSManualLocationProvider: User-specified coordinatesNetworkLocationProvider: Network-based location
Preference keys are defined in PreferenceKey enum. Use these constants instead of hardcoded strings.
GitHub Actions workflows:
- build.yml: Runs on push/PR, executes tests + coverage + SonarQube analysis
- dependabot.yml: Automated dependency updates
- scorecard.yml: OpenSSF security scorecard
- dependency-review.yml: Dependency vulnerability scanning
- Code Coverage: Generated by JaCoCo, viewable at
app/build/reports/jacoco/jacocoTestReport/html/index.html - Lint Reports: Generated at
app/build/reports/lint-results-debug.xml - minSdkVersion 21: Minimum Android 5.0 (Lollipop)
- Translations: Strings in
res/values-{locale}/strings.xml(10+ languages supported) - ChangeLog: Parsed from
ChangeLogfile and displayed in app on first run after update
- Implement
DataProviderinterface indata.provider.data/ - Register in
DataProviderFactory - Add corresponding
DataProviderTypeenum value - Update SharedPreferences to allow selection
- Modify
AlertParametersto include new configuration - Update
AlertDataHandler.checkStrikes()logic - Extend
AlertResultif new metadata needed - Update
AlertViewto display new information
- Extend
Overlayclass and implementLayerOverlaymarker interface - Implement
draw()method for canvas rendering - Optionally implement
MapListenerfor zoom/scroll events - Register overlay in
Main.onStart()and add tomapView.overlays - Subscribe to relevant events for data updates
- Add entry to
PreferenceKeyenum - Add XML definition in
res/xml/preferences.xml - Implement
OnSharedPreferenceChangeListenerin affected components - Update default values in
PreferenceManager.setDefaultValues()call