Skip to content

Failed tiles no longer fall back to other zoom levels since 8.3.0 #2273

Open

Description

@sidlatau

What is the bug?

Since 8.3.0, when tiles fail to load (for example without a connection), the map no longer shows the loaded tiles of other zoom levels in their place: it goes blank.

In 8.2.2, after zooming in while offline, the lower-zoom tiles already loaded stayed visible, scaled up. This is the fallback discussed in #1397. In 8.3.0 to 8.3.2 and on master, the same steps give an empty map.

The cause seems to be #2166. To let tileBuilder draw failed tiles, _onImageLoadError now always calls _display(), which marks the failed tile readyToDisplay even without an errorImage. TileImageView.renderTiles only keeps ancestors and children for tiles that are not ready, so the fallback tiles are dropped. That also contradicts the documentation of readyToDisplay: "Loading errored but an error image is configured."

Found on Android (emulator, API 35) with Flutter 3.47.6, in an app meant to keep working on a spotty connection in the terrain.

How can we reproduce it?

The tile provider below stands in for losing the connection: it serves OpenStreetMap tiles up to zoom 10 and fails above.

import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:latlong2/latlong.dart';

void main() => runApp(const MaterialApp(home: ReproPage()));

class ReproPage extends StatelessWidget {
  const ReproPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: FlutterMap(
        options: const MapOptions(
          initialCenter: LatLng(51.5, -0.09),
          initialZoom: 10,
        ),
        children: [
          TileLayer(
            urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
            userAgentPackageName: 'dev.fleaflet.flutter_map.repro',
            tileProvider: OfflineAboveZoomTileProvider(maxZoom: 10),
          ),
        ],
      ),
    );
  }
}

/// Behaves like a device that lost its connection after loading tiles up to
/// [maxZoom]: tiles above it fail to load.
class OfflineAboveZoomTileProvider extends TileProvider {
  OfflineAboveZoomTileProvider({required this.maxZoom});

  final int maxZoom;

  @override
  ImageProvider getImage(TileCoordinates coordinates, TileLayer options) =>
      NetworkImage(
        coordinates.z <= maxZoom
            ? getTileUrl(coordinates, options)
            : 'https://offline.invalid/${coordinates.z}/${coordinates.x}/${coordinates.y}.png',
        headers: headers,
      );
}
  1. Run it and wait for the tiles to load.
  2. Double-tap twice to zoom in to zoom 12.

Expected (8.2.2): the zoom 10 tiles stay visible, scaled up.
Actual (8.3.0 and later): the map is blank.

Do you have a potential solution?

Keep a failed tile without errorImage not ready to display, but still notify its listeners, so that Tile rebuilds and tileBuilder gets the failed tile:

if (errorImage != null) {
  _display();
} else {
  notifyListeners();
}

The tileBuilder from #2166's example then still draws its text for failed tiles, on top of the fallback tiles, since tiles at the current zoom are drawn last. I have this with a test and can open a PR.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions