I am trying to update a map with markers by using a firestore stream, and then update a different widget that will show this information in a list view widget. I have created a Manager inheriting the ChangeNotifier to handle the stream creation and the update of the markers like this:
class ParkingSpotManager extends ChangeNotifier {
FirebaseFirestore firestoreInstance = FirebaseFirestore.instance;
List<ParkingSpot> parkingSpots = [];
late Stream<QuerySnapshot<Map<String, dynamic>>> stream;
Stream<List<ParkingSpot>> createStream(String collectionPath) {
return firestoreInstance.collection(collectionPath).snapshots().map((event) =>
event.docs.map((e) => ParkingSpot.fromFirestore(e)).toList());
}
void setParkingSpots(List<ParkingSpot> parkingSpotList) {
parkingSpots = parkingSpotList;
notifyListeners();
}
}
main.dart
class FindParking extends StatelessWidget {
FindParking({Key? key}) : super(key: key);
final Future<FirebaseApp> _fbApp = Firebase.initializeApp();
@override
Widget build(BuildContext context) {
final ThemeData theme = FindParkingTheme.dark();
return MaterialApp(
theme: theme,
title: 'FindParking',
home: FutureBuilder(
future: _fbApp,
builder: (context, snapshot) {
if (snapshot.hasError) {
log('You have an error: ${snapshot.error.toString()}');
return const Text('Something went wrong');
} else if (snapshot.hasData) {
return MultiProvider(providers: [
ChangeNotifierProvider(create: (context) => TabManager()),
ChangeNotifierProvider(
create: (context) => ParkingSpotManager())
], child: const Home());
} else {
return const Center(child: CircularProgressIndicator());
}
},
));
}
}
home.dart
@override
Widget build(BuildContext context) {
return Consumer<TabManager> (builder: (context, tabManager, child){
return Scaffold(
appBar: AppBar(
title:
Text('FindParking', style: Theme.of(context).textTheme.headline6),
),
body: IndexedStack(index: tabManager.selectedIndex, children: pages),
bottomNavigationBar: BottomNavigationBar(
currentIndex: tabManager.selectedIndex,
selectedItemColor: Theme.of(context).textSelectionTheme.selectionColor,
onTap: (index) {
tabManager.goToTab(index);
},
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.map),
label: 'Map',
),
BottomNavigationBarItem(
icon: Icon(Icons.list),
label: "List",
),
],
),
);
});
}
}
map.dart
@override
Widget build(BuildContext context) {
var manager = Provider.of<ParkingSpotManager>(
context,
listen: false,
);
return StreamBuilder<List<ParkingSpot>>(
stream: manager
.createStream("parking-events/chirpstck/applications/1/devices"),
builder: (context, snapshot) {
Set<Marker> markers = {};
const double baseLat = 40.10548077666605;
const double baseLon = 22.501827754541637;
var random = Random();
var nextLat = baseLat + ((random.nextDouble() * 0.0005) * 2) / 10;
var nextLon = baseLon + ((random.nextDouble() * 0.0005) * 2) / 10;
dart_dev.log("Snapshot has data: ${snapshot.hasData}");
if (snapshot.hasData) {
for (var parkingSpot in snapshot.data!) {
markers.add(Marker(
markerId: MarkerId(parkingSpot.id),
infoWindow: InfoWindow(title: parkingSpot.name),
icon: BitmapDescriptor.defaultMarker,
position:
LatLng(parkingSpot.latitude, parkingSpot.longitude)));
}
manager.setParkingSpots(snapshot.data!);
}
return Scaffold(
body: GoogleMap(
initialCameraPosition: _initialCameraPosition,
onMapCreated: (ctrl) {
_controller.complete(ctrl);
},
myLocationButtonEnabled: false,
zoomControlsEnabled: false,
markers: markers,
),
floatingActionButton: FloatingActionButton(
backgroundColor: Theme.of(context).primaryColor,
foregroundColor: Colors.white,
onPressed: () async {
GoogleMapController ctrl = await _controller.future;
ctrl.animateCamera(
CameraUpdate.newCameraPosition(_initialCameraPosition));
},
child: const Icon(Icons.center_focus_strong)),
);
});
}
}
list.dart
class _ParkingSpotScreenState extends State<ParkingSpotsScreen> {
@override
Widget build(BuildContext context) {
var pSpotManager = Provider.of<ParkingSpotManager>(
context,
listen: false
);
// TODO: implement build
return Scaffold(
body: ListView.builder(
itemCount: pSpotManager.parkingSpots.length,
itemBuilder: (BuildContext context, int index) {
return Card(
child: Column(
children: <Widget>[
Text(pSpotManager.parkingSpots[index].name),
const SizedBox(height: 14.0),
Text('Latitude: ${pSpotManager.parkingSpots[index].longitude}'),
Text('Longitude: ${pSpotManager.parkingSpots[index].longitude}'),
Text(pSpotManager.parkingSpots[index].occupied
? "occupied"
: "vaccant"),
Text(pSpotManager.parkingSpots[index].lastSeenAt)
],
));
},
));
}
}
All widgets are statefull appart from the "MyApp" which is stateless. When I add the manager I get the error as descfribed in the title.
======= Exception caught by foundation library ====================================================
The following assertion was thrown while dispatching notifications for ParkingSpotManager:
setState() or markNeedsBuild() called during build.
This _InheritedProviderScope<ParkingSpotManager?> widget cannot be marked as needing to build because the framework is already in the process of building widgets. A widget can be marked as needing to be built during the build phase only if one of its ancestors is currently building. This exception is allowed because the framework builds parent widgets before children, which means a dirty descendant will always be built. Otherwise, the framework might not visit this widget during this build phase.
The widget on which setState() or markNeedsBuild() was called was: _InheritedProviderScope<ParkingSpotManager?>
value: Instance of 'ParkingSpotManager'
listening to value
The widget which was currently being built when the offending call was made was: StreamBuilder<List<ParkingSpot>>
dirty
dependencies: [_InheritedTheme, _LocalizationsScope-[GlobalKey#7e216]]
state: _StreamBuilderBaseState<List<ParkingSpot>, AsyncSnapshot<List<ParkingSpot>>>#edafa
When the exception was thrown, this was the stack:
#0 Element.markNeedsBuild.<anonymous closure> (package:flutter/src/widgets/framework.dart:4261:11)
#1 Element.markNeedsBuild (package:flutter/src/widgets/framework.dart:4276:6)
#2 _InheritedProviderScopeElement.markNeedsNotifyDependents (package:provider/src/inherited_provider.dart:570:5)
#3 ChangeNotifier.notifyListeners (package:flutter/src/foundation/change_notifier.dart:308:24)
#4 ParkingSpotManager.setParkingSpots (package:smartparking/models/parking_spot_manager.dart:22:5)
#5 _ParkingMapState.build.<anonymous closure> (package:smartparking/screens/map.dart:62:21)
#6 StreamBuilder.build (package:flutter/src/widgets/async.dart:442:81)
#7 _StreamBuilderBaseState.build (package:flutter/src/widgets/async.dart:124:48)
#8 StatefulElement.build (package:flutter/src/widgets/framework.dart:4705:27)
#9 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4588:15)
#10 StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:4763:11)
#11 Element.rebuild (package:flutter/src/widgets/framework.dart:4311:5)
#12 BuildOwner.buildScope (package:flutter/src/widgets/framework.dart:2578:33)
#13 WidgetsBinding.drawFrame (package:flutter/src/widgets/binding.dart:882:21)
#14 RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:363:5)
#15 SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1145:15)
#16 SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1082:9)
#17 SchedulerBinding._handleDrawFrame (package:flutter/src/scheduler/binding.dart:996:5)
#21 _invoke (dart:ui/hooks.dart:150:10)
#22 PlatformDispatcher._drawFrame (dart:ui/platform_dispatcher.dart:270:5)
#23 _drawFrame (dart:ui/hooks.dart:114:31)
(elided 3 frames from dart:async)
The ParkingSpotManager sending notification was: Instance of 'ParkingSpotManager'
====================================================================================================
Could someone explain what is the best way to share data between widgets with either providers or using a StreamBuilder?