External Position Source Navigation
This example demonstrates how to create a Flutter app that utilizes external position sources for navigation on a map using Maps SDK for Flutter. The app allows users to navigate to a predefined destination while following the route on the map.
How It Works
The example app demonstrates the following features:
- Initialize a map.
- Navigation using external position sources.
- Allows route building and starts navigation with real-time position updates.
![]() | ![]() | ![]() |
---|---|---|
Initial map screen | Computed route | Navigating on route based on external positions |
UI and Navigation Integration
This code sets up the user interface, including a map and navigation buttons.
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
title: 'External Position Source Navigation',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
late GemMapController _mapController;
late NavigationInstruction currentInstruction;
late DataSource _dataSource;
bool _areRoutesBuilt = false;
bool _isNavigationActive = false;
bool _hasDataSource = false;
TaskHandler? _routingHandler;
TaskHandler? _navigationHandler;
void dispose() {
GemKit.release();
super.dispose();
}
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(
key: ValueKey("GemMap"),
onMapCreated: _onMapCreated,
appAuthorization: projectApiToken,
),
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,
);
}
}
Handling Navigation and External Position Data
This code handles building the route from a departure point to a destination, notifying the user when the calculation is in progress.
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;
});
}
});
}
Starting Navigation
This method starts the navigation and sets the map to follow the user’s position.
Future<void> _startNavigation() async {
final routes = _mapController.preferences.routes;
if (routes.mainRoute == null) {
_showSnackBar(context, message: "Route is not available");
return;
}
_navigationHandler = NavigationService.startNavigation(
routes.mainRoute!,
null,
onNavigationInstruction: (instruction, events) {
setState(() {
_isNavigationActive = true;
});
currentInstruction = instruction;
},
onError: (error) {
PositionService.instance.removeDataSource();
_dataSource.stop();
setState(() {
_isNavigationActive = false;
_cancelRoute();
});
if (error != GemError.cancel) {
_stopNavigation();
}
return;
},
onDestinationReached: (landmark) {
PositionService.instance.removeDataSource();
_dataSource.stop();
setState(() {
_isNavigationActive = false;
_cancelRoute();
});
_stopNavigation();
return;
},
);
_mapController.startFollowingPosition();
await _pushExternalPosition();
}
Pushing External Position Data
This code manages the position data, updating the user’s location along the route at regular intervals.
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(
SenseDataFactory.positionFromExternalData(
ExternalPositionData(
timestamp: DateTime.now().toUtc().millisecondsSinceEpoch,
latitude: currentCoordinates.latitude,
longitude: currentCoordinates.longitude,
altitude: 0,
heading: _getHeading(prevCoordinates, currentCoordinates),
speed: 0,
),
),
);
prevCoordinates = currentCoordinates;
}
}
Top Navigation Instruction Panel
class SearchResultItem extends StatefulWidget {
final Landmark landmark;
const SearchResultItem({super.key, required this.landmark});
State<SearchResultItem> createState() => _SearchResultItemState();
}
class _SearchResultItemState extends State<SearchResultItem> {
Widget build(BuildContext context) {
return ListTile(
onTap: () => Navigator.of(context).pop(widget.landmark),
leading: Container(
padding: const EdgeInsets.all(8),
child:
widget.landmark.img.isValid ? Image.memory(widget.landmark.img.getRenderableImageBytes(size: Size(50, 50))!) : SizedBox(),
),
title: Text(
widget.landmark.name,
overflow: TextOverflow.fade,
style: const TextStyle(
color: Colors.black,
fontSize: 14,
fontWeight: FontWeight.w400,
),
maxLines: 2,
),
subtitle: Text(
'${widget.landmark.getFormattedDistance()} ${widget.landmark.getAddress()}',
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: Colors.black,
fontSize: 14,
fontWeight: FontWeight.w400,
),
),
);
}
}
Flutter Examples
Maps SDK for Flutter Examples can be downloaded or cloned with Git.