How to create a map in mapbox for flutter
Mapbox for flutter is still not documented well enough. I’ve spent almost a day until I had something working. With this blog post I hope to help the next ones, so that they don’t need so much time setting it up.
Adding the dependency
I thought it would be done with flutter pub add mapbox_gl
and flutter pub get
, but it was not. For iOS, the compiling process choked at pod install
time:
[!] Error installing Mapbox-iOS-SDK
curl: (22) The requested URL returned error: 401 Unauthorized
And for Android it also didn’t work.
In hindsight, I should have read the docs better, because they cover the following steps:
- Go to your mapbox account
- Click
+ Create token
- Give it a name
- Check the
Downloads:Read
checkbox - Generate the token and copy the secret to a safe place
iOS
Create the file ~/.netrc
and put the following into it:
machine api.mapbox.com
login mapbox
password YOUR_SECRET_MAPBOX_ACCESS_TOKEN
Now, pod install
should work.
Android
Of course, Android building takes the access token from somewhere else :) Put this into ~/.gradle/gradle.properties
:
MAPBOX_DOWNLOADS_TOKEN=YOUR_SECRET_MAPBOX_ACCESS_TOKEN
Running on the iOS simulator
Now it should all work, right? Except it doesn’t. When doing the Xcode build then I ran into:
ld: building for iOS Simulator, but linking in dylib built for iOS, file '/Users/username/code/appname/ios/Pods/Mapbox-iOS-SDK/dynamic/Mapbox.framework/Mapbox' for architecture arm64
This seems to be only a problem when debugging on iOS simulator. This stackoverflow answer helped me solving it. In your ios/Podfile
you need to add the following:
post_install do |installer|
installer.pods_project.build_configurations.each do |config|
config.build_settings["EXCLUDED_ARCHS[sdk=iphonesimulator*]"] = "arm64"
end
end
Since I already had a section with post_install do |installer|
I needed to append it to the end so for me this now looks like this:
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
end
installer.pods_project.build_configurations.each do |config|
config.build_settings["EXCLUDED_ARCHS[sdk=iphonesimulator*]"] = "arm64"
end
end
But now it should work? Still not! 😱 When I started the app in the iOS simulator, it immediately crashed. I needed to add follow this stackoverflow advice:
Add the following lines into <dict>
of your ios/Runner/Info.plist
:
<key>io.flutter.embedded_views_preview</key>
<true/>
<key>MGLMapboxMetricsEnabledSettingShownInApp</key>
<true/>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Shows your location on the map and helps improve the map</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>Shows your location on the map and helps improve the map</string>
And then run:
flutter clean
flutter create . --project-name my-project-name
But take care! The last command (not sure if it is needed at all?) created for me android/app/src/main/kotlin/ch/weegee/MainActivity.kt
which was then later causing trouble for my Android build so I needed to remove that file again.
And now, finally, it worked for me for both iOS and Android.
A minimal example
Here’s a minimal example which shows a map in full page with a marker:
import 'package:mapbox_gl/mapbox_gl.dart';
import 'package:flutter/material.dart';
import '../models/mapbox_api.dart';
class MapDetails extends StatelessWidget {
final LatLng latLng;
const MapDetails({Key? key, required this.latLng}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: MapboxMap(
initialCameraPosition: CameraPosition(
target: latLng,
zoom: 13,
),
onMapCreated: (controller) => addMarker(controller, latLng),
accessToken: mapboxPublicToken,
),
);
}
}
void addMarker(MapboxMapController controller, LatLng latLng) async {
var byteData = await rootBundle.load("images/poi.png");
var markerImage = byteData.buffer.asUint8List();
controller.addImage('marker', markerImage);
await controller.addSymbol(
SymbolOptions(
iconSize: 0.3,
iconImage: "marker",
geometry: latLng,
iconAnchor: "bottom",
),
);
}
A few remarks:
- I had no luck with the “built in” markers. This should be the list of supported markers but I couldn’t get them to work
- images/poi.png in my case is just a 256x256 PNG image with transparent background
- iconMarker tells how to position the marker. In my case it is kind of a pin icon so I want the “center bottom” to be where the lat/lng is
- iconSize you need to find out the best size. In my case I needed to hot restart every time I did a change which was annoying…
mapboxPublicToken
is the public token. In the flutter mapbox documentation they advice to not store it in source code but since the token is public anyway when using it for web, and for mobile you can just unzip the APK and then have the token as well. Plus it’s a “public” token I don’t see the point securing it into an environment variable, so I just store it in source code.
If you want to have the mapbox tile part of a screen (i.e. not filling the whole screen) you probably will have it as child of a column
. To not run into the Horizontal viewport was given unbounded height
exception, you need to limit it’s height:
Column(
children: [
…,
Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: Center(
child: SizedBox(
width: double.infinity,
height: 150,
child: MapboxMap(
onMapCreated: (controller) =>
this.controller = controller,
onStyleLoadedCallback: () =>
addMarker(controller!, latLng!),
initialCameraPosition: CameraPosition(
target: latLng!,
zoom: 13,
),
accessToken: mapboxPublicToken,
),
),
),
),
…,
]
)
You might notice that here the marker is loaded only at onStyleLoadedCallback
. I needed to move it to this, since I ran into some nullpointer issues when adding the marker with onMapCreated
.
Now, on Android I had the issue that the map was showing top left of the screen. There is this currently open issue - which at the time you’re reading might be closed already. If it isn’t: I was able to resolve this downgrading to version 0.14.0
.
That’s it. As always: I hope I covered all the issues I had. If I missed something, please use the comments.
Comments