Skip to content

External Position Source Navigation

This example demonstrates how to create a Flutter app that utilizes external position sources for navigation on a map using ``gem_kit``package. The app allows users to navigate to a predefined destination while following the route on the map.

external_position_navigation - 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

Navigate to the external_position_navigation directory within the Flutter examples directory. This is the project folder for this example.

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:

:lineno-start: 1

    allprojects {
        repositories {
            google()
            mavenCentral()
            maven {
               url "${rootDir}/../plugins/gem_kit/android/build"
            }
        }
    }

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

:lineno-start: 1

android {
    defaultConfig {
        applicationId "com.magiclane.gem_kit.examples.example_pathname"
        minSdk 21
        targetSdk flutter.targetSdk
        versionCode flutterVersionCode.toInteger()
        versionName flutterVersionName
    }
    buildTypes {
        release {
            minifyEnabled false
            shrinkResources false

            // TODO: Add your own signing config for the release build.
            // Signing with the debug keys for now, so `flutter run --release` works.
            signingConfig signingConfigs.debug
        }
    }
}

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

App entry and initialization

const projectApiToken = String.fromEnvironment('GEM_TOKEN');

void main() {
  runApp(const MyApp());
}

This code initializes the projectApiToken with the required authorization token and launches the app.

How It Works

The example app demonstrates the following features:

  • Uses GemKit SDK to initialize the map.

  • Implements navigation using external position sources.

  • Allows route building and starts navigation with real-time position updates.

UI and Navigation Integration

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

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'External Position Source Navigation',
      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 _isNavigationActive = false;
  bool _hasDataSource = false;
  final DataSource _dataSource = DataSource([DataType.position]);

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("ExternalPositionNavigation",
            style: TextStyle(color: Colors.white)),
        backgroundColor: Colors.deepPurple[900],
        actions: [
          if (!_isNavigationActive && _areRoutesBuilt)
            IconButton(
              onPressed: () => _startNavigation(),
              icon: const Icon(Icons.play_arrow, color: Colors.white),
            ),
          if (_isNavigationActive)
            IconButton(
              onPressed: _stopNavigation,
              icon: const Icon(
                Icons.stop,
                color: Colors.white,
              ),
            ),
          if (!_areRoutesBuilt && _hasDataSource)
            IconButton(
              onPressed: () => _onBuildRouteButtonPressed(context),
              icon: const Icon(
                Icons.route,
                color: Colors.white,
              ),
            ),
          if (!_isNavigationActive)
            IconButton(
                onPressed: _onFollowPositionButtonPressed,
                icon: const Icon(
                  Icons.location_searching_sharp,
                  color: Colors.white,
                ))
        ],
      ),
      body: Stack(children: [
        GemMap(
          onMapCreated: _onMapCreated,
        ),
        if (_isNavigationActive)
          Positioned(
            top: 10,
            left: 10,
            child: Column(children: [
              NavigationInstructionPanel(
                instruction: currentInstruction,
              ),
              const SizedBox(
                height: 10,
              ),
              FollowPositionButton(
                onTap: () => _mapController.startFollowingPosition(),
              ),
            ]),
          ),
        if (_isNavigationActive)
          Positioned(
            bottom: MediaQuery.of(context).padding.bottom + 10,
            left: 0,
            child: NavigationBottomPanel(
              remainingDistance:
                  currentInstruction.getFormattedRemainingDistance(),
              remainingDuration:
                  currentInstruction.getFormattedRemainingDuration(),
              eta: currentInstruction.getFormattedETA(),
            ),
          ),
      ]),
      resizeToAvoidBottomInset: false,
    );
  }
}

This code sets up the user interface, including a map and navigation buttons.

external_position_navigation - example flutter screenshot

Handling Navigation and External Position Data

void _onBuildRouteButtonPressed(BuildContext context) {
  final departureLandmark =
      Landmark.withLatLng(latitude: 34.915646, longitude: -110.147933);
  final destinationLandmark =
      Landmark.withLatLng(latitude: 34.933105, longitude: -110.131363);
  final routePreferences = RoutePreferences();
  _showSnackBar(context, message: 'The route is calculating.');

  _routingHandler = RoutingService.calculateRoute(
      [departureLandmark, destinationLandmark], routePreferences,
      (err, routes) {
    _routingHandler = null;
    ScaffoldMessenger.of(context).clearSnackBars();

    if (err == GemError.routeTooLong) {
      print('The destination is too far from your current location. Change the coordinates of the destination.');
      return;
    }

    if (err == GemError.success) {
      final routesMap = _mapController.preferences.routes;
      for (final route in routes!) {
        routesMap.add(route, route == routes.first,
            label: route.getMapLabel());
      }
      _mapController.centerOnRoutes(routes: routes);
      setState(() {
        _areRoutesBuilt = true;
      });
    }
  });
}

This code handles building the route from a departure point to a destination, notifying the user when the calculation is in progress.

external_position_navigation - example flutter screenshot

Starting Navigation

Future<void> _startNavigation() async {
  final routes = _mapController.preferences.routes;
  _navigationHandler = NavigationService.startNavigation(routes.mainRoute,
      (type, instruction) {
    if (type == NavigationEventType.destinationReached ||
        type == NavigationEventType.error) {
      PositionService.instance.removeDataSource();
      _dataSource.stop();
      setState(() {
        _isNavigationActive = false;
        _cancelRoute();
      });
      _stopNavigation();
      return;
    }
    _isNavigationActive = true;
    if (instruction != null) {
      setState(() => currentInstruction = instruction);
    }
  });

  _mapController.startFollowingPosition();
  await _pushExternalPosition();
}

This method starts the navigation and sets the map to follow the user’s position.

external_position_navigation - example flutter screenshot

Pushing External Position Data

Future<void> _pushExternalPosition() async {
  final route = _mapController.preferences.routes.mainRoute;
  final distance = route.getTimeDistance().totalDistanceM;
  Coordinates prevCoordinates = route.getCoordinateOnRoute(0);

  for (int currentDistance = 1;
      currentDistance <= distance;
      currentDistance += 1) {
    if (!_hasDataSource) return;

    if (currentDistance == distance) {
      _stopNavigation();
      return;
    }

    final currentCoordinates = route.getCoordinateOnRoute(currentDistance);
    await Future<void>.delayed(Duration(milliseconds: 25));
    _dataSource.pushData(
        positionData: ExternalPositionData(
            timestamp: DateTime.now().toUtc().millisecondsSinceEpoch,
            latitude: currentCoordinates.latitude,
            longitude: currentCoordinates.longitude,
            altitude: 0,
            heading: _getHeading(prevCoordinates, currentCoordinates),
            speed: 0));
    prevCoordinates = currentCoordinates;
  }
}

This code manages the position data, updating the user’s location along the route at regular intervals.

Flutter Examples

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