Flutter —Free Hand Polygon Drawing on Google Maps

Shady Boshra
4 min readDec 20, 2020

--

So, it’s very interesting to draw free hand polygon on Google Maps. There’s many articles talking about it for Android or iOS, but unfortunately, there’s no one for Flutter. So, I decided to take this challenge and do it for Flutter.

Image illustrate the free hand polygon after drawing on Google Maps
Image illustrate the free hand polygon after drawing on Google Maps

Here’s the steps we will do.

First: Create new Flutter project

You can do so from Android Studio or using command.

flutter create myapp

Second: Adding dependency

We should add google maps dependency which I use google_maps_flutter package, I encourage you to use latest version of it.

google_maps_flutter: ^1.0.6

Don’t forget pub get.

Integration with Android:

Edit the AndroidManifest.xml file

<meta-data android:name="com.google.android.geo.API_KEY"
android:value="YOUR KEY HERE"/>

Integration with iOS:

  • Objective-C: specify your API key in the application delegate ios/Runner/AppDelegate.m
#include "AppDelegate.h"
#include "GeneratedPluginRegistrant.h"
#import "GoogleMaps/GoogleMaps.h"

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[GMSServices provideAPIKey:@"YOUR KEY HERE"];
[GeneratedPluginRegistrant registerWithRegistry:self];
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
@end
  • Swift: specify your API key in the application delegate ios/Runner/AppDelegate.swift
import UIKit
import Flutter
import GoogleMaps

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GMSServices.provideAPIKey("YOUR KEY HERE")
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}

Third: Flutter Google Map UI

  • Adding important dependencies
import 'dart:async';
import 'dart:collection';
import 'dart:io';
import 'dart:math' as Math;

import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
  • Adding some final definitions
final Completer<GoogleMapController> _controller = Completer();

final CameraPosition _kGooglePlex = CameraPosition(
target: LatLng(37.42796133580664, -122.085749655962),
zoom: 14.4746,
);

final Set<Polygon> _polygons = HashSet<Polygon>();
final Set<Polyline> _polyLines = HashSet<Polyline>();
  • Adding main UI, Google Map
GoogleMap(
mapType: MapType.normal,
initialCameraPosition: _kGooglePlex,
polygons: _polygons,
polylines: _polyLines,
onMapCreated: (GoogleMapController controller) {
_controller.complete(controller);
},
),
  • Floating Action Button to manage the activate/deactivate the drawing
FloatingActionButton(
onPressed: _toggleDrawing,
tooltip: 'Drawing',
child: Icon((_drawPolygonEnabled) ? Icons.cancel : Icons.edit),
),

All of them is in Scaffold.

Fourth: Make it able to drawing

It depends on GestureDetector that detect the touches of user in the global position, and then convert it to points for PolyLine, and after finishing user drawing, it converts the points to Polygon.

  • Set new variables that controlling the activate/deactivate of drawing and drawing data
bool _drawPolygonEnabled = false;
List<LatLng> _userPolyLinesLatLngList = List();
bool _clearDrawing = false;
int lastXCoordinate, lastYCoordinate;
  • Wrap GoogleMap widget inside GestureDetector and set the important attributes to it
GestureDetector(
onPanUpdate: (_drawPolygonEnabled) ? _onPanUpdate : null,
onPanEnd: (_drawPolygonEnabled) ? _onPanEnd : null,
child: GoogleMap(
mapType: MapType.normal,
initialCameraPosition: _kGooglePlex,
polygons: _polygons,
polylines: _polyLines,
onMapCreated: (GoogleMapController controller) {
_controller.complete(controller);
},
),
),
  • Toggle button function
_toggleDrawing() {
_clearPolygons();
setState(() => _drawPolygonEnabled = !_drawPolygonEnabled);
}
  • GestureDetector onPanUpdate function
_onPanUpdate(DragUpdateDetails details) async {
// To start draw new polygon every time.
if (_clearDrawing) {
_clearDrawing = false;
_clearPolygons();
}

if (_drawPolygonEnabled) {
double x, y;
if (Platform.isAndroid) {
// It times in 3 without any meaning,
// We think it's an issue with GoogleMaps package.
x = details.globalPosition.dx * 3;
y = details.globalPosition.dy * 3;
} else if (Platform.isIOS) {
x = details.globalPosition.dx;
y = details.globalPosition.dy;
}

// Round the x and y.
int xCoordinate = x.round();
int yCoordinate = y.round();

// Check if the distance between last point is not too far.
// to prevent two fingers drawing.
if (_lastXCoordinate != null && _lastYCoordinate != null) {
var distance = Math.sqrt(Math.pow(xCoordinate - _lastXCoordinate, 2) + Math.pow(yCoordinate - _lastYCoordinate, 2));
// Check if the distance of point and point is large.
if (distance > 80.0) return;
}

// Cached the coordinate.
_lastXCoordinate = xCoordinate;
_lastYCoordinate = yCoordinate;

ScreenCoordinate screenCoordinate = ScreenCoordinate(x: xCoordinate, y: yCoordinate);

final GoogleMapController controller = await _controller.future;
LatLng latLng = await controller.getLatLng(screenCoordinate);

try {
// Add new point to list.
_userPolyLinesLatLngList.add(latLng);

_polyLines.removeWhere((polyline) => polyline.polylineId.value == 'user_polyline');
_polyLines.add(
Polyline(
polylineId: PolylineId('user_polyline'),
points: _userPolyLinesLatLngList,
width: 2,
color: Colors.blue,
),
);
} catch (e) {
print(" error painting $e");
}
setState(() {});
}
}
  • GestureDetector onPanEnd function
_onPanEnd(DragEndDetails details) async {
// Reset last cached coordinate
_lastXCoordinate = null;
_lastYCoordinate = null;

if (_drawPolygonEnabled) {
_polygons.removeWhere((polygon) => polygon.polygonId.value == 'user_polygon');
_polygons.add(
Polygon(
polygonId: PolygonId('user_polygon'),
points: _userPolyLinesLatLngList,
strokeWidth: 2,
strokeColor: Colors.blue,
fillColor: Colors.blue.withOpacity(0.4),
),
);
setState(() {
_clearDrawing = true;
});
}
}
  • Clear polygon and polylines function
_clearPolygons() {
setState(() {
_polyLines.clear();
_polygons.clear();
_userPolyLinesLatLngList.clear();
});
}

Result

Summary

I did this approach with my-self as a challenge, and I think I did it well. It may be there’s other approaches. So you can choose to choose your own code.

I uploaded my full code on GitHub Repo, you can check it, fork it and use it as an opensource. Feel free to add any comment.

Special thanks to GrandTK company, which its team encouraged me to accept this challenge.

--

--

Shady Boshra

I am Mid-Level Flutter Developer, who is excited with growing more the Flutter community.