Skip to content

Map download

This example demonstrates how to list the road maps available on the server for download, how to download a map while indicating the download progress, and how to display the download’s finished status.

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

Go to the map_download directory within the Flutter examples directory, which is the name of this 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

map_download - example flutter screenshot

Map Initialization

 1import 'package:gem_kit/core.dart';
 2import 'package:gem_kit/map.dart';
 3import 'maps_page.dart';
 4import 'package:flutter/material.dart';
 5
 6Future<void> main() async {
 7  const projectApiToken = String.fromEnvironment('GEM_TOKEN');
 8  await GemKit.initialize(appAuthorization: projectApiToken);
 9  runApp(const MyApp());
10}
11
12class MyApp extends StatelessWidget {
13  const MyApp({super.key});
14
15  @override
16  Widget build(BuildContext context) {
17    return const MaterialApp(
18      debugShowCheckedModeBanner: false,
19      title: 'Map Download',
20      home: MyHomePage(),
21    );
22  }
23}

The dart material package is imported, as well as the required gem_kit packages. The map is embedded in a widget that serves as the root of the application.

Map Display and UI Components

 1class MyHomePage extends StatefulWidget {
 2  const MyHomePage({super.key});
 3
 4  @override
 5  State<MyHomePage> createState() => _MyHomePageState();
 6}
 7
 8class _MyHomePageState extends State<MyHomePage> {
 9  @override
10  void dispose() {
11    GemKit.release();
12    super.dispose();
13  }
14
15  @override
16  Widget build(BuildContext context) {
17    return Scaffold(
18      appBar: AppBar(
19        backgroundColor: Colors.deepPurple[900],
20        title: const Text(
21          'Map Download',
22          style: TextStyle(color: Colors.white),
23        ),
24        actions: [
25          IconButton(
26            onPressed: () => _onMapButtonTap(context),
27            icon: const Icon(
28              Icons.map_outlined,
29              color: Colors.white,
30            ),
31          ),
32        ],
33      ),
34      body: GemMap(onMapCreated: _onMapCreated),
35    );
36  }
37
38  void _onMapCreated(GemMapController controller) async {
39    SdkSettings.setAllowOffboardServiceOnExtraChargedNetwork(
40        ServiceGroupType.contentService, true);
41  }
42
43  void _onMapButtonTap(BuildContext context) async {
44    Navigator.of(context).push(MaterialPageRoute<dynamic>(
45      builder: (context) => const MapsPage(),
46    ));
47  }
48}

This code defines the main app structure and how it handles the map’s initialization and the navigation to the map download page.

map_download - example flutter screenshot

Maps page

 1import 'package:gem_kit/content_store.dart';
 2import 'package:gem_kit/core.dart';
 3import 'maps_item.dart';
 4import 'package:flutter/material.dart';
 5import 'dart:async';
 6
 7class MapsPage extends StatefulWidget {
 8  const MapsPage({super.key});
 9
10  @override
11  State<MapsPage> createState() => _MapsPageState();
12}
13
14class _MapsPageState extends State<MapsPage> {
15  List<ContentStoreItem> mapsList = [];
16
17  @override
18  Widget build(BuildContext context) {
19    return Scaffold(
20      appBar: AppBar(
21        automaticallyImplyLeading: true,
22        foregroundColor: Colors.white,
23        title: const Text(
24          "Maps List",
25          style: TextStyle(color: Colors.white),
26        ),
27        backgroundColor: Colors.deepPurple[900],
28      ),
29      body: FutureBuilder<List<ContentStoreItem>>(
30          future: _getMaps(),
31          builder: (context, snapshot) {
32            if (!snapshot.hasData || snapshot.data == null) {
33              return const Center(
34                child: CircularProgressIndicator(),
35              );
36            }
37            return Scrollbar(
38              child: ListView.separated(
39                padding: EdgeInsets.zero,
40                itemCount: snapshot.data!.length,
41                separatorBuilder: (context, index) => const Divider(
42                  indent: 50,
43                  height: 0,
44                ),
45                itemBuilder: (context, index) {
46                  final map = snapshot.data!.elementAt(index);
47                  return MapsItem(map: map);
48                },
49              ),
50            );
51          }),
52    );
53  }
54
55  Future<List<ContentStoreItem>> _getMaps() async {
56    Completer<List<ContentStoreItem>> mapsList =
57        Completer<List<ContentStoreItem>>();
58    ContentStore.asyncGetStoreContentList(ContentType.roadMap,
59        (err, items, isCached) {
60      if (err == GemError.success && items != null) {
61        mapsList.complete(items);
62      }
63    });
64    return mapsList.future;
65  }
66}

The MapsPage widget fetches the list of maps available for download and displays them in a scrollable list.

Map Item

  1import 'dart:typed_data';
  2import 'package:gem_kit/content_store.dart';
  3import 'package:gem_kit/core.dart';
  4import 'package:gem_kit/map.dart';
  5import 'package:flutter_slidable/flutter_slidable.dart';
  6import 'package:flutter/material.dart';
  7import 'dart:async';
  8
  9class MapsItem extends StatefulWidget {
 10  final ContentStoreItem map;
 11
 12  const MapsItem({super.key, required this.map});
 13
 14  @override
 15  State<MapsItem> createState() => _MapsItemState();
 16}
 17
 18class _MapsItemState extends State<MapsItem> {
 19  late bool _isDownloaded;
 20
 21  double _downloadProgress = 0;
 22
 23  @override
 24  void initState() {
 25    super.initState();
 26    _isDownloaded = widget.map.isCompleted;
 27    _downloadProgress = widget.map.downloadProgress.toDouble();
 28
 29    if (_isDownloadingOrWaiting()) {
 30      final errCode = widget.map.pauseDownload();
 31      if (errCode != GemError.success) {
 32        print(
 33            "Download pause for item ${widget.map.id} failed with code $errCode");
 34        return;
 35      }
 36
 37      Future<dynamic>.delayed(const Duration(milliseconds: 500))
 38          .then((value) => _downloadMap());
 39    }
 40  }
 41
 42  bool _isDownloadingOrWaiting() {
 43    final status = widget.map.status;
 44    return [
 45      ContentStoreItemStatus.downloadQueued,
 46      ContentStoreItemStatus.downloadRunning,
 47      ContentStoreItemStatus.downloadWaiting,
 48      ContentStoreItemStatus.downloadWaitingFreeNetwork,
 49      ContentStoreItemStatus.downloadWaitingNetwork,
 50    ].contains(status);
 51  }
 52
 53  @override
 54  Widget build(BuildContext context) {
 55    return Slidable(
 56      enabled: _isDownloaded,
 57      endActionPane: ActionPane(
 58          motion: const ScrollMotion(),
 59          extentRatio: 0.25,
 60          children: [
 61            SlidableAction(
 62              onPressed: (context) => _deleteMap(widget.map),
 63              backgroundColor: Colors.red,
 64              foregroundColor: Colors.white,
 65              padding: EdgeInsets.zero,
 66              icon: Icons.delete,
 67            )
 68          ]),
 69      child: ListTile(
 70        onTap: _onTileTap,
 71        leading: Container(
 72          padding: const EdgeInsets.all(8),
 73          width: 50,
 74          child: Image.memory(_getMapImage(widget.map)),
 75        ),
 76        title: Text(
 77          widget.map.name,
 78          style: const TextStyle(
 79              color: Colors.black, fontSize: 16, fontWeight: FontWeight.w600),
 80        ),
 81        subtitle: Text(
 82          "${(widget.map.totalSize / (1024.0 * 1024.0)).toStringAsFixed(2)} MB",
 83          style: const TextStyle(
 84            color: Colors.black,
 85            fontSize: 16,
 86          ),
 87        ),
 88        trailing: SizedBox.square(
 89            dimension: 50,
 90            child: Builder(
 91              builder: (context) {
 92                if (_isDownloaded == true) {
 93                  return const Icon(
 94                    Icons.check_circle_rounded,
 95                    color: Colors.green,
 96                  );
 97                } else {
 98                  return Stack(
 99                    alignment: Alignment.center,
100                    children: [
101                      CircularProgressIndicator(
102                        value: _downloadProgress,
103                        color: Colors.deepPurple[900],
104                      ),
105                      GestureDetector(
106                        onTap: () => _onProgressTap(),
107                        child: Text(
108                          "${(_downloadProgress * 100).toStringAsFixed(0)}%",
109                          style: TextStyle(
110                              color: Colors.deepPurple[900],
111                              fontWeight: FontWeight.bold),
112                        ),
113                      )
114                    ],
115                  );
116                }
117              },
118            )),
119      ),
120    );
121  }
122
123  Future<void> _onTileTap() async {
124    if (_isDownloaded) {
125      final errCode = await widget.map.makeVisibleOnMap();
126      if (!mounted) return;
127      if (errCode != GemError.success) {
128        await showDialog<void>(
129          context: context,
130          builder: (BuildContext context) {
131            return AlertDialog(
132              title: const Text('Error'),
133              content: const Text('Could not make map visible.'),
134              actions: <Widget>[
135                TextButton(
136                  child: const Text('OK'),
137                  onPressed: () {
138                    Navigator.of(context).pop();
139                  },
140                ),
141              ],
142            );
143          },
144        );
145      } else {
146        Navigator.of(context).pop();
147      }
148    } else {
149      await _downloadMap();
150    }
151  }
152
153  Uint8List _getMapImage(ContentStoreItem map) {
154    final image = map.images[0];
155    return Uint8List.fromList(image);
156  }
157
158  Future<void> _downloadMap() async {
159    final errCode = widget.map.resumeDownload((errCode, progress) async {
160      if (errCode != GemError.success) {
161        print(
162            "Download failed for item ${widget.map.id} with code $errCode");
163      }
164
165      if (progress != null) {
166        setState(() {
167          _downloadProgress = progress.toDouble();
168        });
169      }
170
171      if (_downloadProgress == 1.0) {
172        setState(() {
173          _isDownloaded = true;
174        });
175      }
176    });
177    if (errCode != GemError.success) {
178      print("Download failed for item ${widget.map.id} with code $errCode");
179    }
180  }
181
182  void _deleteMap(ContentStoreItem map) async {
183    final result = await showDialog<bool>(
184        context: context,
185        builder: (context) => AlertDialog(
186              title: const Text('Confirm Delete'),
187              content: const Text('Are you sure you want to delete this map?'),
188              actions: [
189                TextButton(
190                    onPressed: () => Navigator.pop(context, false),
191                    child: const Text('Cancel')),
192                TextButton(
193                    onPressed: () => Navigator.pop(context, true),
194                    child: const Text('Delete'))
195              ],
196            ));
197
198    if (result != true) {
199      return;
200    }
201
202    final errCode = map.deleteContent();
203    if (errCode != GemError.success) {
204      print("Delete map ${map.id} failed with code $errCode");
205    }
206    setState(() {
207      _isDownloaded = false;
208      _downloadProgress = 0.0;
209    });
210  }
211
212  void _onProgressTap() async {
213    if (_isDownloadingOrWaiting()) {
214      final errCode = widget.map.pauseDownload();
215      if (errCode != GemError.success) {
216        print(
217            "Download pause for item ${widget.map.id} failed with code $errCode");
218        return;
219      }
220      setState(() {
221        _downloadProgress = widget.map.downloadProgress.toDouble();
222      });
223    } else {
224      await _downloadMap();
225    }
226  }
227}

The MapsItem widget represents each map item in the list, allowing users to download or delete maps and see the download progress. The widget also allows pausing and resuming downloads.

map_download - example flutter screenshot

Flutter Examples

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