Getting Started with Flutter Map
Twitter | LinkedIn | YouTube | Instagram
This story is also available as a YouTube video. Watch it here.
A couple of days ago, I had the idea of building an app that would show the translation of an inputted word over each country in their respective languages. I wanted to be able to see how languages developed and changed geographically.
I had never built mobile apps or manipulated maps before, but I wanted to give it a try anyway.
Being able to compile my code to iOS, Android, and Web without relying on multiple code bases made me decide to build it with Flutter. Flutter also has a bunch of libraries available, so I knew it would be easy to get started.
Flutter Map is an open-source package for Flutter that provides a highly customizable map widget. With Flutter Map, developers can create interactive maps in their Flutter applications, including features like markers, polygons, polylines, and tile layers.
You can see that I highlighted a couple of words above. This was when my problems started. I thought that learning Flutter would be my biggest challenge. However, learning how maps actually work was a bit more tricky for me. But before I explain why, let's build an app with the sole purpose of displaying a map with Flutter Map.
Step 1: Displaying a map
This is the first screen I implemented. A simple map that I could play around with. As I zoom in & out and move around, the map would automatically adjusts and show more or less information according to how much zoom I apply.
Displaying a map is as easy as implementing the following widget:
return Scaffold(
body: Stack(
children: [
FlutterMap(
options: MapOptions(
center: LatLng(51.509364, -0.128928),
zoom: 3.2,
),
children: [
TileLayer(
urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
userAgentPackageName: 'com.example.app',
),
],
),
],
),
);
This will result in the following screen:
Pretty cool, isn't it? I had just built my first app, but I had no clue how I did it.
So let's take a step back and analyze the code I had just copied and pasted from Flutter Map's documentation.
Understanding the basics of Flutter (Skip this part if you're familiar with it)
You can see that the widget we built is composed of a Scaffold whose body is a Stack and whose child is a FlutterMap.
return Scaffold(
body: Stack(
children: [
FlutterMap(
[...]
),
],
),
);
Everything in Flutter is a widget, and widgets are just tiny chunks of UI that you can combine to make a complete app. Building an app in Flutter is like building a Lego set — piece by piece.
If we had multiple children inside our Stack, they would've been displayed on top of each other, as in an actual stack. If, instead of a Stack widget, we had a Row or Column, their children would be displayed as their names suggest. You just need to play around with the widgets to get them to be displayed as you expect in your app.
In fact, for this simple application, we didn't even need the Stack and Scaffold widget on the back of the FlutterMap widget. If we had returned just the FlutterMap widget, the result would've been the same.
return FlutterMap(
[...]
);
Back to the FlutterMap
The FlutterMap widget is provided by the Flutter Map library. You can see that it’s composed of options and children.
Through the options, we’re telling this widget that we want this map to be initialized centered in London (51.509364, -0.128928) with a zoom of 3.2 applied.
Our children are the layers of our map. For this example, we only need one layer, which is a Tile Layer, responsible for fetching the map images from Open Street Maps.
Tile Server URL
If you pay attention to our TileLayer, you will see that one of the parameters is the urlTemplate.
TileLayer(
urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
userAgentPackageName: 'com.example.app',
),
This kind of URL is called a “tile server URL”. It is a standard format used by many map tile providers to deliver map tiles to mapping software. In the URL, the {z}, {x}, and {y} placeholders are used to specify the zoom level, the X-coordinate, and the Y-coordinate of the tile, respectively. When a map is displayed, the mapping software will make a request to the tile server URL for each tile needed to display the current view.
Here are two tiles that when combined display Portugal entirely:
Our map is actually generated based on multiple static images placed side by side. As we move around and zoom in & out, our app will fetch new images from the Tile Server URL to fill our screen with the map.
Different Tile Server URL Providers
Learning about Tile Server URL led me to my next problem. Open Street Maps displays too much information as you zoom in. Political borders, country names, roads, rivers… All of this information would make my translations hard to see. I needed to get rid of them. But since the images are static, I could not use the tile server provided by OpenStreetMaps. I would have to find a different one.
With a few Google searches, I was able to find multiple map providers, a few of them paid, others free.
I wanted to find something simple. Just the world map with the border of the countries. The closest I got to what I wanted was a Tile Server provided by Stamen Design with the name "Toner". They offer six different flavors, and one of them is only the background. No labels. Close to what I needed.
The "background" one was perfect for me, so I got the Tile Server URL:
https://stamen-tiles.a.ssl.fastly.net/toner-background/{z}/{x}/{y}.png
And by replacing the urlTemplate in my widget, I could immediately see the results:
FlutterMap(
options: MapOptions(
center: LatLng(51.509364, -0.128928),
zoom: 3.2,
),
children: [
TileLayer(
urlTemplate: 'https://stamen-tiles.a.ssl.fastly.net/toner-background/{z}/{x}/{y}.png',
userAgentPackageName: 'com.example.app',
),
],
);
Step 2: Placing a Label on the Map
My idea is to build an app in which I can input a word and see the translation of this word over each country in their respective languages. Now that I have a map showing up, I had to find a way to display labels over it on specific coordinates.
Map Layers
Let's look back at a previous paragraph:
[In Flutter Map] Our children are the layers of our map. For this example, we only need one layer, which is a Tile Layer, responsible for fetching the map images from Open Street Maps.
Our map is composed of layers on top of each other. For the previous example, we only required one layer: TileLayer. However, the Flutter Map library provides other types of layers, such as MarkerLayer, PolygonLayer, PolylineLayer, CircleLayer, and AttributionLayer.
PolygonLayer
The polygon layer is used for displaying a polygon over our map. Let's take a look:
PolygonLayer(
polygonCulling: false,
polygons: [
Polygon(
points: [
LatLng(36.95, -9.5),
LatLng(42.25, -9.5),
LatLng(42.25, -6.2),
LatLng(36.95, -6.2),
],
color: Colors.blue.withOpacity(0.5),
borderStrokeWidth: 2,
borderColor: Colors.blue,
isFilled: true
),
],
)
The example above is creating a PolygonLayer
widget, which is used to display one or more polygons on the map.
The PolygonLayer
widget takes a list of Polygon
objects as its polygons
parameter. In this case, there is only one polygon defined in the list.
The Polygon
class is used to define a polygon on the map, and it takes several parameters to define the shape, location, and appearance of the polygon.
The polygonCulling
parameter is a boolean value that specifies whether to cull (remove) polygons that are entirely outside the viewable area of the map. Setting it to false
means that all polygons will be rendered regardless of whether they are outside the viewable area of the map.
In this example, the Polygon
is defined with a points
parameter, which is a list of LatLng
objects that define the vertices of the polygon. The color
parameter specifies the fill color of the polygon. In this case, we're placing a square over Portugal. Let's see how it looks like:
PolylineLayer
PolylineLayer(
polylines: [
Polyline(
points: [
LatLng(38.73, -9.14), // Lisbon, Portugal
LatLng(51.50, -0.12), // London, United Kingdom
LatLng(52.37, 4.90), // Amsterdam, Netherlands
],
color: Colors.blue,
strokeWidth: 2,
),
],
)
The example above creates a PolylineLayer
widget, which is used to display one or more polylines on the map.
The PolylineLayer
widget takes a list of Polyline
objects as its polylines
parameter. In this case, there is only one polyline defined in the list.
The Polyline
class is used to define a polyline on the map, and it takes several parameters to define the shape, location, and appearance of the polyline.
In this example, the Polyline
is defined with a points
parameter, which is a list of LatLng
objects that define the vertices of the polyline. The color
parameter specifies the color of the polyline. Therefore, we're drawing a line connecting the capitals of Portugal, England, and the Netherlands.
CircleLayer
CircleLayer(
circles: [
CircleMarker(
point: LatLng(52.2677, 5.1689), // center of 't Gooi
radius: 5000,
useRadiusInMeter: true,
color: Colors.red.withOpacity(0.3),
borderColor: Colors.red.withOpacity(0.7),
borderStrokeWidth: 2,
)
],
)
The example above creates a CircleLayer
widget, which is used to display one or more circles on the map.
The CircleLayer
widget takes a list of CircleMarker
objects as its circles
parameter. In this case, there is only one circle defined in the list.
The CircleMaker
class is used to define a circle on the map, and it takes several parameters to define the shape, location, and appearance of the circle.
In this example, the CircleMarker
is defined with a point
parameter, which specifies the location of the marker on the map as a LatLng
object. The color
parameter specifies the color of the circle and the radius parameter the size of the circle.
We're drawing a circle over the 't Gooi area in the Netherlands with a radius of 5km.
MarkerLayer
The marker layer is the most simple one. We use it for displaying a widget on a specific coordinate. Let's take a look:
MarkerLayer(
markers: [
Marker(
point: LatLng(51.509364, -0.128928),
width: 80,
height: 80,
builder: (context) => FlutterLogo(),
),
],
)
In the example above we create a MarkerLayer
widget, which is used to display one or more markers on the map.
The MarkerLayer
widget takes a list of Marker
objects as its markers
parameter. In this case, there is only one marker defined in the list.
The Marker
class is used to define a marker on the map, and it takes several parameters to define the location, size, and appearance of the marker.
In this example, the Marker
is defined with a point
parameter, which specifies the location of the marker on the map as a LatLng
object. The width
and height
parameters specify the size of the marker in pixels, and the builder
parameter takes a function that returns a Widget
to define the appearance of the marker.
In this example, the builder
function is using the FlutterLogo
widget to display a Flutter logo at the location specified by the point
parameter, which is London.
Placing Words on the Map
As I said before, my objective is to place words on the map, and the MarkerLayer is the perfect solution for it.
Before we create our markers, let's create our text widgets. Starting with the style:
TextStyle getDefaultTextStyle() {
return const TextStyle(
fontSize: 12,
backgroundColor: Colors.black,
color: Colors.white,
);
}
This method will return a TextStyle object defining the font size, the background color, and the color of the font. It'll be used by:
Container buildTextWidget(String word) {
return Container(
alignment: Alignment.center,
child: Text(
word,
textAlign: TextAlign.center,
style: getDefaultTextStyle()
)
);
}
Our Text
widget receives a word as its text and is placed within a Container
widget.
Marker buildMarker(LatLng coordinates, String word) {
return Marker(
point: coordinates,
width: 100,
height: 12,
builder: (context) => buildTextWidget(word)
);
}
The buildTextWidget function is called within our buildMarker function to create the Markers that will be the children of our MarkerLayer widget. They will be created with the coordinates where they'll be placed and the word they'll display.
MarkerLayer(
markers: [
buildMarker(LatLng(39.3999, -8.2245), "Amor"), // Portugal
buildMarker(LatLng(55.3781, -3.4360), "Love"), // England
buildMarker(LatLng(46.2276, 2.2137), "Aimer"), // France
buildMarker(LatLng(52.1326, 5.2913), "Liefde"), // Netherlands
buildMarker(LatLng(51.1657, 10.4515), "Liebe"), // Germany
],
)
And finally:
Conclusion
In this story, we learned the basics of how maps are rendered and a few possibilities when it comes to Tile Servers.
Besides that, we also learned how our map is sliced in layers and how to take advantage of them to place markers, polygons, polylines, and circles on specific coordinates.
To wrap up, we learned through a practical example how to display text labels over specific countries on our map.
What's next?
My journey with Flutter is just getting started. This is just the surface of the first app I've been building.
In my next stories, I'll show how I was able to allow the user to input a word and translate this word into several languages so that it could be displayed on the map.
Besides that, I'll also show how to manipulate your map in more advanced ways, such as increasing and decreasing the size of the labels as we zoom in & out and how to implement a map controller to use buttons instead of gestures to control our map.
Stay tuned!
Final App
Wanna check out the final version of my app? It's now available on the App Store!
https://apps.apple.com/br/app/worldmap-translator/id6447809232
Contribute
Writing takes time and effort. I love writing and sharing knowledge, but I also have bills to pay. If you like my work, please, consider donating through Buy Me a Coffee: https://www.buymeacoffee.com/RaphaelDeLio
Or by sending me BitCoin: 1HjG7pmghg3Z8RATH4aiUWr156BGafJ6Zw
Follow Me on Social Media
Stay connected and dive deeper into the world of Flutter with me! Follow my journey across all major social platforms for exclusive content, tips, and discussions.