# Speed Watcher

Last updated:

<!-- -->

October 18, 2026

<!-- -->

\| 5 minutes read

<br />

<br />

In this guide, you will learn how to monitor speed using an interactive map, calculate a route based on GPS data, and visualize navigation instructions.

## How it works[​](#how-it-works "Direct link to How it works")

This example demonstrates the following features:

* Compute a route between two points.
* Simulate navigation on route.
* Monitor speed while navigating.

![Initial map screen](/docs/flutter/assets/images/example_flutter_speed_watcher1-cc750801a24867b5e5f9e548ae069702.png)

**Initial map screen**

![Computed route](/docs/flutter/assets/images/example_flutter_speed_watcher2-b1914f6e05e514ef4e9350dca84e1f41.png)

**Computed route**

![Speed watcher displayed while navigating](/docs/flutter/assets/images/example_flutter_speed_watcher3-be78c97ea9fa1be3c2f067b3aa9d3364.png)

**Speed watcher displayed while navigating**

### UI and Map Integration[​](#ui-and-map-integration "Direct link to UI and Map Integration")

main.dart[View on Github](https://github.com/magiclane/magiclane-maps-sdk-examples-for-flutter/tree/main/speed_watcher/lib/main.dart?ref_type=heads#L19)

```dart
class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Speed Watcher',
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  late GemMapController _mapController;
  late NavigationInstruction currentInstruction;

  bool _areRoutesBuilt = false;
  bool _isSimulationActive = false;

  // We use the progress listener to cancel the route calculation.
  TaskHandler? _routingHandler;

  // We use the progress listener to cancel the navigation.
  TaskHandler? _navigationHandler;

  @override
  void dispose() {
    GemKit.release();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text(
          "Speed Watcher",
          style: TextStyle(color: Colors.white),
        ),
        backgroundColor: Colors.deepPurple[900],
        actions: [
          if (!_isSimulationActive && _areRoutesBuilt)
            IconButton(
              onPressed: _startSimulation,
              icon: const Icon(Icons.play_arrow, color: Colors.white),
            ),
          if (_isSimulationActive)
            IconButton(
              onPressed: _stopSimulation,
              icon: const Icon(Icons.stop, color: Colors.white),
            ),
          if (!_areRoutesBuilt)
            IconButton(
              onPressed: () => _onBuildRouteButtonPressed(context),
              icon: const Icon(Icons.route, color: Colors.white),
            ),
        ],
      ),
      body: Stack(
        children: [
          GemMap(
            key: ValueKey("GemMap"),
            onMapCreated: _onMapCreated,
            appAuthorization: projectApiToken,
          ),
          if (_isSimulationActive)
            const Align(
              alignment: Alignment.topCenter,
              child: SpeedIndicator(),
            ),
          if (_isSimulationActive)
            Align(
              alignment: Alignment.bottomCenter,
              child: FollowPositionButton(
                onTap: () => _mapController.startFollowingPosition(),
              ),
            ),
        ],
      ),
      resizeToAvoidBottomInset: false,
    );
  }

  void _onMapCreated(GemMapController controller) {
    _mapController = controller;
  }

```

### Custom Method for Building and Canceling the Route[​](#custom-method-for-building-and-canceling-the-route "Direct link to Custom Method for Building and Canceling the Route")

Define a method for building the route based on the departure and destination landmarks.

main.dart[View on Github](https://github.com/magiclane/magiclane-maps-sdk-examples-for-flutter/tree/main/speed_watcher/lib/main.dart?ref_type=heads#L115)

```dart
  void _onBuildRouteButtonPressed(BuildContext context) {
    // Define the departure.
    final departureLandmark = Landmark.withLatLng(
      latitude: 41.898499,
      longitude: 12.526655,
    );

    // Define the destination.
    final destinationLandmark = Landmark.withLatLng(
      latitude: 41.891037,
      longitude: 12.492692,
    );

    // Define the route preferences.
    final routePreferences = RoutePreferences();
    _showSnackBar(context, message: 'The route is calculating.');

    // Calling the calculateRoute SDK method.
    // (err, results) - is a callback function that gets called when the route computing is finished.
    // err is an error enum, results is a list of routes.
    _routingHandler = RoutingService.calculateRoute(
      [departureLandmark, destinationLandmark],
      routePreferences,
      (err, routes) async {
        // If the route calculation is finished, we don't have a progress listener anymore.
        _routingHandler = null;

        ScaffoldMessenger.of(context).clearSnackBars();

        // If there aren't any errors, we display the routes.
        if (err == GemError.success) {
          // Get the routes collection from map preferences.
          final routesMap = _mapController.preferences.routes;

          // Display the routes on map.
          for (final route in routes) {
            routesMap.add(
              route,
              route == routes.first,
              label: getMapLabel(route),
            );
          }

          // Center the camera on routes.
          _mapController.centerOnRoutes(routes: routes);
        }
        setState(() {
          _areRoutesBuilt = true;
        });
      },
    );
  }

  void _cancelRoute() {
    // Remove the routes from map.
    _mapController.preferences.routes.clear();

    if (_routingHandler != null) {
      // Cancel the navigation.
      RoutingService.cancelRoute(_routingHandler!);
      _routingHandler = null;
    }

    setState(() {
      _areRoutesBuilt = false;
    });
  }

```

### Starting and stopping the Simulation[​](#starting-and-stopping-the-simulation "Direct link to Starting and stopping the Simulation")

Define the method for starting and stopping the navigation simulation.

main.dart[View on Github](https://github.com/magiclane/magiclane-maps-sdk-examples-for-flutter/tree/main/speed_watcher/lib/main.dart?ref_type=heads#L169)

```dart
  void _startSimulation() {
    final routes = _mapController.preferences.routes;

    if (routes.mainRoute == null) {
      _showSnackBar(context, message: "No main route available");
      return;
    }

    _navigationHandler = NavigationService.startSimulation(
      routes.mainRoute!,
      onNavigationInstruction: (instruction, events) {
        _isSimulationActive = true;

        setState(() => currentInstruction = instruction);
      },
      onDestinationReached: (landmark) {
        setState(() {
          _isSimulationActive = false;
          _cancelRoute();
        });
      },
      onError: (error) {
        setState(() {
          _isSimulationActive = false;
          _cancelRoute();
        });
      },
    );

    // Set the camera to follow position.
    _mapController.startFollowingPosition();
  }

  void _stopSimulation() {
    // Cancel the navigation.
    NavigationService.cancelNavigation(_navigationHandler);
    _navigationHandler = null;

    _cancelRoute();

    setState(() => _isSimulationActive = false);
  }

```

### Speed Watcher[​](#speed-watcher-1 "Direct link to Speed Watcher")

This is the code for displaying and getting current speed data.

speed\_indicator.dart[View on Github](https://github.com/magiclane/magiclane-maps-sdk-examples-for-flutter/tree/main/speed_watcher/lib/speed_indicator.dart?ref_type=heads#L10)

```dart
class SpeedIndicator extends StatefulWidget {
  const SpeedIndicator({super.key});

  @override
  State<SpeedIndicator> createState() => _SpeedIndicatorState();
}

class _SpeedIndicatorState extends State<SpeedIndicator> {
  double _currentSpeed = 0;
  double _speedLimit = 0;

  @override
  void initState() {
    // Listen to the current position to detect the current speed and the speed limit.
    PositionService.addImprovedPositionListener((position) {
      if (mounted) {
        setState(() {
          _currentSpeed = position.speed;
          _speedLimit = position.speedLimit;
        });
      }
    });
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      height: 100,
      width: 200,
      margin: const EdgeInsets.only(top: 10),
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(15),
      ),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: [
          const Text('Current speed:'),
          Text('${mpsToKmph(_currentSpeed)} km/h'),
          const SizedBox(height: 10),
          const Text('Speed limit:'),
          Text('${mpsToKmph(_speedLimit)} km/h'),
        ],
      ),
    );

```

### Class for the Follow Position Button[​](#class-for-the-follow-position-button "Direct link to Class for the Follow Position Button")

Define a button that allows the user to recenter the map on their position.

main.dart[View on Github](https://github.com/magiclane/magiclane-maps-sdk-examples-for-flutter/tree/main/speed_watcher/lib/main.dart?ref_type=heads#L241)

```dart
class FollowPositionButton extends StatelessWidget {
  const FollowPositionButton({super.key, required this.onTap});

  final VoidCallback onTap;

  @override
  Widget build(BuildContext context) {
    return InkWell(
      onTap: onTap,
      child: Container(
        height: 50,
        width: 125,
        margin: const EdgeInsets.only(bottom: 10),
        padding: const EdgeInsets.symmetric(horizontal: 10),
        decoration: BoxDecoration(
          color: Colors.white,
          borderRadius: const BorderRadius.all(Radius.circular(20)),
          boxShadow: [
            BoxShadow(
              color: Colors.grey.withValues(alpha: 0.5),
              spreadRadius: 5,
              blurRadius: 7,
              offset: const Offset(0, 3),
            ),
          ],
        ),
        child: const Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: [
            Icon(Icons.navigation),
            Text(
              'Recenter',
              style: TextStyle(
                color: Colors.black,
                fontSize: 16,
                fontWeight: FontWeight.w600,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

```
