I am developing a flutter app, trying to add Google Maps using the official plugin, however, there are tons of system markers that I don't know how to remove.
The markers are from Google's own database, but we really don't need that extra information, they are causing issues with our users because users thought the markers are from our own app! Big user experience issue.
I couldn't find a switch or something like that from the class GoogleMap.
Any ideas? thanks!
This can be achieved by applying custom google map styles to your google map.
To create custom google map styling. Use this tool to generate a map_style.json and save it in your assets folder. (Make sure it is referenced in pubspec.yaml aswell).
//this is the function to load custom map style json
void changeMapMode(GoogleMapController mapController) {
getJsonFile("assets/map_style.json")
.then((value) => setMapStyle(value, mapController));
}
//helper function
void setMapStyle(String mapStyle, GoogleMapController mapController) {
mapController.setMapStyle(mapStyle);
}
//helper function
Future<String> getJsonFile(String path) async {
ByteData byte = await rootBundle.load(path);
var list = byte.buffer.asUint8List(byte.offsetInBytes,byte.lengthInBytes);
return utf8.decode(list);
}
Implement it like this in your google map widget:
GoogleMap(
...
onMapCreated: (GoogleMapController c) {
yourMapController = c;
changeMapMode(yourMapController);
},
),
Related
I am building a Flutter app that allows users to create locations and then have those locations uploaded to a database and shown to other users on a map. I have gotten the database and displaying of locations to work properly, but I want the map, a Google Map, to dynamically reload after the user has created a location and is returned to the map screen.
Right now, I am using Firebase Realtime Database to listen for changes and trying to update the map accordingly after the user has clicked on the button to create a new location. Here is the code for that:
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const LocationCreation()),
).then((valuef) {
locationsRef.onValue.listen((DatabaseEvent event) {
final data = event.snapshot.value;
if (event.snapshot.value != null) {
allMarkers = {};
final map = data as Map<dynamic, dynamic>;
map.forEach((key, value){
print(value['name']);
allLocations.add(value);
// Code to create a Marker object called marker
}
);
setState(() {
allMarkers.add(marker);
});
});
}
});
I know this code is reached, because the print statement is being called and the correct names are being printed out, including the name of the newly created location. However, the map screen is not being updated with the new locations and their markers.
The Google Map is being built in the build function as so:
GoogleMap(
scrollGesturesEnabled: true,
onMapCreated: (onCreated),
initialCameraPosition: CameraPosition(
target: _center,
zoom: 11.0,
),
markers: allMarkers,
),
And here is the onCreated function:
print('here again');
mapController = controller;
final snapshot = await locationsRef.get();
final map = snapshot.value as Map<dynamic, dynamic>;
setState(() {
allMarkers.clear();
map.forEach((key, value) {
allLocations.add(value);
//Code to make a Marker called marker
allMarkers.add(marker);
});
});
The print statement is called once upon initial building of the app, but is not called again after a new location has been created and the user is returned to the map screen.
allMarkers is not being initialized within the build function, which I found was a common problem for others.
Each time I try to make a new location, the database will store the new location and the print statement within the listen for new database event will be called, but it seems that Flutter does not detect a change to allMarkers. I have also tried allMarker.clear(), but the problem is the same. I have also tried to move the call to setState(), but nothing has worked.
I'm working on an editor which needs to work on both web and mobile. So the core functionalities will remain same but the UI part will change.
Within that core part I have a portion which works with images. As the File object comes from both dart:io and dart:html I am facing some issues with getter setters.
Where I want to show the image:
Widget buildImage() {
return Image.file(
widget.item!.imageFile,
fit: widget.item!.imageFit,
color: widget.item!.color,
colorBlendMode: widget.item!.blendMode,
alignment: widget.item!.alignment,
gaplessPlayback: true,
excludeFromSemantics: true,
);
}
widget.item!.imageFile is a getter setter that I worked on the mobile counter part:
io.File get imageFile => io.File(_image!.image.filename);
set imageFile(io.File value) => _image!.image.filename = value.path;
But as now I will have to make the code work on the web version as well I tried:
if (isWeb) {
await _pickImageWeb();
} else {
// some code for mobile
}
And the _pickImageWeb() is as below:
_pickImageWeb() async {
final ImagePicker _picker = ImagePicker();
XFile? image = await _picker.pickImage(source: ImageSource.gallery);
if (image != null) {
var imageBytes = await image.readAsBytes();
setState(() {});
}
}
At this point I'm completely lost and I think I went the wrong way. Since till now I was my images from the path using getter setter and now in web the image picking is completely different.
Should I have multiple getter setters for both mobile and web i.e: imageFileMobile and imageFileWeb?
Or how to solve the issue in general. I have seen file_picker but couldn't figure out how to integrate the package in this case.
dart:io doesn't work with web, and vice versa for dart:html, I would suggest using cross_file and store the result in XFile and use it as file.readAsBytes(), because flutter web gives fake paths when selecting files, so going with bytes seems to be the best option IMO.
I am building a QR scanner to help organize a collection. Basic data about the items is encoded into a json string then into a QR code. The generation of such labels programatically was successful. The next phase was to create a simple visor in flutter. The chosen library was qr_code_scanner. My simple app was to be a scanner which when detecting a valid QR code (one containing a json string describing the required structures) would redirect into another screen where data was displayed. The scanner detects the QR codes, the objects are parsed and a widget containing another scaffold is pushed. This is mostly what I want however an issue was detected: when a valid QR was detected the app would push the data display screen multiple times, sometimes as many as 9, thus breaking backward navigation.
The method responsible of handling the event is named "onQRViewCreated" (like in the example).
void onQRViewCreated(QRViewController controller){
setState(() => this.controller = controller);
controller.scannedDataStream.listen(
(qrData) {
setState(
() {
barcode = qrData;
if (barcode?.format == BarcodeFormat.qrcode) {
try {
Item item = Item.fromJSon(
jsonDecode(barcode?.code ?? "")
);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
ItemDisplay(
key: const Key("item"),
item: item
)
)
);
}
on FormatException {
Fluttertoast.showToast(msg: "Invalid QR Code!");
}
on Exception {
Fluttertoast.showToast(msg: "Error!");
}
}
}
);
}
);
}
I would like to be able to push the ItemDisplay only once but I don't know how to do it.
Thank you in advance
Put await controller.pauseCamera(); before the Navigator.push
I would suggest to use mobile_scanner package, which is a newer version from the same author. When creating MobileScanner widget of this package, there is an option allowDuplicates, which you can set to false to avoid this behaviour.
I am using flutter_typeahead for showing place suggestions to users as they search using a text field and fetching these suggestions from HERE Maps. flutter typeahead package has an asynchronous callback function that requires, a list of strings to be returned that would then be shown to the user. The problem is that HERE Map's search engine doesn't return the search results and instead takes its own callback function which is called with the suggestions. Here's an example of it to make it clear.
TypeAheadFormField(
suggestionsCallback: (pattern) async {
final taskHandle = _searchEngine.suggest(
TextQuery.withAreaCenter(pattern, centerCoords),
searchOptions,
(error, suggestions) {
// How can i return these suggestions back from the suggestionsCallback?
final suggestionStrings = _handleSuggestions(error, suggestions);
},
);
},
),
The taskHandle also doesn't provide any way to await the searchEngine so I basically have no way of knowing when the suggestions will be available to return them by using a global variable (storing the suggestions after the searchEngine completes its callback and then returning the stored suggestions from the suggestionCallback.
The HERE SDK notifies in the SuggestionCallback when suggestions are available. Inside the callback you can proceed with your app logic, and, e.g. call typeAhead:
_searchEngine.suggest(
TextQuery.withAreaCenter(
"piz", // User typed "piz".
centerGeoCoordinates),
searchOptions, (SearchError? searchError, List<Suggestion>? list) async {
// Suggestions have been provided by HERE SDK. Use the results ...
});
Instead of using global variables it's probably better to proceed inside the callback. However, if you want to make this call blocking, you can wrap it in an async method that returns a Future. When calling this method you can await the resuts.
I am working on testing how my navigator 2.0 setup handles url changes in the browser in flutter web.
The closest i have come to being able to test how my app handles url changes is to manually update state in the RouterDelegate by calling the setNewRoutePath with a config from the RouteInformationParser.
I would really like to test the navigator closer to the origin of the url change.
Any ideas and pointers would be appreciated.
My current code looks like this:
//Pass routeInformation to RouterInformationParser
RouteInformation selectShopRoute = RouteInformation(location: '/selectshop?token=321');
RouterConfig selectShopConfig = await app.myRouteParser.parseRouteInformation(selectShopRoute);
await app.myRouterDelegate.setNewRoutePath(selectShopConfig);
await tester.pumpAndSettle();
//Verify that navigator state is select shop
expect(app.myRouterDelegate.currentScreen, RouterEnum.selectshop);
//Verify that navigator token is set correctly
expect(app.myRouterDelegate.token, '321');
I had the same question and could not find a good approach. I came up with a way to test our code and wanted to share it to you.
Basically, we have a custom RouteInformationParser, in which a location is added only for the testing purpose.
class MyRouteInformationParser
extends RouteInformationParser<PageConfiguration> {
String? customPath; // only use for testing
#override
Future<PageConfiguration> parseRouteInformation(
RouteInformation routeInformation,
) async {
final location = customPath ?? routeInformation.location;
// Compute the configuration based on the location
return PageConfiguration()
}
}
In the widget test, we just create the route information parser and use it with the MaterialApp. Changing the customPath during testing has similar effect as changing the URL of the web browser.
final informationParser = MyRouteInformationParser();
informationParser.customPath = "my/expected/path";