Updating restaurant locations when map position moves - flutter

This code successfully shows nearby restaurants using Google Places API based on my current location. When I manually move the map position on my phone, though, new restaurant markers do not show in the new location of the map. Below is my code. I also have commented out something I was trying to do to update the markers to the new camera position. I used onCameraMove in the Google Maps. Any help with updating new restaurant markers based on the new camera position is greatly appreciated.
import 'package:family_farms_forever/utilities/secrets.dart';
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:google_maps_webservice/places.dart';
import 'package:google_maps_flutter_platform_interface/src/types/marker_updates.dart';
void main() => runApp(const MyApp());
final places = GoogleMapsPlaces(apiKey: Secrets.iosApiKey);
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
title: "Market Map",
home: Scaffold(
// We'll change the AppBar title later
appBar: AppBar(title: const Text("My Location")),
body: const MyMarketMap()),
);
}
}
class MyMarketMap extends StatefulWidget {
const MyMarketMap({super.key});
#override
State<StatefulWidget> createState() {
return _MyMarketMapState();
}
}
class _MyMarketMapState extends State<MyMarketMap> {
late Future<Position> _currentLocation;
late final Set<Marker> _markers = {};
#override
void initState() {
super.initState();
_currentLocation = Geolocator.getCurrentPosition();
}
Future<void> _retrieveNearbyRestaurants(LatLng userLocation) async {
PlacesSearchResponse response = await places.searchNearbyWithRadius(
Location(lat: userLocation.latitude, lng: userLocation.longitude),
10000,
type: "restaurant");
Set<Marker> restaurantMarkers = response.results
.map((result) => Marker(
markerId: MarkerId(result.name),
// Use an icon with different colors to differentiate between current location
// and the restaurants
icon: BitmapDescriptor.defaultMarkerWithHue(
BitmapDescriptor.hueAzure),
infoWindow: InfoWindow(
title: result.name,
snippet:
"Ratings: ${result.rating?.toString() ?? "Not Rated"}"),
position: LatLng(
result.geometry!.location.lat, result.geometry!.location.lng)))
.toSet();
setState(() {
_markers.addAll(restaurantMarkers);
});
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: _currentLocation,
builder: (context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasData) {
// The user location returned from the snapshot
Position snapshotData = snapshot.data;
LatLng userLocation =
LatLng(snapshotData.latitude, snapshotData.longitude);
if (_markers.isEmpty) {
_retrieveNearbyRestaurants(userLocation);
}
// void upDateMarkers() {
// Set<Marker> updatedMarkers =
// {}; //new markers with updated position go here
// updatedMarkers = {};
// /// Then call the SetState function.
// /// I called the MarkersUpdate class inside the setState function.
// /// You can do it your way but remember to call the setState function so that the updated markers reflect on your Flutter app.
// /// Ps: I did not try the second way where the MarkerUpdate is called outside the setState buttechnically it should work.
// setState(() {
// MarkerUpdates.from(Set<Marker>.from(_markers),
// Set<Marker>.from(updatedMarkers));
// _markers = updatedMarkers;
// _markers.addAll(updatedMarkers);
// //swap of markers so that on next marker update the previous marker would be the one which you updated now.
// // And even on the next app startup, it takes the updated markers to show on the map.
// });
// }
return GoogleMap(
myLocationEnabled: true,
zoomGesturesEnabled: true,
//onCameraMove: (position) => upDateMarkers(),
initialCameraPosition: CameraPosition(
target: userLocation,
zoom: 12,
),
markers: _markers
..add(Marker(
markerId: const MarkerId("User Location"),
infoWindow: const InfoWindow(title: "User Location"),
position: userLocation)),
);
} else {
return const Center(child: Text("Failed to get user location."));
}
}
// While the connection is not in the done state yet
return const Center(child: CircularProgressIndicator());
});
}
}

You have to use Stream for getting updates :
StreamBuilder<Position>(
stream: Geolocator.getPositionStream(
locationSettings: LocationSettings()
),
builder: (context, snapshot) {
... Do your Rest Coding Here...
You can control the behavior of the stream by specifying an instance of the [LocationSettings] class for the [locationSettings] parameter. Standard settings are:
LocationSettings.accuracy: allows controlling the precision of the position updates by supplying (defaults to "best");
LocationSettings.distanceFilter: allows controlling the minimum distance the device needs to move before the update is emitted (default value is 0 which indicates no filter is used);
LocationSettings.timeLimit: allows for setting a timeout interval. If between fetching locations the timeout interval is exceeded a [TimeoutException] will be thrown. By default no time limit is configured.

Related

adding polyline "sub destinations" google maps flutter, optimize for a single API call

The application that I'm trying to create required the creation of a route that has destinations between the starting and ending point, between the beginning and end provided in the getRouteBetweenCoordinates, I need a way to add a custom Latlong pair that must travel through, instead of finding the quickest route, I need it to route between all points that I provided while still following the road (not just a direct line).
The only method that I could come up with is recalling the setPolyLines function for each stretch that makes up the total route. While this method could get the desired result, it required making multiple API calls, ideally, the entirety of the custom route would be loaded upon that first directions API call.
Here is the code that I'm working with, Is there an easier solution to this problem that I missed? This may be very obvious but I'm new with google maps integration so sorry if that's the case.
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:flutter_polyline_points/flutter_polyline_points.dart';
//new polyline between each destination
class Tour extends StatefulWidget {
const Tour({Key? key}) : super(key: key);
#override
_TourState createState() => _TourState();
}
class _TourState extends State<Tour> {
late GoogleMapController mapController;
//poly line variables
Set<Polyline> _polyLine = Set<Polyline>();
List<LatLng> polylineCordinates = [];
late PolylinePoints polylinePoints;
//starting location
static const _start =
CameraPosition(target: LatLng(48.696985, -122.905595), zoom: 17.0);
//METHODS
void _onMapCreated(GoogleMapController controller) {
mapController = controller;
//TODO: provide with start and end point for specific line, end of last ==
//start of next
setPolyLines(PointLatLng(48.696985, -122.905595),
PointLatLng(48.657421, -122.917412));
setPolyLines(PointLatLng(48.657421, -122.917412),
PointLatLng(48.644983, -122.944760));
}
void setPolyLines(PointLatLng start, PointLatLng end) async {
//polyline result DT is a collection of latlng following roads
PolylineResult result = await polylinePoints.getRouteBetweenCoordinates(
"MY API KEY IS HERE",
//route start
start,
//route end
end);
//list of latlng pairs in order of exectecution
//this is preparing the drawing of the line, the set state plots it out
if (result.status == 'OK') {
result.points.forEach((PointLatLng point) {
polylineCordinates.add(LatLng(point.latitude, point.longitude));
});
}
setState(() {
_polyLine.add(Polyline(
width: 10,
//set id to
polylineId: PolylineId("route"),
color: Color(0xFF00BFA6),
points: polylineCordinates));
});
}
#override
void initState() {
polylinePoints = PolylinePoints();
}
#override
void dispose() {
mapController.dispose();
super.dispose();
}
//upon call, modal sheet toggles from the bottom of screen
modalSheet() {
showModalBottomSheet(
context: context,
builder: (context) {
return Column(
children: [
Container(
height: 200,
color: Colors.amber,
),
Container(
height: 100,
color: Colors.blue,
)
],
);
});
}
//adjusts camera position to the _start location
center() {
mapController.animateCamera(CameraUpdate.newCameraPosition(_start));
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: GoogleMap(
polylines: _polyLine,
myLocationButtonEnabled: false,
zoomControlsEnabled: false,
onMapCreated: _onMapCreated,
initialCameraPosition: _start),
floatingActionButton: FloatingActionButton(
onPressed: () => center(), child: Icon(Icons.add)),
);
}
}
You can use wayPoints parameter of getRouteBetweenCoordinates method which accepts a list of PolylineWayPoint (List<PolylineWayPoint>).
PolylineResult result = await polylinePoints.getRouteBetweenCoordinates(
googleAPiKey,
PointLatLng(48.696985, -122.905595),
PointLatLng(48.644983, -122.944760),
wayPoints: [PolylineWayPoint(location: "48.657421,-122.917412")]);
Please see the image below for the result using your sample code.

Real-Time Updates with Streambuilder in Flutter and Google Maps (Firestore)

I have a sample app that I am building that fetches multiple markers from Firestore and displays them on Google Maps.
The issue is that the changes (markers added/deleted/updated) aren't displayed in real time since it only rebuilds in init state. I think that I could use Stream Builder to listen to the changes and update them in real time.
I am not sure how to do it with Google Maps and Firestore, since the syntax is kinda weird with snapshots, I tried many ways, but not successful. Here's my code:
#override
void initState() {
populateMarkers();
super.initState();
}
populateMarkers() {
FirebaseFirestore.instance.collection('marker').get().then((documents) {
if (documents.docs.isNotEmpty) {
for (int i = 0; i < documents.docs.length; i++) {
initMarker(
documents.docs[i].data(), documents.docs[i].id);
}
}
});
}
void initMarker(request, requestId) {
var markerIdVal = requestId;
final MarkerId markerId = MarkerId(markerIdVal);
//creating new markers
final Marker marker = Marker(
markerId: markerId,
position:
LatLng(request['location'].latitude, request['location'].longitude),
infoWindow:
InfoWindow(title: request['name'], snippet: request['description']),
onTap: () => print('Test'),
);
setState(() {
markers[markerId] = marker;
//print(markerId);
});
}
Widget loadMap() {
return GoogleMap(
markers: Set<Marker>.of(markers.values),
mapType: MapType.normal,
initialCameraPosition:
CameraPosition(target: LatLng(43.8031287, 20.453008), zoom: 12.0),
onMapCreated: onMapCreated,
// },
);
}
and in the buider, I just call loadMap() function as the body. As I mentioned, this works fine, but I would like to use Stream Builder for these functions to update in real time. Any ideas how to do it?
This thread has a similar issue, but I feel it's not the best way to do it:
Flutter - Provide Real-Time Updates to Google Maps Using StreamBuilder
Here is the full code in case someone wants to test it out:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
class MapScreen extends StatefulWidget {
#override
_MapScreenState createState() => _MapScreenState();
}
class _MapScreenState extends State<MapScreen> {
Map<MarkerId, Marker> markers = <MarkerId, Marker>{}; //--> google
GoogleMapController _controller;
#override
void initState() {
populateMarkers();
super.initState();
}
onMapCreated(GoogleMapController controller) async {
_controller = controller;
String value = await DefaultAssetBundle.of(context)
.loadString('assets/map_style.json');
_controller.setMapStyle(value);
}
populateMarkers() {
FirebaseFirestore.instance.collection('marker').get().then((documents) {
if (documents.docs.isNotEmpty) {
for (int i = 0; i < documents.docs.length; i++) {
initMarker(
documents.docs[i].data(), documents.docs[i].id); //maybe error
//documents.docs[i].data, documents.docs[i].id
}
}
});
}
void initMarker(request, requestId) {
var markerIdVal = requestId;
final MarkerId markerId = MarkerId(markerIdVal);
//creating new markers
final Marker marker = Marker(
markerId: markerId,
position:
LatLng(request['location'].latitude, request['location'].longitude),
infoWindow:
InfoWindow(title: request['name'], snippet: request['description']),
onTap: () => print('Test'),
);
setState(() {
markers[markerId] = marker;
//print(markerId);
});
}
Widget loadMap() {
return GoogleMap(
// markers: markers.values.toSet(),
markers: Set<Marker>.of(markers.values),
mapType: MapType.normal,
initialCameraPosition:
CameraPosition(target: LatLng(44.8031267, 20.432008), zoom: 12.0),
onMapCreated: onMapCreated,
cameraTargetBounds: CameraTargetBounds(LatLngBounds(
northeast: LatLng(44.8927468, 20.5509553),
southwest: LatLng(44.7465138, 20.2757283))),
mapToolbarEnabled: false,
// onMapCreated: (GoogleMapController controller) {
// _controller = controller;
// },
);
}
void dispose() {
_controller.dispose();
super.dispose();
}
//-----------------------------------
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('App'),
centerTitle: true,
backgroundColor: Colors.blue[700],
actions: [
// ElevatedButton(
// onPressed: () => refresh(),
// child: Text('REFRESH'),
// )
],
),
body: loadMap(),
);
}
}
So you want to subscribe to a stream coming from a firestore collection, and use this data to update markers on a map in real-time, is that right?
Please note that you absolutely need to separate your services from your widgets, or you'll end up quickly with a jumbled mess of a code.
In one file you have the class defining access to your database (such as API calls, or Firestore in this case). ANY write or read with Firestore will go through this service.
class FirestoreService {
FirestoreService._();
static final instance = FirestoreService._();
Stream<List<T>> collectionStream<T>({
required String path,
required T Function(Map<String, dynamic> data, String documentID) builder,
}) {
Query query = FirebaseFirestore.instance.collection(path);
final Stream<QuerySnapshot> snapshots = query.snapshots();
return snapshots.map((snapshot) {
final result = snapshot.docs
.map((snapshot) =>
builder(snapshot.data() as Map<String, dynamic>, snapshot.id))
.where((value) => value != null)
.toList();
return result;
});
}
}
In another file, you'll have the repository service, that contains the CRUD operations. Each operation makes a call to the DB service (defined in the previous step) and uses the objects's serialization (toJson, send to DB) or parsing (fromJson, get from DB) methods.
class MarkersRepo {
final _service = FirestoreService.instance;
Stream<List<Marker>> getMarkers() {
return _service.collectionStream(
path: 'marker',
builder:(json, docId){
return Marker.fromJson(json);
}
);
}
}
In another file, you define your Marker object model with the serialization and parsing methods. Please don't use strings to access directly the document properties in your code, as that is error-prone and again will cause messy code. Instead, you define those once and for all in the object model.
class Marker{
//properties of the object such as location, name and description
//toJson and fromJson methods
}
And finally in your widget file, as you noted yourself you are only reading the document once, in the initstate, so the view does not update. Instead, one simple option is to have a StreamBuilder inside your build method, extract the markers there and then display them:
Widget build(Buildcontext context){
return StreamBuilder(
stream: MarkersRepos().getMarkers(),
builder:(context, snapshot){
//check for connection state and errors, see streambuilder documentation
final List<Marker> markers = snapshot.data;
return loadMap(markers);
}
);
}
EDIT: added more details

Zoom camera in Flutter

I'm using this lib for displaying a camera (https://pub.dev/packages/camera), however, I'd like to set a predefined zoom value for the camera before it starts, but I'm not able to identify where I can set it using this lib.
Does anyone know how to do it?
Here's the code I got so far
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:camera/camera.dart';
class CameraPage extends StatefulWidget {
#override
_CameraPageState createState() => _CameraPageState();
}
class _CameraPageState extends State<CameraPage> {
List<CameraDescription> cameras;
CameraController controller;
#override
void initState() {
// TODO: implement initState
super.initState();
buscarCameras();
}
Future<void> buscarCameras() async {
cameras = await availableCameras();
controller = CameraController(cameras[0], ResolutionPreset.medium);
controller.initialize().then((_) {
if (!mounted) {
return;
}
setState(() {});
});
}
#override
Widget build(BuildContext context) {
if(controller == null || controller.value == null)
return Center(
child: CircularProgressIndicator(),
);
if (!controller.value.isInitialized) {
return Container();
}
return AspectRatio(
aspectRatio: controller.value.aspectRatio,
child: CameraPreview(controller));
}
}
Flutter has added zoom support as of Camera version 0.6.2. You can use cameraController.setZoomLevel(4.0); in your code to adjust the zoom level.
There are also other helpful functions, such as cameraControl.getMaxZoomLevel(); to find the limits for the zoom level.
There isn't any documentation for this feature at time of writing, as it is very recent, but you can look through the code for the camera controller to see the available methods.
Create your zoom variable in state class:
class _ShowCameraState extends State<ShowCamera> {
CameraController _cameraController;
Future<void> _initializeControllerFuture;
double zoom = 0.0;
}
And then in a widget, create slider:
#override
Widget build(BuildContext context) {
return OrientationBuilder(
builder: (context, orientation) => Scaffold(
backgroundColor: Colors.black,
body: Stack(
children: [
FutureBuilder<void>(
future: _initializeControllerFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return CameraPreview(_cameraController);
} else {
return Center(
child: CircularProgressIndicator(),
);
}
},
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Slider(
activeColor: Colors.red,
value: zoom,
onChanged: (value) {
print(value);
// as slider values are in decimals, we multiply it by 10 because camera
//take values above 1.0 and below 8.0
//e.g:
//value=0.19 //according to slider
//value=value*10=> 0.19*10
//Now the updated value is:
//value=1.9
value = value * 10;
if (value <= 8.0 && value >= 1.0) {
//Here we set the zoom level when we move slider pointer
_cameraController.setZoomLevel(value);
}
//and to set slider pointer position visually, we divided the value by 10
//to give slider its original value.
setState(() => zoom = value / 10);
},
),
For those who need to do it now: Wrap your CameraPreview widget inside a GestureDetector and add the below code block inside it (you must initialize your CameraController before this).
onScaleUpdate: (details) async {
var maxZoomLevel = await camController.getMaxZoomLevel();
// just calling it dragIntensity for now, you can call it whatever you like.
var dragIntensity = details.scale;
if (dragIntensity < 1) {
// 1 is the minimum zoom level required by the camController's method, hence setting 1 if the user zooms out (less than one is given to details when you zoom-out/pinch-in).
camController.setZoomLevel(1);
} else if (dragIntensity > 1 && dragIntensity < maxZoomLevel) {
// self-explanatory, that if the maxZoomLevel exceeds, you will get an error (greater than one is given to details when you zoom-in/pinch-out).
camController.setZoomLevel(dragIntensity);
} else {
// if it does exceed, you can provide the maxZoomLevel instead of dragIntensity (this block is executed whenever you zoom-in/pinch-out more than the max zoom level).
camController.setZoomLevel(maxZoomLevel);
}
},
Very simple logic and gets the job done right away, but you can optimize it further for better user experience if you need.

Flutter Camera Plugin taking dark image bug

I am getting dark images from flutter Camera Plugin.
Camera Preview is showing correctly but after taking the picture it becomes too dark.
I searched and what i found that it's about the FPS and exposure of the camera.
How can I solve this problem?
I need to show camera preview and take pictures in my app.
Please don't tell me to use image_picker package.
Device : Redmi note 4
Android OS : 7.0
Here is the Image
dark image
Here is the code
import 'dart:async';
import 'dart:io';
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:path/path.dart' show join;
import 'package:path_provider/path_provider.dart';
Future<void> main() async {
// Obtain a list of the available cameras on the device.
final cameras = await availableCameras();
// Get a specific camera from the list of available cameras.
final firstCamera = cameras.first;
runApp(
MaterialApp(
theme: ThemeData.dark(),
home: TakePictureScreen(
// Pass the appropriate camera to the TakePictureScreen widget.
camera: firstCamera,
),
),
);
}
// A screen that allows users to take a picture using a given camera.
class TakePictureScreen extends StatefulWidget {
final CameraDescription camera;
const TakePictureScreen({
Key key,
#required this.camera,
}) : super(key: key);
#override
TakePictureScreenState createState() => TakePictureScreenState();
}
class TakePictureScreenState extends State<TakePictureScreen> {
CameraController _controller;
Future<void> _initializeControllerFuture;
#override
void initState() {
super.initState();
// To display the current output from the Camera,
// create a CameraController.
_controller = CameraController(
// Get a specific camera from the list of available cameras.
widget.camera,
// Define the resolution to use.
ResolutionPreset.medium,
);
// Next, initialize the controller. This returns a Future.
_initializeControllerFuture = _controller.initialize();
}
#override
void dispose() {
// Dispose of the controller when the widget is disposed.
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Take a picture')),
// Wait until the controller is initialized before displaying the
// camera preview. Use a FutureBuilder to display a loading spinner
// until the controller has finished initializing.
body: FutureBuilder<void>(
future: _initializeControllerFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
// If the Future is complete, display the preview.
return CameraPreview(_controller);
} else {
// Otherwise, display a loading indicator.
return Center(child: CircularProgressIndicator());
}
},
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.camera_alt),
// Provide an onPressed callback.
onPressed: () async {
// Take the Picture in a try / catch block. If anything goes wrong,
// catch the error.
try {
// Ensure that the camera is initialized.
await _initializeControllerFuture;
// Construct the path where the image should be saved using the
// pattern package.
final path = join(
// Store the picture in the temp directory.
// Find the temp directory using the `path_provider` plugin.
(await getTemporaryDirectory()).path,
'${DateTime.now()}.png',
);
// Attempt to take a picture and log where it's been saved.
await _controller.takePicture(path);
// If the picture was taken, display it on a new screen.
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DisplayPictureScreen(imagePath: path),
),
);
} catch (e) {
// If an error occurs, log the error to the console.
print(e);
}
},
),
);
}
}
// A widget that displays the picture taken by the user.
class DisplayPictureScreen extends StatelessWidget {
final String imagePath;
const DisplayPictureScreen({Key key, this.imagePath}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Display the Picture')),
// The image is stored as a file on the device. Use the `Image.file`
// constructor with the given path to display the image.
body: Image.file(File(imagePath)),
);
}
}
Just put delay before take picture.
Future.delayed(const Duration(milliseconds: 500), () {
_controller.takePicture(path);
});
I think it's not about a delay, images are dark if exposure is not handled.
Also exposure requires focus pre captures to work and are not handled in official plugin now.
You can use this plugin : CamerAwesome
Official plugin has been quite abandonned. This plugin includes flash, zoom, auto focus, exposure... and no initialisation required.
It uses value notifier to change data directly in preview like this :
// init Notifiers
ValueNotifier<CameraFlashes> _switchFlash = ValueNotifier(CameraFlashes.NONE);
ValueNotifier<Sensors> _sensor = ValueNotifier(Sensors.BACK);
ValueNotifier<Size> _photoSize = ValueNotifier(null);
#override
Widget build(BuildContext context) {
return CameraAwesome(
onPermissionsResult: (bool result) { }
selectDefaultSize: (List<Size> availableSizes) => Size(1920, 1080),
onCameraStarted: () { },
onOrientationChanged: (CameraOrientations newOrientation) { },
zoom: 0.64,
sensor: _sensor,
photoSize: _photoSize,
switchFlashMode: _switchFlash,
orientation: DeviceOrientation.portraitUp,
fitted: true,
);
};
A hack that works for me, with camera plugin: take the picture twice. The first one buys time for the second one to have the proper exposure and focus.
final image = await controller.takePicture(); // is not used
final image2 = await controller.takePicture();

StreamBuilder doesn't rebuild after Navigator.pop

I have a simple service which is tracking the current user position :
class LocationService {
LatLng _lastLocation;
Location location = Location();
StreamController<LatLng> _locationController = StreamController<LatLng>();
Stream<LatLng> get locationStream => _locationController.stream;
LocationService() {
location.onLocationChanged().listen((locationData) {
LatLng location = LatLng(locationData.latitude, locationData.longitude);
if(_lastLocation == null || _lastLocation != location) {
_lastLocation = location;
_locationController.add(location);
}
});
}
}
Then, I'm using this service to create a Map (thanks to flutter_map) which is following the current user position :
class SelfUpdatingMap extends StatelessWidget {
final Icon currentPositionIcon;
final MapController _controller = MapController();
SelfUpdatingMap({
this.currentPositionIcon,
});
#override
Widget build(BuildContext context) => StreamBuilder<LatLng>(
stream: LocationService().locationStream,
builder: (context, asyncSnapshot) {
if (asyncSnapshot.hasError || asyncSnapshot.data == null) {
return Text('Loading...');
}
try {
_controller?.move(asyncSnapshot.data, 18);
} catch (ignored) {}
return _createMapWidget(context, asyncSnapshot.data);
},
);
Widget _createMapWidget(BuildContext context, LatLng location) => FlutterMap(
options: MapOptions(
center: location,
zoom: 18,
),
layers: [
TileLayerOptions(
urlTemplate: 'https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}.png', // https://a.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png is good too.
subdomains: ['a', 'b', 'c'],
),
MarkerLayerOptions(
markers: [
Marker(
width: 40,
height: 40,
point: location,
builder: (contact) => currentPositionIcon,
),
]
),
],
mapController: _controller,
);
}
Then, I use the SelfUpdating widget in two places :
The page 1, ancestor of page 2.
And in the page 3, successor of page 2.
So here is the situation :
I launch my app, I'm on the page 1. I have my SelfUpdatingMap.
I call Navigator.pushNamed(context, '/page-2').
I call Navigator.pushNamed(context, '/page-3'). I have another SelfUpdatingMap.
I call two times Navigator.pop(context), I get the page 1 BUT the SelfUpdatingMap doesn't update itself anymore.
The builder is not even called anymore. So please, what is wrong with this code ?
Thank you !
When you push and after pop a page, the build metod doesn't restart.
I found the same problem with the FlutterBluetoothSerial.instance.onStateChanged() stream and the solution that I found is to add the stream to a local static final variable and use it instead of calling every time the original method (you can do that only if the stream is a broadcast one I think).
Solution example:
class ExampleClass {
static final Stream<LatLng> locationStream = LocationService().locationStream;
}
class SelfUpdatingMap extends StatelessWidget {
...
#override
Widget build(BuildContext context) => StreamBuilder<LatLng>(
stream: ExampleClass.locationStream,
builder: (context, asyncSnapshot) {
if (asyncSnapshot.hasError || asyncSnapshot.data == null) {
return Text('Loading...');
}
try {
_controller?.move(asyncSnapshot.data, 18);
} catch (ignored) {}
return _createMapWidget(context, asyncSnapshot.data);
},
);
...
}
class Page3Widget extends StatelessWidget {
...
#override
Widget build(BuildContext context) => StreamBuilder<LatLng>(
stream: ExampleClass.locationStream,
builder: (context, asyncSnapshot) {
//do something
},
);
...
}