Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle exception on move without internet connection #593

Closed
beerline opened this issue Apr 19, 2020 · 14 comments
Closed

Handle exception on move without internet connection #593

beerline opened this issue Apr 19, 2020 · 14 comments

Comments

@beerline
Copy link

beerline commented Apr 19, 2020

Hi flutter_map team!

How I can handling exception while user move or zoom map but internet connect is lost?

Steps to do:
Open app with interne

  1. t connection. As result we see the map
  2. enable "Airplane mode"
  3. tray to zoom the map
  4. getting console errors

Code the same as in this example

console errors

I/flutter (15443): SocketException: Failed host lookup: 'c.tile.openstreetmap.org' (OS Error: No address associated with hostname, errno = 7)
I/flutter (15443): SocketException: Failed host lookup: 'a.tile.openstreetmap.org' (OS Error: No address associated with hostname, errno = 7)
I/flutter (15443): SocketException: Failed host lookup: 'c.tile.openstreetmap.org' (OS Error: No address associated with hostname, errno = 7)
I/flutter (15443): SocketException: Failed host lookup: 'b.tile.openstreetmap.org' (OS Error: No address associated with hostname, errno = 7)
I/flutter (15443): SocketException: Failed host lookup: 'c.tile.openstreetmap.org' (OS Error: No address associated with hostname, errno = 7)
I/flutter (15443): SocketException: Failed host lookup: 'a.tile.openstreetmap.org' (OS Error: No address associated with hostname, errno = 7)
I/flutter (15443): SocketException: Failed host lookup: 'b.tile.openstreetmap.org' (OS Error: No address associated with hostname, errno = 7)
I/flutter (15443): SocketException: Failed host lookup: 'b.tile.openstreetmap.org' (OS Error: No address associated with hostname, errno = 7)
I/flutter (15443): SocketException: Failed host lookup: 'a.tile.openstreetmap.org' (OS Error: No address associated with hostname, errno = 7)

Thank you for your help!

@avioli
Copy link
Contributor

avioli commented Apr 20, 2020

The only way I can think of handling this is using a custom TileProvider, that keeps track of the current connectivity state and only loads tiles if connected to the internet or if a socket exception happened then await connectivity change to retry. It might have to use a custom ImageProvider since getImage is not async.

A plugin, paired with a tile provider might be a better fit, since it has access to the map state, thus will be able to invoke a reload on the visible tiles.

@maRci002
Copy link
Contributor

User won't see console and as far as I know Leaflet will flood console too if there is no internet connection avaible (I tested it on browser). Anyway Flutter caches Network Images even if they loaded with error (forinstance no internet connection) so the Tiles gets stuck, take a look on #577 PR, I am goind to add connection detect strategy soon.

@beerline
Copy link
Author

@maRci002, @avioli thank you for your responses!

Main goal to catch this exception, is to notify user about internet connection lost.

@maRci002 thank you for yuor PR. I will wait for it!

@avioli
Copy link
Contributor

avioli commented Apr 20, 2020

@beerline - this sounds like business logic, that’s not related to the plugin. I would say that this plugin is not supposed to handle the user facing connectivity notifications. It can handle it internally for a better user experience, but it is the developer’s job to handle connectivity state changes and notify the user accordingly in a way that makes sense for the final app.

@beerline
Copy link
Author

beerline commented Apr 21, 2020

@avioli totally agree with you, that this is business logic
I tried to wrap this move call by try catch

try{
  map.move(centerZoom.center, zoom);
} catch (e) {
  // handle error here
}

But since it useing strams under the hood _onMoveSink.add(null);, my catch can not catch this exception.

I also tried to subscribe to subscribe to this stream

map.onMoved(
  (_){}, 
  onError: (error) { 
    // handle error here
  }
);
map.move(centerZoom.center, zoom);

But onError neve execute.

In theory, i can check internet connection before each call map.move, but i am afraid, this will be cause performance issue. And also this solution should by should be done befor each drag of map. So it seams to a lot of boilerplate code.

may be you can suggest better solution how i can handle this exception, i will be grateful to you

@maRci002
Copy link
Contributor

I think the easiest way to do this if you provide some callback method to TileLayerOptions and if options provided callback then fire the callback forinstace here.

if (options.errorTileCallback != null) {
    errorTileCallback(tile, error);
}

Note if you have multiple Tile Layers then you should pass your callback to every layer's TileLayerOptions.

@avioli
Copy link
Contributor

avioli commented Apr 22, 2020

If you are using the default tileProvider: CachedNetworkTileProvider, then you can recreate it with an added errorListener, which is invoked every time a tile cannot be loaded from the internet or the disk. This is only useful if you want to be notified though.

class MyTileProvider extends TileProvider {
  MyTileProvider({ this.onError });

  final VoidCallback onError;

  @override
  ImageProvider getImage(Coords<num> coords, TileLayerOptions options) {
    final url = getTileUrl(coords, options);
    return CachedNetworkImageProvider(url, errorListener: onError);
  }
}

Then in your Widget:

Widget build(BuildContext context) {
  return FlutterMap(
    ...
    layers: [
      TileLayerOptions(
        urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
        subdomains: ['a', 'b', 'c'],
        tileProvider: MyTileProvider(onError: _onTileError),
      ),
    ],
  );
}

void _onTileError() {
  print("uh-oh - a tile couldn't load");
}

Unfortunately, given that errorListener doesn't pass the error you can only know an error has happened, but not what kind.

@avioli
Copy link
Contributor

avioli commented Apr 22, 2020

Then... if you want to go crazy you can try to get a grip of the cache manager that CachedNetworkImageProvider uses by default - DefaultCacheManager, but I would STRONGLY SUGGEST NOT TO...

// other imports
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter_cache_manager/src/web_helper.dart'; // ignore: implementation_imports
import 'package:flutter_map/flutter_map.dart';
import 'package:http/http.dart' as http;

typedef ErrorResponseHandler = Future<http.Response> Function(Object error,
    [StackTrace stack]);

class MyTileProvider extends TileProvider {
  MyTileProvider({this.errorHandler});

  final ErrorResponseHandler errorHandler;

  @override
  ImageProvider getImage(Coords<num> coords, TileLayerOptions options) {
    final url = getTileUrl(coords, options);
    final cacheManager = MyCacheManager.instance..errorHandler = errorHandler;
    return CachedNetworkImageProvider(
      url,
      cacheManager: cacheManager,
    );
  }
}

class MyCacheManager extends BaseCacheManager {
  static MyCacheManager _instance;
  static MyCacheManager get instance => _instance ??= MyCacheManager._();

  MyCacheManager._() : super(DefaultCacheManager.key) {
    webHelper = WebHelper(store, _fetch);
  }

  ErrorResponseHandler errorHandler;

  @override
  Future<String> getFilePath() => DefaultCacheManager().getFilePath();

  Future<FileFetcherResponse> _fetch(String url,
      {Map<String, String> headers}) async {
    try {
      final response = await http.get(url, headers: headers);
      return new HttpFileFetcherResponse(response);
    } catch (e, stack) {
      if (errorHandler != null) {
        final response = await errorHandler(e, stack);
        return new HttpFileFetcherResponse(response);
      }
      rethrow;
    }
  }
}

Then use it like so:

Widget build(BuildContext context) {
  return FlutterMap(
    ...
    layers: [
      TileLayerOptions(
        urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
        subdomains: ['a', 'b', 'c'],
        tileProvider: MyTileProvider(errorHandler: _onTileError), // <-- changed
      ),
    ],
  );
}

// This now gets an error object and a stack trace, but only for internet downloads,
// not for disk failures
Future<http.Response> _onTileError(Object err, [StackTrace stack]) {
  print("uh-oh - a tile couldn't load");
  print("error: $err");
  print(stack);
  return Future.error(err, stack);
}

The errors you'll get look like this:

flutter: Couldn't download or retrieve file.
flutter: uh-oh - a tile couldn't load
flutter: error: SocketException: Failed host lookup: 'b.tile.openstreetmap.org' (OS Error: nodename nor servname provided, or not known, errno = 8)
flutter: #0      IOClient.send (package:http/src/io_client.dart:33:24)
<asynchronous suspension>
#1      BaseClient._sendUnstreamed (package:http/src/base_client.dart:176:38)
#2      BaseClient.get (package:http/src/base_client.dart:35:7)
#3      get.<anonymous closure> (package:http/http.dart:46:36)
#4      _withClient (package:http/http.dart:166:20)
#5      get (package:http/http.dart:46:5)
#6      MyCacheManager._fetch (package:testmap/screens/my_home_page.dart:357:30)
#7      MyCacheManager._staticFetch (package:testmap/screens/my_home_page.dart:352:24)
#8      WebHelper._downloadRemoteFile (package:flutter_cache_manager/src/web_helper.dart:69:26)
<asynchronous suspension>
#9      WebHelper.downloadFile.<anonymous closure> (package:flutter_cache_manager/src/web_helper.dart:36:21)
#10     WebHelper.downloadFile (package:flutter_cache_manager/src/web_helper.dart:43:8)
#11     BaseCacheManager.getSingleFile (package:flutter_cache_manager/src/cache_manager.dart:96:38)
<asynchronous suspension>
<…>

@beerline
Copy link
Author

@avioli thank you for your solution!
But unfortinaltly SocketException failed before checking if (file == null) in CachedNetworkImageProvider._loadAsync. I wrap whole body of method _loadAsync by try catch to show it.

Please see screencast

@beerline
Copy link
Author

Implementrd this solution #593 (comment)

PR: #600

@github-actions
Copy link

This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.

@github-actions github-actions bot added the Stale label Mar 28, 2021
@github-actions
Copy link

github-actions bot commented Apr 2, 2021

This issue was closed because it has been stalled for 5 days with no activity.

@github-actions github-actions bot closed this as completed Apr 2, 2021
@tierecke
Copy link

tierecke commented Jan 6, 2022

Still not solved. Shouldn't it be added as an issue?

@ibrierley
Copy link
Collaborator

Didn't the PR #600 fix it ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants