driving_weather 0.5.0 copy "driving_weather: ^0.5.0" to clipboard
driving_weather: ^0.5.0 copied to clipboard

Real-time weather monitoring with snow, ice, and visibility hazard detection. Open-Meteo integration (free, no API key). Pure Dart.

driving_weather #

pub package CI License: BSD-3-Clause

Know when road conditions turn dangerous. Real-time weather monitoring with snow, ice, and visibility hazard detection — no API key required.

Use driving_weather when your app needs to warn drivers about hazardous conditions. Pluggable providers: real weather from Open-Meteo (free) or simulated scenarios for demos and testing.

Features #

  • WeatherCondition — Equatable model with precipitation type/intensity, temperature, visibility, wind speed, and ice risk
  • WeatherProvider — Abstract interface for pluggable weather data sources
  • OpenMeteoWeatherProvider — Real weather from Open-Meteo (free, no API key) with offline fallback
  • SimulatedWeatherProvider — Demo provider with a realistic mountain-pass snow scenario

It will not tell you something it did not measure #

This is the package's central promise, and in versions up to 0.4.4 it was broken — an empty advisory feed produced a fabricated +5.0 °C, no ice. See CHANGELOG 0.5.0 for the full disclosure and migration table.

  • Every measured field is nullable. null means NOT MEASURED — never zero, never benign, never "clear".
  • Safety verdicts are tri-state (SafetyVerdict), never bool. A bool cannot say "I don't know", so it resolves absence into its false branch — which for a hazard question is the clear road branch.
  • A failed fetch is a WeatherStale or WeatherUnavailable reading, not a silently re-dated old value.

Positive hazard evidence fires even on partial data; only the negative verdict ("all clear") requires complete data. So going offline does not raise a false alarm — it reports unknown, which you can actually tell the driver.

Install #

dependencies:
  driving_weather: ^0.5.0

Quick Start #

import 'package:driving_weather/driving_weather.dart';

// Real weather from Open-Meteo (Nagoya region default)
final provider = OpenMeteoWeatherProvider(
  latitude: 35.18,
  longitude: 136.91,
);
await provider.startMonitoring();

provider.conditions.listen((reading) {
  switch (reading) {
    case WeatherObserved(:final condition):
      switch (condition.hazard) {
        case SafetyVerdict.hazardous:
          print('⚠ Hazardous conditions detected');
        case SafetyVerdict.notHazardous:
          print('Assessed: no hazard');
        case SafetyVerdict.unknown:
          // NOT a green light. Nobody measured the road — say so.
          print('Conditions unavailable — drive to what you can see');
      }
    case WeatherStale(:final lastKnown, :final age):
      print('Stale (${age.inMinutes} min old): ${lastKnown.precipType?.name}');
    case WeatherUnavailable():
      print('No weather data');
  }
});

Simulated weather (for demos and testing) #

final sim = SimulatedWeatherProvider(
  interval: Duration(seconds: 5),
);
await sim.startMonitoring();
// Cycles: clear → light snow → moderate → heavy → ice → clearing

Integration Pattern #

The usual app pattern is: start one weather provider in initState, subscribe through StreamBuilder, and convert the raw condition into a compact status bar or alert strip. This keeps the weather source swappable while the UI stays stable.

import 'package:driving_weather/driving_weather.dart';
import 'package:flutter/material.dart';

class WeatherBanner extends StatefulWidget {
  const WeatherBanner({super.key});

  @override
  State<WeatherBanner> createState() => _WeatherBannerState();
}

class _WeatherBannerState extends State<WeatherBanner> {
  late final WeatherProvider provider;

  @override
  void initState() {
    super.initState();
    provider = SimulatedWeatherProvider(
      interval: const Duration(seconds: 5),
    )..startMonitoring();
  }

  @override
  void dispose() {
    provider.stopMonitoring();
    provider.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return StreamBuilder<WeatherReading>(
      stream: provider.conditions,
      builder: (context, snapshot) {
        final reading = snapshot.data;
        if (reading == null) {
          return const Text('Loading weather...');
        }

        return switch (reading) {
          WeatherObserved(:final condition) => ListTile(
              title: Text(
                '${condition.precipType?.name ?? 'unknown'} '
                '${condition.intensity?.name ?? ''}',
              ),
              subtitle: Text(_describe(condition)),
              trailing: switch (condition.hazard) {
                SafetyVerdict.hazardous =>
                  const Icon(Icons.warning_amber_rounded),
                SafetyVerdict.notHazardous =>
                  const Icon(Icons.cloud_outlined),
                // Absence is shown as absence — never as a calm sky.
                SafetyVerdict.unknown => const Icon(Icons.help_outline),
              },
            ),
          WeatherStale(:final age) => ListTile(
              title: const Text('Weather data is stale'),
              subtitle: Text('Last observed ${age.inMinutes} min ago'),
              trailing: const Icon(Icons.history),
            ),
          WeatherUnavailable() => const ListTile(
              title: Text('No weather data'),
              subtitle: Text('Conditions unknown — drive to what you can see'),
              trailing: Icon(Icons.cloud_off),
            ),
        };
      },
    );
  }

  // Render absence as absence. Never substitute a value.
  String _describe(WeatherCondition c) {
    final visibility = c.visibilityMeters == null
        ? 'Visibility unknown'
        : 'Visibility ${c.visibilityMeters!.toStringAsFixed(0)}m';
    final ice = switch (c.iceRisk) {
      true => 'Ice risk yes',
      false => 'Ice risk no',
      null => 'Ice risk unknown',
    };
    return '$visibility • $ice';
  }
}

Swap SimulatedWeatherProvider for OpenMeteoWeatherProvider when you move from demo mode to live weather. The widget contract does not have to change.

Custom weather source #

class MyFleetWeatherProvider implements WeatherProvider {
  // Implement the 4 methods: conditions, startMonitoring,
  // stopMonitoring, dispose
}

API Overview #

Type Purpose
WeatherCondition Snapshot of precipitation, temperature, visibility, wind, and ice risk — each of which may be absent.
WeatherReading Sealed: WeatherObserved | WeatherStale | WeatherUnavailable. What a provider actually hands you.
SafetyVerdict hazardous | notHazardous | unknown. The third one is the point.
ObservationSource measured | derived | simulated — provenance of the fields that are present.
HazardAssertion A hazard declared by a road authority, as distinct from one measured by a sensor.
WeatherProvider Abstract interface for live or simulated weather sources.
OpenMeteoWeatherProvider Pulls real weather data; reports staleness rather than concealing it.
DigitrafficWeatherProvider Finnish road-authority advisories, carried as an assertion — never as invented sensor values.
SimulatedWeatherProvider Deterministic demo/test sequences, labelled simulated.

Model #

Every measured field is nullable, and null means NOT MEASURED.

Field Type Description
precipType PrecipitationType? none, rain, snow, sleet, hail. nullnone.
intensity PrecipitationIntensity? none, light, moderate, heavy
temperatureCelsius double? °C. null never means "above freezing".
visibilityMeters double? 10000 = clear, <1000 = reduced, <200 = hazardous. null never means "clear".
windSpeedKmh double? null never means "calm".
iceRisk bool? Black ice / road icing risk. null never means "no ice".
humidityRH double? Relative humidity %, for radiative-frost black ice.
hazardAssertion HazardAssertion? Severity declared by an authority.
source ObservationSource Provenance of the present fields (required).
timestamp DateTime Observation time

Safety verdicts (tri-state — never bool) #

  • hazard — heavy precip, visibility < 200 m, ice risk, or a severe/extreme authority assertion. Fires on partial data. Returns notHazardous only when ice risk, intensity and visibility are all known and none fired; otherwise unknown.
  • snowingunknown when precipitation was not reported.
  • reducedVisibilityunknown when visibility was not measured.
  • freezingunknown when temperature was not measured.

unknown is not a green light. Show it to the driver.

Safety #

Display and advisory only — does not control vehicle systems. Built with automotive-grade test discipline, usable in any Flutter app.

See SAFETY_BOUNDARY.md for the full safety-class boundary record, including the §0 correction notice and a known SOTIF performance insufficiency that is not fixed in 0.5.0 (sub-zero clear-sky residual ice).

Works With #

Package How
driving_conditions Converts weather into road surface, grip, and safety scores
navigation_safety Displays weather-driven safety alerts to the driver
fleet_hazard Correlates weather with fleet-reported road hazards

See Also #

Part of SNGNav — 11 packages for offline-first navigation on Flutter.

License #

BSD-3-Clause — see LICENSE.

0
likes
160
points
322
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Real-time weather monitoring with snow, ice, and visibility hazard detection. Open-Meteo integration (free, no API key). Pure Dart.

Repository (GitHub)
View/report issues
Contributing

Topics

#weather #driving #navigation #safety #winter-driving

License

BSD-3-Clause (license)

Dependencies

condition_aggregator, condition_aggregator_digitraffic, equatable, http

More

Packages that depend on driving_weather