The journey of writing a new package
A few days ago, I was looking for a way of displaying a map of the world in my app and be able to select a country on the map. Surprisingly, I haven’t found one meeting my needs (I haven’t searched a lot though).
Most of the packages giving you the opportunity to draw a map are packages using third parties like Google Maps
or OpenStreetMap/Leaflet
. They are powerful solutions but you will need to pay to render the map and what’s more, it cannot select a special area.
So I decided to write my own package! The goal of this article is to show to the people what it could be to write a package in Flutter and to show you a way to proceed to build a project. I hope it will give motivations to some of you!
The Package
I have decided at the first moment that the maps will be stored in svg
files. To those who don’t know what SVG is, it’s a format to design an image with mathematical formulas. It’s useful to have lightweight images that can scale without losing information. And what’s the well-known package to render svg
files in Flutter? It’s flutter_svg
!
flutter_svg
is a good package but it doesn’t allow us to select a specific part of the svg. You have a disc and you want to receive an onTap event? You can’t. So I started to read the source code. Maybe I can fork the project and add some interaction.
It seems flutter_svg
is not doing so much on its own. It depends on a package called vector_graphics. If I understood correctly, vector_graphics
compiles the svg into a binary format that it can read. There’s not a lot of documentation explaining the projects. I didn’t want to spend too much time understanding a library I may use just once in my life.
An SVG file is composed of lines, circles, curves… But… Flutter can draw that too! It has the Painter
class to draw lines and others on a canvas. There also is an interesting class called Path
. It’s used to draw a succession of lines but it has a special property. It has the contains(point)
method that tells us if a point is inside the path. So if I give the cursor position, I would know what path is selected. It’s decided, this is how I will render my map.
Write the SVG Parser
Before rendering a map, I need to read an SVG. It’s not a task very complicated. SVG
is an xml file and it contains different <path>
. A <path>
is associated with one country/region and contains 2 pieces of information:
id
: that’s the code of the country/region/area. It’s defined by the ISO 3166-2 standard.d
: succession of lines, curves, circles etc… In short, the information to draw. Let’s call thatpath
.
The path is composed of commands. The commands are the unique letters in lower case (relative coordinates) and upper case (absolute coordinates) m ; l ; h ; v ; z; c and they are followed by several 2D points: M273.73,261.63l-0.55,0.24
.
To parse it easily, I added spaces between letters and numbers thanks to the regex (\S)?([mlhvzcMLHVZC])
.
I can split the string and go through all the tokens. Each command will be stored in its own class inherited Point
. Then I created a class SvgPath
to accept a country code and a list of Point
.
The SVG Maps
I need to download the SVG maps. I have found a website called MapSVG that provides the maps. Great! I don’t want to manually download the ~200 SVGs so I wrote a little script. It’s basically a web crawler downloading the SVG maps and generating code for an enum I’m going to use with this format: <countryCamelCase>("<filename>")
The Assets Issue
As you may already know, to define assets in Flutter, we need to add that in our pubspec.yaml
file:
flutter:
assets:
- res/maps/
Then, you can call DefaultAssetBundle.of(context).loadString("res/maps/france.svg")
to get the content of the SVG file.
It wasn’t working. I’ve spent hours reading and testing. Should I put the res
folder at the root or in the lib
folder? Are there additional things to do? Why does it not work?
If I had read the documentation a bit better, I would have read that we need to add packages/<my package name>
to my path. That’s all. It was the most time-consuming task of the package…
Render the Map
I have created a Painter called MapPainter
. It takes as input a CountryMap
, the cursor position, and a map theme. There is not much to do. We define 4 different painters for the selected/non-selected borders and the selected/non-selected background and we start to draw.
To draw, we read the information contained by the SvgPath
, the command objects returned by the parser. Each command has its equivalent in the Canvas
class so it’s a simple switch.
To know if a region is selected, we use the Path().contains(point)
method. If it’s true, we use the painters for the selected ones. There’s nothing much to do. Only 80 lines of code.
To add interaction like zoom in/out and moving the map, I’ve started to compute myself the scale factor and use an offset. It was working well but it added a lot of complexity and I couldn’t zoom in on the cursor position. But there’s already a widget that can do that! You may already know it, InteractiveViewer
.
I wrap my CustomPainter
with an InteractiveViewer
and done. All the interaction features are implemented. The project is ready to be deployed.
The License Problem
A user on the FlutterDev
Discord noticed that I wasn’t mentioning the license of MapSVG anywhere in my package. They are using the CC BY 4.0 Deed
license meaning that anyone using their resources must create them, add a link to the license and indicate if changes were made.
I’ve simply added credits to them into my ReadMe. I hope it’s enough. The users may use the LicenseRegistry
to directly add their licenses.
What Could Be Added in the Future
I want to add markers using latitude and longitude. It would give us the opportunity to build some interesting projects:
- Users could pin and save their current localization on a map
- It could be used to use simple flightradar24.com. You won’t have to worry about the Google Maps cost.
- You have several shops in your country? Put them on the map and add interaction on the markers
I’d like to add a way to draw different layers of markers. The idea I have in mind is let’s say I want to build an app to track the position of my train, I’d like to see the train line and the different cities where it makes a stop. The need of layers is to indicate the Z coordinates of the markers.
Instead of filling the regions with a color, I could use an image. It’s used by some people to build interesting charts.
Thank you all for reading this post! I hope it gave ideas to some of you!
This package wasn’t really a complex project. I only needed 3 days to finish it. I don’t think the code quality is so good but it should be enough to be understandable.
Feel free to contribute to this package, you can create an issue and say what you would like.
Github repo
Pub page
My Twitter