flutter-mapbox-gl / maps

A Mapbox GL flutter package for creating custom maps
Other
1.03k stars 496 forks source link

[iOS] Reopening map offline throws exception from SQLite and breaks #703

Open AAverin opened 2 years ago

AAverin commented 2 years ago

Neither onMapReady nor onStyleLoaded are not called when device is offline

AAverin commented 2 years ago

I suspect that style files are actually somewhere online and because in the implementation didFinishLoading style and not didFinishLoadingMap is used as a trigger that map is ready, if style is not accessible map is never ready in Flutter.

Is there a way to put styles offline too? Why doesn't this happen on Android?

Some logs that I get

2021-10-03 09:32:23.132188+0200 Runner[1843:2050389] Connection 117: received failure notification
2021-10-03 09:32:23.132224+0200 Runner[1843:2050389] Connection 117: failed to connect 1:50, reason -1
2021-10-03 09:32:23.132238+0200 Runner[1843:2050389] Connection 117: encountered error(1:50)
2021-10-03 09:32:23.132764+0200 Runner[1843:2050389] Task <5684C397-827F-4E36-8BE1-8183B6B5292F>.<11> HTTP load failed, 0/0 bytes (error code: -1009 [1:50])
2021-10-03 09:32:23.133185+0200 Runner[1843:2050391] Task <5684C397-827F-4E36-8BE1-8183B6B5292F>.<11> finished with error [-1009] Error Domain=NSURLErrorDomain Code=-1009 "The Internet connection appears to be offline." UserInfo={_kCFStreamErrorCodeKey=50, NSUnderlyingError=0x283ce7ab0 {Error Domain=kCFErrorDomainCFNetwork Code=-1009 "(null)" UserInfo={_kCFStreamErrorCodeKey=50, _kCFStreamErrorDomainKey=1}}, _NSURLErrorFailingURLSessionTaskErrorKey=LocalDataTask <5684C397-827F-4E36-8BE1-8183B6B5292F>.<11>, _NSURLErrorRelatedURLSessionTaskErrorKey=(
    "LocalDataTask <5684C397-827F-4E36-8BE1-8183B6B5292F>.<11>"
), NSLocalizedDescription=The Internet connection appears to be offline., NSErrorFailingURLStringKey=https://events.mapbox.com/events/v2?access_token=s..., NSErrorFailingURLKey=https://events.mapbox.com/events/v2?access_token=..., _kCFStreamErrorDomainKey=1}
2021-10-03 09:32:29.170930+0200 Runner[1843:2050389] Connection 118: received failure notification
2021-10-03 09:32:29.170973+0200 Runner[1843:2050389] Connection 118: failed to connect 1:50, reason -1
2021-10-03 09:32:29.171003+0200 Runner[1843:2050389] Connection 118: encountered error(1:50)
2021-10-03 09:32:29.172025+0200 Runner[1843:2050389] Task <0F420101-5251-48D3-98FE-2F920E68A9A5>.<108> HTTP load failed, 0/0 bytes (error code: -1009 [1:50])
2021-10-03 09:32:29.172229+0200 Runner[1843:2050389] Task <0F420101-5251-48D3-98FE-2F920E68A9A5>.<108> finished with error [-1009] Error Domain=NSURLErrorDomain Code=-1009 "The Internet connection appears to be offline." UserInfo={_kCFStreamErrorCodeKey=50, NSUnderlyingError=0x283ce2730 {Error Domain=kCFErrorDomainCFNetwork Code=-1009 "(null)" UserInfo={_kCFStreamErrorCodeKey=50, _kCFStreamErrorDomainKey=1}}, _NSURLErrorFailingURLSessionTaskErrorKey=LocalDataTask <0F420101-5251-48D3-98FE-2F920E68A9A5>.<108>, _NSURLErrorRelatedURLSessionTaskErrorKey=(
    "LocalDataTask <0F420101-5251-48D3-98FE-2F920E68A9A5>.<108>"
), NSLocalizedDescription=The Internet connection appears to be offline., NSErrorFailingURLStringKey=https://api.mapbox.com/styles/v1/mapbox/streets-v11?sku=100RqKiN39k2f64dae1bfc0b11ffe0564e296d139dc&access_token=..., NSErrorFailingURLKey=https://api.mapbox.com/styles/v1/mapbox/streets-v11?sku=100RqKiN39k2f64dae1bfc0b11ffe0564e296d139dc&access_token=..., _kCFStreamErrorDomainKey=1}
2021-10-03 09:32:30.183758+0200 Runner[1843:2050389] Connection 119: received failure notification
2021-10-03 09:32:30.184189+0200 Runner[1843:2050389] Connection 119: failed to connect 1:50, reason -1
2021-10-03 09:32:30.184308+0200 Runner[1843:2050389] Connection 119: encountered error(1:50)
2021-10-03 09:32:30.186569+0200 Runner[1843:2054072] Task <C1B164E1-921A-4EC9-987B-F867E39F8723>.<109> HTTP load failed, 0/0 bytes (error code: -1009 [1:50])
2021-10-03 09:32:30.187461+0200 Runner[1843:2050389] Task <C1B164E1-921A-4EC9-987B-F867E39F8723>.<109> finished with error [-1009] Error Domain=NSURLErrorDomain Code=-1009 "The Internet connection appears to be offline." UserInfo={_kCFStreamErrorCodeKey=50, NSUnderlyingError=0x283ca91d0 {Error Domain=kCFErrorDomainCFNetwork Code=-1009 "(null)" UserInfo={_kCFStreamErrorCodeKey=50, _kCFStreamErrorDomainKey=1}}, _NSURLErrorFailingURLSessionTaskErrorKey=LocalDataTask <C1B164E1-921A-4EC9-987B-F867E39F8723>.<109>, _NSURLErrorRelatedURLSessionTaskErrorKey=(
    "LocalDataTask <C1B164E1-921A-4EC9-987B-F867E39F8723>.<109>"
), NSLocalizedDescription=The Internet connection appears to be offline., NSErrorFailingURLStringKey=https://api.mapbox.com/styles/v1/mapbox/streets-v11?sku=100RqKiN39k2f64dae1bfc0b11ffe0564e296d139dc&access_token=..., NSErrorFailingURLKey=https://api.mapbox.com/styles/v1/mapbox/streets-v11?sku=100RqKiN39k2f64dae1bfc0b11ffe0564e296d139dc&access_token=..., _kCFStreamErrorDomainKey=1}
2021-10-03 09:32:32.199082+0200 Runner[1843:2050389] Connection 120: received failure notification
2021-10-03 09:32:32.199245+0200 Runner[1843:2050389] Connection 120: failed to connect 1:50, reason -1
2021-10-03 09:32:32.199347+0200 Runner[1843:2050389] Connection 120: encountered error(1:50)
2021-10-03 09:32:32.202032+0200 Runner[1843:2050389] Task <487B69EE-224B-42B0-8E95-41AFCCA7E7A7>.<110> HTTP load failed, 0/0 bytes (error code: -1009 [1:50])
2021-10-03 09:32:32.202822+0200 Runner[1843:2050389] Task <487B69EE-224B-42B0-8E95-41AFCCA7E7A7>.<110> finished with error [-1009] Error Domain=NSURLErrorDomain Code=-1009 "The Internet connection appears to be offline." UserInfo={_kCFStreamErrorCodeKey=50, NSUnderlyingError=0x283cafdb0 {Error Domain=kCFErrorDomainCFNetwork Code=-1009 "(null)" UserInfo={_kCFStreamErrorCodeKey=50, _kCFStreamErrorDomainKey=1}}, _NSURLErrorFailingURLSessionTaskErrorKey=LocalDataTask <487B69EE-224B-42B0-8E95-41AFCCA7E7A7>.<110>, _NSURLErrorRelatedURLSessionTaskErrorKey=(
    "LocalDataTask <487B69EE-224B-42B0-8E95-41AFCCA7E7A7>.<110>"
), NSLocalizedDescription=The Internet connection appears to be offline., NSErrorFailingURLStringKey=https://api.mapbox.com/styles/v1/mapbox/streets-v11?sku=100RqKiN39k2f64dae1bfc0b11ffe0564e296d139dc&access_token=..., NSErrorFailingURLKey=https://api.mapbox.com/styles/v1/mapbox/streets-v11?sku=100RqKiN39k2f64dae1bfc0b11ffe0564e296d139dc&access_token=..., _kCFStreamErrorDomainKey=1}
2021-10-03 09:32:36.212156+0200 Runner[1843:2054071] Connection 121: received failure notification
2021-10-03 09:32:36.212295+0200 Runner[1843:2054071] Connection 121: failed to connect 1:50, reason -1
2021-10-03 09:32:36.212399+0200 Runner[1843:2054071] Connection 121: encountered error(1:50)
2021-10-03 09:32:36.214260+0200 Runner[1843:2054072] Task <9E16A306-93AC-44B2-9C9C-2C5A56FEB2CF>.<111> HTTP load failed, 0/0 bytes (error code: -1009 [1:50])
2021-10-03 09:32:36.215052+0200 Runner[1843:2054072] Task <9E16A306-93AC-44B2-9C9C-2C5A56FEB2CF>.<111> finished with error [-1009] Error Domain=NSURLErrorDomain Code=-1009 "The Internet connection appears to be offline." UserInfo={_kCFStreamErrorCodeKey=50, NSUnderlyingError=0x283caad00 {Error Domain=kCFErrorDomainCFNetwork Code=-1009 "(null)" UserInfo={_kCFStreamErrorCodeKey=50, _kCFStreamErrorDomainKey=1}}, _NSURLErrorFailingURLSessionTaskErrorKey=LocalDataTask <9E16A306-93AC-44B2-9C9C-2C5A56FEB2CF>.<111>, _NSURLErrorRelatedURLSessionTaskErrorKey=(
    "LocalDataTask <9E16A306-93AC-44B2-9C9C-2C5A56FEB2CF>.<111>"
), NSLocalizedDescription=The Internet connection appears to be offline., NSErrorFailingURLStringKey=https://api.mapbox.com/styles/v1/mapbox/streets-v11?sku=100RqKiN39k2f64dae1bfc0b11ffe0564e296d139dc&access_token=..., NSErrorFailingURLKey=https://api.mapbox.com/styles/v1/mapbox/streets-v11?sku=100RqKiN39k2f64dae1bfc0b11ffe0564e296d139dc&access_token=..., _kCFStreamErrorDomainKey=1}
AAverin commented 2 years ago

The style itself is, of course, bundled into the offline package and should be used by default instead of loading it from the network and failing. And it seems to be used the first time map is opened, but every subsequent opening results in error

AAverin commented 2 years ago

The reason behind is a leak. I will continue investigation if it's in my codebase or in mapbox for ios

AAverin commented 2 years ago

https://github.com/tobrun/flutter-mapbox-gl/issues/389

AAverin commented 2 years ago

Fixing the leaks, it doesn't seem to be related.

AAverin commented 2 years ago

The first start of the map, when offline map loads correctly, the errors look like

mapViewDidFailLoadingMap Error Domain=MGLErrorDomain Code=-1 "The map failed to load because an unknown error occurred." UserInfo={NSLocalizedDescription=The map failed to load because an unknown error occurred., NSLocalizedFailureReason=Failed to load tile 2/2/0=>2 for source composite: The Internet connection appears to be offline.}

The second start of the screen with the map, when offline map does not display anymore correctly, errors look like:

mapViewDidFailLoadingMap Error Domain=MGLErrorDomain Code=5 "The map failed to load because the style can't be loaded." UserInfo={NSLocalizedDescription=The map failed to load because the style can't be loaded., NSLocalizedFailureReason=loading style failed: The Internet connection appears to be offline.}

Nothing changes between two screens, second screen with the map just doesn't work because it can't load style even though it's already bundled into the map database that was side-loaded.

@m0nac0 Do you have any iOS knowledge or maybe ideas why this could be happening?

m0nac0 commented 2 years ago

Unfortunately, my iOS dev knowledge is very limited, so I'm afraid I don't have an idea of what is going on here.

@TimothySealy wrote a lot of the iOS code for this plugin, maybe he has got an idea?

AAverin commented 2 years ago

I have also noticed this in logs

[logging] BUG IN CLIENT OF libsqlite3.dylib: database integrity compromised by API violation: vnode unlinked while in use: /private/var/mobile/Containers/Data/Application/E9F89FE6-3545-416A-A344-313E5A65A875/Library/Application Support/___/.mapbox/cache.db
[logging] invalidated open fd: 48 (0x11)
AAverin commented 2 years ago

This gets outputed on second call to installOfflineMapTiles method. @TimothySealy I would really appreciate some help or input on this ticket, offline support is critical for my project release and it's the last thing that needs fixing before I can roll out the app to customers.

AAverin commented 2 years ago

I have also opened https://github.com/mapbox/mapbox-gl-native-ios/issues/637 because it's highly likely issue is somewhere in original Mapbox-iOS SDK

AAverin commented 2 years ago

It looks like original mapbox-gl-native-ios SDK is not in active development for the last 2 years, they have probably been working on new v10 implementation

raphaaugustosilva commented 2 years ago

@AAverin how did you managed to workaround this situation? I need to initialize mapbox map when offline too.

AAverin commented 2 years ago

@raphaaugustosilva I didn't. I created mapbox/mapbox-gl-native-ios#637 for the original mapbox ios SDK because the error is coming from it, so it's likely that cause is also inside original SDK. Didn't get any response there yet.

Another theoretical possibility are memory leaks that are now investigated in https://github.com/tobrun/flutter-mapbox-gl/pull/706 and https://github.com/tobrun/flutter-mapbox-gl/pull/710 https://github.com/flutter/flutter/issues/91508 is tracking leaks in flutter engine itself.

All in all, I don't know how to solve this problem yet. Some input of experienced iOS developer is needed here.

@raphaaugustosilva If you will manage to reproduce the issue using original Mapbox iOS SDK sample app and post the example here, it would help greately with investigation

raphaaugustosilva commented 2 years ago

@AAverin thanks! Unfortunately, I am only going to use Mapbox using Flutter (Android and iOS), but my core is to work offline.

I also tried to download offline region in background with internet before opening the map for the first time without internet (using flutter mapbox downloadOfflineRegion method), but the error is the same.

Thanks for the inputs, I`ll subscribe here in order to get more updates.

AAverin commented 2 years ago

It seems that database communication is implemented in the C++ layer of Mapbox. on iOS everything is supposed to correctly destroy in dealloc, including database connections. This leads me to thinking again that memory leaks in the main culprit. With flutter/flutter#91508 Flutter seems to retain in memory at least one instance of the widget, including MapboxMap. If that widget was not correctly destroyed dealloc was not invoked in the Swift implementation and database connection wasn't correctly teared down.

AAverin commented 2 years ago

Another major flutter issue related to memory leaks: https://github.com/flutter/flutter/issues/79605

AAverin commented 2 years ago

Debugging some more I can confirm that even if there are completely no memory leaks in Flutter code Mapbox on iOS still throws BUG IN CLIENT OF libsqlite3.dylib: database integrity compromised by API violation: vnode unlinked while in use: /private/var/mobile/Containers/Data/Application/E9F89FE6-3545-416A-A344-313E5A65A875/Library/Application Support/___/.mapbox/cache.db That would mean that database connection is not properly cleaned either by Mapbox iOS SDK or by MapboxGL native implementation.

@tobrun Is there any way to get some insight from Mapbox developers on this topic?

AAverin commented 2 years ago

Some issues that can be related: https://github.com/mapbox/mapbox-gl-native/issues/16502 https://github.com/mapbox/mapbox-gl-native/issues/13282 https://github.com/mapbox/mapbox-gl-native-ios/issues/486

AAverin commented 2 years ago

I can reproduce the bug with the following minimal example.

The problem seems to be that MapboxMap is holding connection to cache.db even when map is no longer displayed on the screen and should have been cleared. Installing offline tiles tries to replace cache.db with a different offline set of tiles and fails with sql error. Same thing doesn't happen on Android, replacing cache.db when app is running works fine.

Because I have eliminated all possible issues in dart code I think problem lies in ios implementation of mapbox sdk. @tobrun is there any way Mapbox iOS devs could investigate the problem mapbox/mapbox-gl-native-ios#637?

import 'dart:math';

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

void main() async {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      initialRoute: "/",
      onGenerateRoute: (settings) {
        switch (settings.name) {
          case "/":
            return MaterialPageRoute(builder: (_) {
              return HomePage();
            });
          case "/map":
            return MaterialPageRoute(builder: (_) {
              return MapPage();
            });
          default:
            return MaterialPageRoute(
                settings: const RouteSettings(name: "error"),
                builder: (_) {
                  return Scaffold(
                    appBar: AppBar(
                      title: const Text("error"),
                    ),
                  );
                });
        }
      },
    );
  }
}

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Home page"),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () async {
          // uncomment this to install offline tile
          // try {
          //   await installOfflineMapTiles("assets/61.db");
          // } catch (err) {
          //   print(err);
          // }
          await Navigator.of(context).pushNamed("/map");
        },
        child: const Icon(Icons.navigation),
        backgroundColor: Colors.green,
      ),
    );
  }
}

class MapPage extends StatefulWidget {
  @override
  State<MapPage> createState() => _MapPageState();
}

class _MapPageState extends State<MapPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: const Text("Map page"),
        ),
        body: MapboxMap(
          initialCameraPosition:
              const CameraPosition(target: LatLng(52.5200, 13.4050), zoom: 15),
        ));
  }
}
stale[bot] commented 2 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

AAverin commented 2 years ago

Issue is still relevant and not fixed

stale[bot] commented 2 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

AAverin commented 2 years ago

Issue is still relevant and not fixed downstream

BartoszStasiurka commented 1 year ago

Hi, I faced the same problem. Is there any workaround?

AAverin commented 1 year ago

@BartoszStasiurka If issue is still there, then probably it is related to some problem in the underlying mapbox ios sdk and it won't be fixed. New official Mapbox plugin can be found here: https://pub.dev/packages/mapbox_maps_flutter Offline is not yet supported as far as I know, but they do accept feature requests

raphael-bmec-co commented 2 months ago

@AAverin we are using this package in production for Android. Our customer is asking after an iOS port. Is this issue still a known issue? We run mostly offline so if it is likely to crash we need to advise them to wait on mapbox_maps_flutter.

AAverin commented 2 months ago

@raphael-bmec-co I have removed offline support from my product for now and didn't test since then. New official mapbox still doesn't support offline and I couldn't get any official feedback from Mapbox on the roadmap and plans. You can try asking in their Discord server. But for me it doesn't look like Mapbox is investing into Flutter, so if you need offline, best bet would be to use community plugins and serve your own tiles instead of mapbox.

raphael-bmec-co commented 2 months ago

Thanks @AAverin. Appreciate the feedback.