I am trying to implement a log in/log out functionality using Provider for controlling the map.
Everything runs if I execute the straight flow - log in and use the map.
The problem comes when I log out and try to move the map around, then I get this exception:
The following assertion was thrown building _LayerPainter(state: _LayerState#912db):
A JobSet was used after being disposed.
Once you have called dispose() on a JobSet, it can no longer be used.
The relevant error-causing widget was:
_LayerPainter
_LayerPainter:file:///Users/zhivkovasilev/.pub-cache/git/mapsforge_flutter-fd413b440980d3755f671511400f708fe8e1b34f/mapsforge_flutter/lib/src/vie
w/mapview_widget.dart:229:9
When the exception was thrown, this was the stack:
#0 ChangeNotifier.debugAssertNotDisposed.<anonymous closure> (package:flutter/src/foundation/change_notifier.dart:179:9)
#1 ChangeNotifier.debugAssertNotDisposed (package:flutter/src/foundation/change_notifier.dart:186:6)
#2 ChangeNotifier.addListener (package:flutter/src/foundation/change_notifier.dart:271:27)
#3 CustomPainter.addListener (package:flutter/src/rendering/custom_paint.dart:160:56)
#4 RenderCustomPaint.attach (package:flutter/src/rendering/custom_paint.dart:544:25)
#5 RenderObject.adoptChild (package:flutter/src/rendering/object.dart:1841:13)
#6 ContainerRenderObjectMixin.insert (package:flutter/src/rendering/object.dart:4255:5)
#7 MultiChildRenderObjectElement.insertRenderObjectChild (package:flutter/src/widgets/framework.dart:6843:18)
#8 RenderObjectElement.attachRenderObject (package:flutter/src/widgets/framework.dart:6602:35)
#9 RenderObjectElement.mount (package:flutter/src/widgets/framework.dart:6467:5)
#10 SingleChildRenderObjectElement.mount (package:flutter/src/widgets/framework.dart:6768:11)
#11 Element.inflateWidget (package:flutter/src/widgets/framework.dart:4340:16)
#12 Element.updateChild (package:flutter/src/widgets/framework.dart:3843:20)
#13 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5512:16)
#14 StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5650:11)
#15 Element.rebuild (package:flutter/src/widgets/framework.dart:5203:7)
#16 BuildOwner.buildScope (package:flutter/src/widgets/framework.dart:2905:19)
#17 WidgetsBinding.drawFrame (package:flutter/src/widgets/binding.dart:1136:21)
#18 RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:443:5)
#19 SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1392:15)
#20 SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1313:9)
#21 SchedulerBinding._handleDrawFrame (package:flutter/src/scheduler/binding.dart:1171:5)
#22 _invoke (dart:ui/hooks.dart:312:13)
#23 PlatformDispatcher._drawFrame (dart:ui/platform_dispatcher.dart:419:5)
#24 _drawFrame (dart:ui/hooks.dart:283:31)
════════════════════════════════════════════════════════════════════════════════════════════════════
this is my provider code:
class MapViewModel with ChangeNotifier {
late MapDataStore map;
late JobRenderer jobRenderer;
late SymbolCache symbolCache = FileSymbolCache();
late FileTileBitmapCache bitmapCache;
late DisplayModel displayModel =
DisplayModel(fontScaleFactor: 0.5, maxZoomLevel: 19);
late ViewModel viewModel;
late MapModel mapModel;
bool initialLocationSet = false;
bool _disposed = false; // Flag to track disposal state
MarkerByItemDataStore currentLocationMarkerStore = MarkerByItemDataStore();
MarkerByItemDataStore hutsMarkerstore = MarkerByItemDataStore();
GpsService gpsService = GpsService();
List<Marker> tappedMarker = [];
StreamSubscription<geo.Position>? _gpsSubscription;
Function(int)? onMarkerTappedCallback;
Future<void> initializeMap() async {
_resetState();
try {
bool permissionsGranted = await gpsService.checkPermissionsAndServices();
if (!permissionsGranted) {
throw Exception('Location permissions not granted');
}
await createMapModel();
await createViewModel();
gpsService.startPositioning();
// Start listening to GPS updates
_startGpsListening();
notifyListeners(); // Notify listeners that everything is initialized
} catch (e) {
if (!_disposed) {
print('Failed to initialize map: $e');
throw Exception('Failed to initialize MapViewModel');
}
}
}
void _resetState() {
initialLocationSet = false;
_gpsSubscription?.cancel();
currentLocationMarkerStore = MarkerByItemDataStore(); // Recreate the store
hutsMarkerstore = MarkerByItemDataStore(); // Recreate the store
tappedMarker = [];
}
Future<MapModel> getMapModel() async {
await Future.delayed(const Duration(milliseconds: 500));
return mapModel;
}
Future<ViewModel> getViewModel() async {
await Future.delayed(const Duration(milliseconds: 500));
return viewModel;
}
Future<MapModel> createMapModel() async {
try {
ByteData content =
await rootBundle.load('assets/map/mapstyles/bgmountains.map');
map = await MapFile.using(content.buffer.asUint8List(), null, null);
// Initialize render theme and renderer
final renderTheme = await RenderThemeBuilder.create(
displayModel,
'assets/map/mapstyles/Elevate.xml',
);
jobRenderer = MapDataStoreRenderer(map, renderTheme, symbolCache, true);
bitmapCache =
await FileTileBitmapCache.create(jobRenderer.getRenderKey());
bitmapCache.purgeAll();
mapModel = MapModel(
displayModel: displayModel,
renderer: jobRenderer,
tileBitmapCache: bitmapCache,
symbolCache: symbolCache,
);
mapModel.markerDataStores.add(currentLocationMarkerStore);
mapModel.markerDataStores.add(hutsMarkerstore);
return mapModel;
} catch (e) {
if (!_disposed) {
print('Failed to initialize map: $e');
throw Exception('Failed to initialize MapViewModel');
}
return Future.error('Failed to initialize MapViewModel');
}
}
Future<ViewModel> createViewModel() async {
viewModel = ViewModel(
displayModel: displayModel,
contextMenuBuilder: null, // Remove default ContextMenuBuilder
);
viewModel.observeTap.listen((event) {
if (!_disposed) {
tappedMarker = hutsMarkerstore.isTapped(event);
if (tappedMarker.isNotEmpty && tappedMarker[0] is PoiMarker<int>) {
int hutId = (tappedMarker[0] as PoiMarker<int>).item!;
onMarkerTappedCallback?.call(hutId); // Call the callback if it's set
}
}
});
viewModel.setZoomLevel(18);
return viewModel;
}
void _startGpsListening() {
_gpsSubscription = GpsService.positionStream.listen((position) {
print(
"GPS position received: ${position.latitude}, ${position.longitude}");
if (!initialLocationSet) {
viewModel.setMapViewPosition(position.latitude, position.longitude);
}
_updatePositionMarker(Pos(position.latitude, position.longitude));
});
}
Future<void> _updatePositionMarker(Pos position) async {
if (!_disposed) {
currentLocationMarkerStore.clearMarkers();
final positionMarker = PoiMarker(
displayModel: displayModel,
src: 'assets/images/walk-2.png',
height: 32,
width: 32,
latLong: LatLong(position.latitude, position.longitude),
position: Position.CENTER,
);
await positionMarker.initResources(symbolCache);
currentLocationMarkerStore.addMarker(positionMarker);
currentLocationMarkerStore.setRepaint();
notifyListeners(); // Notify listeners that the marker has been updated
}
}
@override
void dispose() {
_disposed = true; // Set the disposed flag
_gpsSubscription?.cancel();
print('GPS subscription canceled'); // Debugging statement
currentLocationMarkerStore.dispose();
hutsMarkerstore.dispose();
mapModel.dispose();
super.dispose();
}
}
and this is where I init the map:
class AuthWrapper extends StatelessWidget {
const AuthWrapper({Key? key}) : super(key: key);
Future<bool> _checkAuthState() async {
final user = FirebaseAuth.instance.currentUser;
final accessToken = await FacebookAuth.instance.accessToken;
return user != null || accessToken != null;
}
@override
Widget build(BuildContext context) {
return Consumer<AuthService>(
builder: (context, authService, child) {
return FutureBuilder<bool>(
future: _checkAuthState(),
builder: (context, authSnapshot) {
if (authSnapshot.connectionState == ConnectionState.waiting) {
return const LoadingView();
}
if (authSnapshot.hasError ||
!authSnapshot.hasData ||
!authSnapshot.data!) {
return const LoginScreen();
}
return FutureBuilder(
future: Provider.of<MapViewModel>(context, listen: false)
.initializeMap(),
builder: (context, mapSnapshot) {
if (mapSnapshot.connectionState != ConnectionState.done) {
return const LoadingView(); // Show loading until the initialization is done
}
if (mapSnapshot.hasError) {
return Center(
child:
Text('Error initializing map: ${mapSnapshot.error}'));
}
return const MainScaffold(body: MapPage());
},
);
},
);
},
);
}
}
I am trying to implement a log in/log out functionality using Provider for controlling the map. Everything runs if I execute the straight flow - log in and use the map. The problem comes when I log out and try to move the map around, then I get this exception:
this is my provider code:
and this is where I init the map: