Skip to content

Public Transit

This example demonstrates how to create a Flutter app that displays public transit routes using gem_kit package.
Users can calculate and visualize routes between two landmarks.

public_transit - main screen

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 project folder for this example to build and run the application.

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

  1. Main App Setup: The main app initializes GemKit and displays a map.

  2. Route Calculation: Users can tap a button to calculate a route between two predefined landmarks.

  3. Route Visualization: The calculated route is displayed on the map, along with transit segments at the bottom of the screen.

User Interface

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

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

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

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

Main Screen with Transit Routes

class _MyHomePageState extends State<MyHomePage> {
  late GemMapController _mapController;
  TaskHandler? _routingHandler;
  List<PTRouteSegment>? _ptSegments;

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Colors.deepPurple[900],
        title: const Text('Public Transit', style: TextStyle(color: Colors.white)),
        actions: [
          if (_routingHandler == null && _ptSegments == null)
            IconButton(
              onPressed: () => _onBuildRouteButtonPressed(context),
              icon: const Icon(Icons.route, color: Colors.white),
            ),
          if (_routingHandler != null)
            IconButton(
              onPressed: () => _onCancelRouteButtonPressed(),
              icon: const Icon(Icons.stop, color: Colors.white),
            ),
          if (_ptSegments != null)
            IconButton(
              onPressed: () => _onClearRoutesButtonPressed(),
              icon: const Icon(Icons.clear, color: Colors.white),
            ),
        ],
      ),
      body: Stack(alignment: AlignmentDirectional.bottomCenter, children: [
        GemMap(onMapCreated: _onMapCreated, appAuthorization: projectApiToken),
        if (_ptSegments != null)
          Padding(
            padding: const EdgeInsets.all(8.0),
            child: Container(
              height: MediaQuery.of(context).size.height * 0.1,
              width: MediaQuery.of(context).size.width * 0.9,
              color: Colors.white,
              child: Row(
                children: _ptSegments!.map((segment) {
                  return TransitSegment(segment: segment);
                }).toList(),
              ),
            ),
          ),
      ]),
    );
  }

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

  // Additional methods for route calculation...
}

This code sets up the main screen with a map and functionality for calculating and displaying public transit routes.

Route Calculation

void _onBuildRouteButtonPressed(BuildContext context) {
  final departureLandmark = Landmark.withLatLng(latitude: 51.505929, longitude: -0.097579);
  final destinationLandmark = Landmark.withLatLng(latitude: 51.507616, longitude: -0.105036);
  final routePreferences = RoutePreferences(transportMode: RouteTransportMode.public);

  _showSnackBar(context, message: "The route is being calculated.");

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

    if (err == GemError.success) {
      final routesMap = _mapController.preferences.routes;
      routesMap.add(routes!.first, true, label: routes.first.getMapLabel());
      _mapController.centerOnRoutes(routes: routes);
      final ptRoute = routes.first.toPTRoute();
      final ptSegments = ptRoute!.segments.map((seg) => seg.toPTRouteSegment()).toList();

      setState(() {
        _ptSegments = ptSegments;
      });
    }
  });

  setState(() {});
}

This code handles the route calculation and updates the UI with the calculated segments.

Transit Segment Display

class TransitSegment extends StatelessWidget {
  final PTRouteSegment segment;

  const TransitSegment({super.key, required this.segment});

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 8.0),
      child: segment.transitType == TransitType.walk
          ? Row(
              children: [
                const Icon(Icons.directions_walk, size: 35.0),
                Text(convertDuration(segment.timeDistance.totalDistanceM)),
              ],
            )
          : Row(
              children: [
                const Icon(Icons.directions_bus_outlined, size: 35.0),
                if (segment.hasWheelchairSupport) const Icon(Icons.accessible_forward),
                Container(color: Colors.green, child: Text(segment.shortName)),
              ],
            ),
    );
  }
}

This widget displays transit segments and relevant information about each route leg.

Utility Functions

String convertDistance(int meters) {
  if (meters >= 1000) {
    double kilometers = meters / 1000;
    return '${kilometers.toStringAsFixed(1)} km';
  } else {
    return '${meters.toString()} m';
  }
}

String convertDuration(int seconds) {
  int hours = seconds ~/ 3600;
  int minutes = (seconds % 3600) ~/ 60;

  String hoursText = (hours > 0) ? '$hours h ' : '';
  String minutesText = '$minutes min';

  return hoursText + minutesText;
}

These utility functions convert distances and durations into user-friendly formats.

Flutter Examples

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