Skip to content

Pause tile retries when device is offline #136

@dougborg

Description

@dougborg

Problem

The exponential backoff for tile retries (2s → 4s → 8s → ... → 60s cap) works well for transient network errors, but when the device is fully offline (airplane mode, no wifi/cellular), retrying is pointless. It burns through the backoff progression and spams logs with DNS failures like:

ClientException with SocketException: Failed host lookup: 'tile.openstreetmap.org'
[TileLayerManager] Tile error at 14/2620/6333, scheduling retry in 2s
[TileLayerManager] Tile error at 14/2620/6333, scheduling retry in 4s
[TileLayerManager] Tile error at 14/2620/6333, scheduling retry in 8s
...

Proposed solution

Add connectivity_plus package to detect when the OS reports no network interfaces, and wire it into TileLayerManager:

  1. When offline (no wifi, no cellular): cancel the retry timer and stop scheduling new retries
  2. When connectivity restores: fire the reset stream immediately, reset backoff to 2s → tiles retry instantly

Limitation

connectivity_plus detects network interface availability (wifi/cellular up), not actual internet reachability. If the interface is up but DNS fails (e.g., captive portal, USB-only), the exponential backoff still handles it. This is the right trade-off — catches the common case (airplane mode, wifi off) without heavyweight reachability probes.

Implementation notes

Dependencies

  • Add connectivity_plus: ^6.1.0 to pubspec.yaml
  • Add ACCESS_NETWORK_STATE permission to android/app/src/main/AndroidManifest.xml (currently only has INTERNET)

Key changes to lib/widgets/map/tile_layer_manager.dart

// New fields
StreamSubscription<List<ConnectivityResult>>? _connectivitySub;
bool _isConnected = true;

// In initialize():
_connectivitySub = Connectivity().onConnectivityChanged.listen((results) {
  final connected = results.any((r) => r != ConnectivityResult.none);
  if (connected && !_isConnected) {
    _isConnected = true;
    _retryDelay = _minRetryDelay;
    _resetController.add(null); // Retry immediately
  } else if (!connected && _isConnected) {
    _isConnected = false;
    _retryTimer?.cancel(); // Stop retrying
  }
});

// In scheduleRetry():
if (!_isConnected) return; // Skip when offline

// In dispose():
_connectivitySub?.cancel();

Current state of retry system

  • TileLayerManager already has exponential backoff: 2s → 4s → 8s → 16s → 32s → 60s cap
  • onTileLoadSuccess() resets backoff to 2s when a tile loads
  • scheduleRetry() is @visibleForTesting — connectivity tests can call it directly
  • Existing test suite: 9 backoff tests in test/widgets/map/tile_layer_manager_test.dart

Existing infrastructure

  • NetworkStatus service (lib/services/network_status.dart) tracks request-level status (loading/success/error) — NOT device connectivity. Don't conflate the two.
  • AppState.offlineMode is a user-controlled manual toggle — separate concern
  • No existing connectivity detection package in the project

Tests to add

  • scheduleRetry is a no-op when offline
  • Connectivity restored fires reset stream and resets backoff
  • Connectivity lost cancels pending retry timer

Expected behavior after fix

Scenario Current After
Airplane mode on Retries every 2s→60s forever Stops retrying, waits for connectivity
Airplane mode off Waits up to 60s for next retry Retries immediately, backoff reset to 2s
Spotty wifi Exponential backoff Same (connectivity_plus still reports "connected")
Captive portal Exponential backoff Same

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions