Flutter MapBox Integration: Complete Guide with Example
A Step-by-Step Guide To Integrate MapBox Inside Flutter

Search for a command to run...
A Step-by-Step Guide To Integrate MapBox Inside Flutter

Excellent article, please friend do this same article with Bloc Flutter Bloc and if you can please using Google Maps and Flutter Bloc please yes?
Lessons from Code, Content, and Consistency

Reliability basics: Outbox pattern + why it matters

Introducing Kafka/Redpanda + move to event-driven workflow

Connect Orders โ Inventory (first working service-to-service flow)

Inventory service + gRPC + proto contracts






As you can see, utilizing MapBox instead of Google Maps has several benefits since it provides you greater control over the map you're integrating into the app.
So, without further-a-do let's see how we can integrate MapBox inside a Flutter Application.
You must first register for a MapBox account in order to use MapBox. Create an account by going to MapBox SignIn.
After the successful sign in you'll be redirected to the Dashboard.

pubspec.yaml.dependencies:
flutter_map: ^1.1.1




// Example
"https://api.mapbox.com/styles/v1/dhruv25/{mapStyleId}/tiles/256/{z}/{x}/{y}@2x?access_token={accessToken}"

import 'package:latlong2/latlong.dart';
class AppConstants {
static const String mapBoxAccessToken = 'YOUR_ACCESS_TOKEN';
static const String mapBoxStyleId = 'YOUR_STYLE_ID';
static final myLocation = LatLng(51.5090214, -0.1982948);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: const Color.fromARGB(255, 33, 32, 32),
title: const Text('Flutter MapBox'),
),
body: Stack(
children: [
FlutterMap(
options: MapOptions(
minZoom: 5,
maxZoom: 18,
zoom: 13,
center: AppConstants.myLocation,
),
layers: [
TileLayerOptions(
urlTemplate:
"https://api.mapbox.com/styles/v1/dhruv25/{mapStyleId}/tiles/256/{z}/{x}/{y}@2x?access_token={accessToken}",
additionalOptions: {
'mapStyleId': AppConstants.mapBoxStyleId,
'accessToken': AppConstants.mapBoxAccessToken,
},
),
],
),
],
),
);
}
A tile map is a map in which each region is represented by a single tile of the same shape and size. For example, a square tile map of the United States would consist of square tiles of the same size for each state, regardless of the geographic area of each state.
accessToken and the second is for mapStyleId.As suggested in flutter_map documentation we need to add internet permission inside the AndriodManifest file. So copy the below line and paste it inside the
manifesttag.
<uses-permission android:name="android.permission.INTERNET"/>

import 'package:latlong2/latlong.dart';
class MapMarker {
final String? image;
final String? title;
final String? address;
final LatLng? location;
final int? rating;
MapMarker({
required this.image,
required this.title,
required this.address,
required this.location,
required this.rating,
});
}
final mapMarkers = [
MapMarker(
image: 'assets/images/restaurant_1.jpg',
title: 'Alexander The Great Restaurant',
address: '8 Plender St, London NW1 0JT, United Kingdom',
location: LatLng(51.5382123, -0.1882464),
rating: 4),
MapMarker(
image: 'assets/images/restaurant_2.jpg',
title: 'Mestizo Mexican Restaurant',
address: '103 Hampstead Rd, London NW1 3EL, United Kingdom',
location: LatLng(51.5090229, -0.2886548),
rating: 5),
MapMarker(
image: 'assets/images/restaurant_3.jpg',
title: 'The Shed',
address: '122 Palace Gardens Terrace, London W8 4RT, United Kingdom',
location: LatLng(51.5090215, -0.1959988),
rating: 2),
MapMarker(
image: 'assets/images/restaurant_4.jpg',
title: 'Gaucho Tower Bridge',
address: '2 More London Riverside, London SE1 2AP, United Kingdom',
location: LatLng(51.5054563, -0.0798412),
rating: 3),
MapMarker(
image: 'assets/images/restaurant_5.jpg',
title: 'Bill\'s Holborn Restaurant',
address: '42 Kingsway, London WC2B 6EY, United Kingdom',
location: LatLng(51.5077676, -0.2208447),
rating: 4,
),
];
markers property add the below code.MarkerLayerOptions(
markers: [
for (int i = 0; i < mapMarkers.length; i++)
Marker(
height: 40,
width: 40,
point: mapMarkers[i].location ?? AppConstants.myLocation,
builder: (_) {
return GestureDetector(
onTap: () {},
child: SvgPicture.asset(
'assets/icons/map_marker.svg',
),
);
},
),
],
),
point parameter is the actual location of the Marker in the form of LatLng.

Positioned(
left: 0,
right: 0,
bottom: 2,
height: MediaQuery.of(context).size.height * 0.3,
child: PageView.builder(
onPageChanged: (value) {},
itemCount: mapMarkers.length,
itemBuilder: (_, index) {
final item = mapMarkers[index];
return Padding(
padding: const EdgeInsets.all(15.0),
child: Card(
elevation: 5,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
color: const Color.fromARGB(255, 30, 29, 29),
child: Row(
children: [
const SizedBox(width: 10),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
child: ListView.builder(
padding: EdgeInsets.zero,
scrollDirection: Axis.horizontal,
itemCount: item.rating,
itemBuilder:
(BuildContext context, int index) {
return const Icon(
Icons.star,
color: Colors.orange,
);
},
),
),
Expanded(
flex: 2,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
item.title ?? '',
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 10),
Text(
item.address ?? '',
style: const TextStyle(
fontSize: 14,
color: Colors.grey,
),
),
],
),
),
],
),
),
const SizedBox(width: 10),
Expanded(
child: Padding(
padding: const EdgeInsets.all(4.0),
child: ClipRRect(
borderRadius: BorderRadius.circular(10),
child: Image.asset(
item.image ?? '',
fit: BoxFit.cover,
),
),
),
),
const SizedBox(width: 10),
],
),
),
);
},
),
)
As you can see, we created a swipeable card on the screen using PageView.builder(). The layout is fairly straightforward. There is a Card with a Row widget inside of it. The two primary sections/children of the Row Widget are.
The Restaurant Details contain Rating, Name of the Restaurant, and Address.
Now, what we want is, when you click on any particular marker the information related to it should appear. It's pretty simple.
final pageController = PageController();
PageView.builder(
controller: pageController,
...
)
onTap parameter.MarkerLayerOptions(
markers: [
for (int i = 0; i < mapMarkers.length; i++)
Marker(
height: 40,
width: 40,
point: mapMarkers[i].location ?? AppConstants.myLocation,
builder: (_) {
return GestureDetector(
onTap: () {
pageController.animateToPage(
i,
duration: const Duration(milliseconds: 500),
curve: Curves.easeInOut,
);
setState(() {});
},
child: SvgPicture.asset(
'assets/icons/map_marker.svg',
),
);
},
),
],
),

Let's give the marker some Opacity. What we want to do is that just that marker should have full opacity when it is tapped, leaving the other markers with only half.
First create a variable named selectedIndex. And update it every time the marker is tapped.
int selectedIndex = 0;
1 when selected and 0.5 when not.1 when selected otherwise 0.7Marker(
height: 40 ,
width: 40,
point: mapMarkers[i].location ?? AppConstants.myLocation,
builder: (_) {
return GestureDetector(
onTap: () {
pageController.animateToPage(
i,
duration: const Duration(milliseconds: 500),
curve: Curves.easeInOut,
);
selectedIndex = i;
setState(() {});
},
child: AnimatedScale(
duration: const Duration(milliseconds: 500),
scale: selectedIndex == i ? 1 : 0.7,
child: AnimatedOpacity(
duration: const Duration(milliseconds: 500),
opacity: selectedIndex == i ? 1 : 0.5,
child: SvgPicture.asset(
'assets/icons/map_marker.svg',
),
),
),
);

Woah ๐ฎ Now that looks smoooooth.... Isn't it?
Okay, there is the last thing to do, it is we also want to update the marker when we manually swipe the card.
onPageChanged function, and paste the below code : PageView.builder(
controller: pageController,
onPageChanged: (value) {
selectedIndex = value;
currentLocation =
mapMarkers[value].location ?? AppConstants.myLocation;
_animatedMapMove(currentLocation, 11.5);
setState(() {});
},
)
selectedIndex. And also I've created a currentLocation variable. Which will be useful, to tell the map where to center the map. The default value of it is myLocation defined in the constans file.var currentLocation = AppConstants.myLocation;
center value of MapOptions with currentLocation.FlutterMap(
options: MapOptions(
minZoom: 5,
maxZoom: 18,
zoom: 11,
center: currentLocation, // <---
),
...
)
late final MapController mapController;
@override
void initState() {
super.initState();
mapController = MapController();
}
FlutterMap(
mapController: mapController,
options: MapOptions(
minZoom: 5,
maxZoom: 18,
zoom: 11,
center: currentLocation,
),
...
)
move() in order to move the map to a specific location. But It doesn't animate the map. To do smooth animation from one location to another we can use TweenAnimation.
To do that, first, extend your StateFulWidget with TickerProviderStateMixin.
class _HomePageState extends State<HomePage> with TickerProviderStateMixin {}
void _animatedMapMove(LatLng destLocation, double destZoom) {
// Create some tweens. These serve to split up the transition from one location to another.
// In our case, we want to split the transition be<tween> our current map center and the destination.
final latTween = Tween<double>(
begin: mapController.center.latitude, end: destLocation.latitude);
final lngTween = Tween<double>(
begin: mapController.center.longitude, end: destLocation.longitude);
final zoomTween = Tween<double>(begin: mapController.zoom, end: destZoom);
// Create a animation controller that has a duration and a TickerProvider.
var controller = AnimationController(
duration: const Duration(milliseconds: 1000), vsync: this);
// The animation determines what path the animation will take. You can try different Curves values, although I found
// fastOutSlowIn to be my favorite.
Animation<double> animation =
CurvedAnimation(parent: controller, curve: Curves.fastOutSlowIn);
controller.addListener(() {
mapController.move(
LatLng(latTween.evaluate(animation), lngTween.evaluate(animation)),
zoomTween.evaluate(animation),
);
});
animation.addStatusListener((status) {
if (status == AnimationStatus.completed) {
controller.dispose();
} else if (status == AnimationStatus.dismissed) {
controller.dispose();
}
});
controller.forward();
}
You don't need to understand the above code if you are not familiar with Animation in Flutter. It simply animates the zoom position, that's it.
Now add this method two onPageChanged function
onPageChanged: (value) {
selectedIndex = value;
currentLocation =
mapMarkers[value].location ?? AppConstants.myLocation;
_animatedMapMove(currentLocation, 11.5);
setState(() {});
},
and to Markers' GestureDetector's onTap function
onTap: () {
pageController.animateToPage(
i,
duration: const Duration(milliseconds: 500),
curve: Curves.easeInOut,
);
selectedIndex = i;
currentLocation = mapMarkers[i].location ??
AppConstants.myLocation;
_animatedMapMove(currentLocation, 11.5);
setState(() {});
},


Follow me on Twitter, LinkedIn, and Github for more updates.