Skip to content

Route Instructions

In this guide you will learn how to display a map, calculate routes between multiple points, and show detailed route instructions.

route_instructions - example flutter screenshot

Setup

First, get an API key token, see the Getting Started guide.

Prerequisites

Make sure you completed the Environment Setup - Flutter Examples guide before starting this guide.

Build and Run

Start a terminal/command prompt and navigate to the route_instructions directory within the Flutter examples directory. This is the name of the example project.

Note - the gem_kit directory containing the Maps SDK for Flutter should be in the plugins directory of the example, e.g. example_pathname/plugins/gem_kit - see the environment setup guide above.

Run: flutter pub get

Configure the native parts:

First, verify that the ANDROID_SDK_ROOT environment variable is set to the root path of your android SDK.

In android/build.gradle add the maven block as shown, within the allprojects block, for both debug and release builds:

1    allprojects {
2        repositories {
3            google()
4            mavenCentral()
5            maven {
6               url "${rootDir}/../plugins/gem_kit/android/build"
7            }
8        }
9    }

in android/app/build.gradle within the android block, in the defaultConfig block, the android SDK version minSdk must be set as shown below.

Additionally, for release builds, in android/app/build.gradle, within the android block, add the buildTypes block as shown:

Replace example_pathname with the actual project pathname

 1android {
 2    defaultConfig {
 3        applicationId "com.magiclane.gem_kit.examples.example_pathname"
 4        minSdk 21
 5        targetSdk flutter.targetSdk
 6        versionCode flutterVersionCode.toInteger()
 7        versionName flutterVersionName
 8    }
 9    buildTypes {
10        release {
11            minifyEnabled false
12            shrinkResources false
13
14            // TODO: Add your own signing config for the release build.
15            // Signing with the debug keys for now, so `flutter run --release` works.
16            signingConfig signingConfigs.debug
17        }
18    }
19}

In the ios/Podfile configuration file, at the top, set the minimum ios platform version to 14 like this:

platform :ios, '14.0'

We recommend you to run these commands after you copy the gem_kit into your project: flutter clean flutter pub get and cd ios pod install

Then run the project:

flutter run --debug
or
flutter run --release

How it Works

This example showcases the use of the gem_kit package to display a map, calculate routes between multiple points, and show detailed route instructions.

route_instructions - example flutter screenshot

Import Necessary Packages

First, import the required packages in your Dart code.

1import 'package:gem_kit/core.dart';
2import 'package:gem_kit/map.dart';
3import 'package:gem_kit/routing.dart';
4import 'route_instructions_page.dart';
5import 'utility.dart';
6import 'package:flutter/material.dart' hide Route;

Initialize GemKit

In the main function, initialize GemKit with your project API token.

1Future<void> main() async {
2  const projectApiToken = String.fromEnvironment('GEM_TOKEN');
3  await GemKit.initialize(appAuthorization: projectApiToken);
4  runApp(const MyApp());
5}

Build the Main Application

Define the main application widget, MyApp.

 1class MyApp extends StatelessWidget {
 2  const MyApp({super.key});
 3
 4  @override
 5  Widget build(BuildContext context) {
 6    return const MaterialApp(
 7      debugShowCheckedModeBanner: false,
 8      title: 'Route Instructions',
 9      home: MyHomePage(),
10    );
11  }
12}

Handle Maps and Routes in the Stateful Widget

route_instructions - example flutter screenshot

Create the stateful widget, MyHomePage, which will handle the map and routing functionality.

1class MyHomePage extends StatefulWidget {
2  const MyHomePage({super.key});
3
4  @override
5  State<MyHomePage> createState() => _MyHomePageState();
6}

Define State Variables and Methods

Within _MyHomePageState, define the necessary state variables and methods to interact with the map and manage routes.

  1class _MyHomePageState extends State<MyHomePage> {
  2  late GemMapController _mapController;
  3  TaskHandler? _routingHandler;
  4  bool _areRoutesBuilt = false;
  5  List<RouteInstruction>? instructions;
  6
  7  @override
  8  void dispose() {
  9    GemKit.release();
 10    super.dispose();
 11  }
 12
 13  @override
 14  Widget build(BuildContext context) {
 15    return Scaffold(
 16      appBar: AppBar(
 17        backgroundColor: Colors.deepPurple[900],
 18        title: const Text("Route Instructions",
 19            style: TextStyle(color: Colors.white)),
 20        actions: [
 21          if (_areRoutesBuilt)
 22            IconButton(
 23              onPressed: _onRouteCancelButtonPressed,
 24              icon: const Icon(Icons.cancel, color: Colors.white),
 25            ),
 26          if (!_areRoutesBuilt)
 27            IconButton(
 28              onPressed: () => _onBuildRouteButtonRoute(context),
 29              icon: const Icon(Icons.route, color: Colors.white),
 30            ),
 31        ],
 32        leading: Row(
 33          children: [
 34            if (_areRoutesBuilt)
 35              IconButton(
 36                onPressed: _onRouteInstructionsButtonPressed,
 37                icon: const Icon(Icons.density_medium_sharp, color: Colors.white),
 38              ),
 39          ],
 40        ),
 41      ),
 42      body: GemMap(
 43        onMapCreated: _onMapCreated,
 44      ),
 45    );
 46  }
 47
 48  void _onMapCreated(GemMapController controller) {
 49    _mapController = controller;
 50  }
 51
 52  void _onBuildRouteButtonRoute(BuildContext context) {
 53    final departureLandmark =
 54        Landmark.withLatLng(latitude: 50.11428, longitude: 8.68133);
 55    final intermediaryPointLandmark =
 56        Landmark.withLatLng(latitude: 49.0069, longitude: 8.4037);
 57    final destinationLandmark =
 58        Landmark.withLatLng(latitude: 48.1351, longitude: 11.5820);
 59
 60    final routePreferences = RoutePreferences();
 61    _showSnackBar(context, message: 'The route is calculating.');
 62
 63    _routingHandler = RoutingService.calculateRoute(
 64        [departureLandmark, intermediaryPointLandmark, destinationLandmark],
 65        routePreferences, (err, routes) async {
 66      _routingHandler = null;
 67      ScaffoldMessenger.of(context).clearSnackBars();
 68
 69      if (err == GemError.success) {
 70        final routesMap = _mapController.preferences.routes;
 71
 72        for (final route in routes!) {
 73          routesMap.add(route, route == routes.first,
 74              label: route.getMapLabel());
 75        }
 76
 77        _mapController.centerOnRoutes(routes);
 78
 79        instructions = _getInstructionsFromSegments(routes.first.segments);
 80        setState(() {
 81          _areRoutesBuilt = true;
 82        });
 83      }
 84    });
 85  }
 86
 87  void _onRouteCancelButtonPressed() async {
 88    _mapController.preferences.routes.clear();
 89
 90    if (_routingHandler != null) {
 91      RoutingService.cancelRoute(_routingHandler!);
 92      _routingHandler = null;
 93    }
 94
 95    if (instructions != null) {
 96      instructions!.clear();
 97    }
 98
 99    setState(() {
100      _areRoutesBuilt = false;
101    });
102  }
103
104  void _onRouteInstructionsButtonPressed() {
105    Navigator.of(context).push(MaterialPageRoute<dynamic>(
106        builder: (context) =>
107            RouteInstructionsPage(instructionList: instructions!)));
108  }
109
110  List<RouteInstruction> _getInstructionsFromSegments(
111      List<RouteSegment> segments) {
112    List<RouteInstruction> instructionsList = [];
113
114    for (final segment in segments) {
115      final segmentInstructions = segment.instructions;
116      instructionsList.addAll(segmentInstructions);
117    }
118    return instructionsList;
119  }
120
121  void _showSnackBar(BuildContext context,
122      {required String message, Duration duration = const Duration(hours: 1)}) {
123    final snackBar = SnackBar(
124      content: Text(message),
125      duration: duration,
126    );
127
128    ScaffoldMessenger.of(context).showSnackBar(snackBar);
129  }
130}

Route Instructions Page

The RouteInstructionsPage displays detailed route instructions. Here is the code for RouteInstructionsPage and the InstructionsItem widget.

 1import 'package:gem_kit/core.dart';
 2import 'utility.dart';
 3import 'package:flutter/material.dart';
 4
 5class RouteInstructionsPage extends StatefulWidget {
 6  final List<RouteInstruction> instructionList;
 7
 8  const RouteInstructionsPage({super.key, required this.instructionList});
 9
10  @override
11  State<RouteInstructionsPage> createState() => _RouteInstructionsState();
12}
13
14class _RouteInstructionsState extends State<RouteInstructionsPage> {
15  @override
16  Widget build(BuildContext context) {
17    return Scaffold(
18      appBar: AppBar(
19        automaticallyImplyLeading: true,
20        title: const Text("Route Instructions", style: TextStyle(color: Colors.white)),
21        backgroundColor: Colors.deepPurple[900],
22        foregroundColor: Colors.white,
23      ),
24      body: ListView.separated(
25        padding: EdgeInsets.zero,
26        itemCount: widget.instructionList.length,
27        separatorBuilder: (context, index) => const Divider(
28          indent: 50,
29          height: 0,
30        ),
31        itemBuilder: (contex, index) {
32          final instruction = widget.instructionList.elementAt(index);
33          return InstructionsItem(instruction: instruction);
34        },
35      ),
36    );
37  }
38}
39
40class InstructionsItem extends StatefulWidget {
41  final RouteInstruction instruction;
42  const InstructionsItem({super.key, required this.instruction});
43
44  @override
45  State<InstructionsItem> createState() => _InstructionsItemState();
46}
47
48class _InstructionsItemState extends State<InstructionsItem> {
49  @override
50  Widget build(BuildContext context) {
51    return ListTile(
52      leading: Container(
53        padding: const EdgeInsets.all(8),
54        width: 50,
55        child: Image.memory(widget.instruction.turnDetails
56            .getAbstractGeometryImage(renderSettings: const AbstractGeometryImageRenderSettings())),
57      ),
58      title: Text(
59        widget.instruction.turnInstruction,
60        overflow: TextOverflow.fade,
61        style: const TextStyle(color: Colors.black, fontSize: 14, fontWeight: FontWeight.w400),
62        maxLines: 2,
63      ),
64      subtitle: Text(
65        widget.instruction.followRoadInstruction,
66        overflow: TextOverflow.fade,
67        style: const TextStyle(color: Colors.black, fontSize: 14, fontWeight: FontWeight.w400),
68        maxLines: 2,
69      ),
70      trailing: Text(
71        widget.instruction.getFormattedDistanceUntilInstruction(),
72        overflow: TextOverflow.fade,
73        style: const TextStyle(color: Colors.black, fontSize: 14, fontWeight: FontWeight.w400),
74      ),
75    );
76  }
77}

This example guides you through the setup and implementation of route instructions in a Flutter application using the gem_kit package. The focus is on handling maps, user interactions, and route preferences to provide a seamless user experience for calculating and displaying route instructions.

Flutter Examples

Maps SDK for Flutter Examples can be downloaded or cloned with Git