Learn how to download and display an offline map

Offline maps
In this tutorial, you will download an offline map
Prerequisites
Before starting this tutorial:
-
You need an ArcGIS Location Platform or ArcGIS Online account.
-
Your system meets the system requirements.
-
You need an IDE for Flutter - we recommend VS Code.
Develop or download
You have two options for completing this tutorial:
Option 1: Develop the code
To start the tutorial, complete the Display a map tutorial. This creates a map to display the Santa Monica Mountains in California using the topographic basemap from the ArcGIS Basemap Styles service
Open Flutter project
-
Open the project you created by completing the Display a map tutorial.
-
Continue with the following instructions to download and display an offline map for a user-defined geographical area of a web map.
Import additional library and package
As part of the offline workflow, you will need to store a downloaded map to your device. You will add the dart:io library to gain access to an API to deal with files and directories and the path_provider package to access local files on a device.
-
Open lib\main.dart.
-
From the menu bar, select View > Terminal to open a new terminal, if necessary.
-
Add the
path_providerpackage to the project as a dependency running the following command:flutter pub add path_providerThe
path_providerpackage provides a Flutter plugin for finding commonly used locations on the file system. You can learn more about this package on pub.dev. -
Import
path_provideranddart.iointomain.dart.main.dartimport 'dart:io';import 'package:path_provider/path_provider.dart';The
dart:iolibrary provides API to deal with files and directories. You can read more about the library here.
Get the web map item ID
You can use ArcGIS tools
- Go to the Naperville water network
in the Map Viewer
Map Viewer is a browser-based mapping tool that can view, create, and save web maps. It can also perform mapping, visualization, and spatial analysis operations. in ArcGIS OnlineArcGIS Online is a GIS mapping, analytics, data hosting, and content management software as a service (SaaS) product. It includes applications, tools, APIs, and location services for users and developers. It is subscription-based and requires an ArcGIS Online account. . This web mapA web map is a map stored as a JSON object that defines properties such as the basemap layer, data layers, layer styles, and pop-up styles. Its JSON structure is defined by the web map specification. displays stormwater network within Naperville, IL, USA . - Make a note of the item ID
An item ID is a unique identifier representing a single item stored, managed, and accessed in a portal, such as a web map, hosted layer, or file. at the end of the browser’s URL. The item ID should be:acc027394bc84c2fb04d1ed317aac674
Display the web map
You can display a web map
-
Replace all the code contained in
onMapViewReady()to instead create a portal item using the item ID from the ArcGIS Online web map.main.dartvoid onMapViewReady() {// Create an ArcGIS Online portal item using the item ID.final portalItem = PortalItem.withPortalAndItemId(portal: Portal.arcGISOnline(),itemId: 'acc027394bc84c2fb04d1ed317aac674',);} -
Define a final
mapvariable and set its value to an instance ofArcGISMapusing thePortalItem.main.dart > onMapViewReady()// Create an ArcGIS Online portal item using the item ID.final portalItem = PortalItem.withPortalAndItemId(portal: Portal.arcGISOnline(),itemId: 'acc027394bc84c2fb04d1ed317aac674',);// Define an ArcGIS map variable to hold on to the web map.final map = ArcGISMap.withItem(portalItem); -
Set the
mapto theArcGISMapViewController‘sarcGISMapproperty.main.dart > onMapViewReady()// Define an ArcGIS map variable to hold on to the web map.final map = ArcGISMap.withItem(portalItem);// Set the map to the map view controller's ArcGIS map property._mapViewController.arcGISMap = map; -
Make sure you have an Android emulator, iOS simulator or physical device configured and running.
-
In VS Code, select Run > Run Without Debugging.
You should see a map of the stormwater network within Naperville, IL, USA. Pinch, drag, and double-tap the map view to explore the map. Leave the application running while you move into the next section to continue with the tutorial.
Specify an area of the web map to take offline
To specify an area of the web mapEnvelope that will be constructed using the EnvelopeBuilder. You will then display the area on the map using a Graphic.
-
Create an envelope builder and set its spatial reference to match the web map.
main.dart > onMapViewReady()// Set the map to the map view controller's ArcGIS map property._mapViewController.arcGISMap = map;// Create an envelope builder.final envelopeBuilder = EnvelopeBuilder(// Set the spatial reference.spatialReference: SpatialReference.wgs84,); -
Use the envelope builder to define an area to take offline.
Geometry builders in the Flutter Maps SDK are useful for constructing geometries in a step-by-step fashion. Although the values are provided in the tutorial, you could use the same builder with user input to allow for a more dynamic area to be defined.
main.dart > onMapViewReady()// Create an envelope builder.final envelopeBuilder = EnvelopeBuilder(// Set the spatial reference.spatialReference: SpatialReference.wgs84,);// Add the lower left (Xmin, Ymin) and upper right (Xmax, Ymax)// coordinates to define the area to take offline.envelopeBuilder.xMin = -88.1526;envelopeBuilder.xMax = -88.1490;envelopeBuilder.yMin = 41.7694;envelopeBuilder.yMax = 41.7714;// Retrieve the new geometry from the builder.final offlineArea = envelopeBuilder.toGeometry(); -
Display a graphic of the area to take offline.
Use
SimpleLineSymbolandSimpleFillSymbolto display a newGraphicof theofflineAreawith a red outline. Add the graphic to a newGraphicsOverlayand add the new overlay to the_mapViewController.graphicsOverlaysproperty to display it in the map view.main.dart > onMapViewReady()// Retrieve the new geometry from the builder.final offlineArea = envelopeBuilder.toGeometry();// Define a red line symbol to act as the outline// for the area of interest.final lineSymbol = SimpleLineSymbol(style: SimpleLineSymbolStyle.solid,color: Colors.red,width: 2,);// Define a fill symbol that is transparent, setting the// outline to the line symbol.final fillSymbol = SimpleFillSymbol(style: SimpleFillSymbolStyle.solid,color: Colors.transparent,outline: lineSymbol,);// Create a graphic for the area of interest using the// built geometry and symbol.final offlineAreaGraphic = Graphic(geometry: offlineArea,symbol: fillSymbol,);// Create a new graphics overlay.final areaOverlay = GraphicsOverlay();// Add the graphic to the graphics overlay.areaOverlay.graphics.add(offlineAreaGraphic);// Add the graphics overlay to the map view controller._mapViewController.graphicsOverlays.add(areaOverlay); -
Use Flutter’s hot restart to load your code changes and restart the app.
You should see a red outline on the stormwater network within Naperville, IL, USA. This indicates the area of the web map that you are going to take offline.
Download and display the offline map
You can generate and download an offline map
-
Create an
OfflineMapTaskusing the online map.main.dart > onMapViewReady()// Add the graphics overlay to the map view controller._mapViewController.graphicsOverlays.add(areaOverlay);// Create an offline map task for the map.final offlineMapTask = OfflineMapTask.withOnlineMap(map); -
In
onMapViewReady(), change the method signature to denote the method as asynchronous.main.dartvoid onMapViewReady() async { -
Get default parameters to generate and download the offline map
An offline map is a map area and its data content downloaded from an offline-enabled web map for use in offline applications built with ArcGIS Maps SDKs for Native Apps. . Modify them to download a read-only offline map.This tutorial does not involve editing and updating the contents of the offline map. When an offline map is editable, metadata is stored in ArcGIS to track and synchronize edits. Setting the
GenerateOfflineMapUpdateModetonoUpdatesavoids the overhead of maintaining this metadata in ArcGIS.main.dart > onMapViewReady()// Create an offline map task for the map.final offlineMapTask = OfflineMapTask.withOnlineMap(map);// Create parameters specifying the region to take offline// using the graphic's geometry. Set the update mode to// no updates to avoid the overhead of maintaining the// metadata required when synchronizing edits.final parameters =await offlineMapTask.createDefaultGenerateOfflineMapParameters(areaOfInterest: offlineArea,)..updateMode = GenerateOfflineMapUpdateMode.noUpdates; -
Set a download location for the offline map
An offline map is a map area and its data content downloaded from an offline-enabled web map for use in offline applications built with ArcGIS Maps SDKs for Native Apps. .main.dart > onMapViewReady()// Create parameters specifying the region to take offline// using the graphic's geometry. Set the update mode to// no updates to avoid the overhead of maintaining the// metadata required when synchronizing edits.final parameters =await offlineMapTask.createDefaultGenerateOfflineMapParameters(areaOfInterest: offlineArea,)..updateMode = GenerateOfflineMapUpdateMode.noUpdates;// Prepare an empty directory to store the offline map.final documentsUri = (await getApplicationDocumentsDirectory()).uri;final downloadDirectoryUri = documentsUri.resolve('offline_map');// If the download directory already exists we delete.// If this is undesirable, use a unique identifier for the downloadDirectoryUri// such as appending a timestamp.if (Directory.fromUri(downloadDirectoryUri).existsSync()) {Directory.fromUri(downloadDirectoryUri).deleteSync(recursive: true);}Directory.fromUri(downloadDirectoryUri).createSync(); -
Create a new
GenerateOfflineMapJobusing theparametersanddownloadDirectoryUri.main.dart > onMapViewReady()// Prepare an empty directory to store the offline map.final documentsUri = (await getApplicationDocumentsDirectory()).uri;final downloadDirectoryUri = documentsUri.resolve('offline_map');// If the download directory already exists we delete.// If this is undesirable, use a unique identifier for the downloadDirectoryUri// such as appending a timestamp.if (Directory.fromUri(downloadDirectoryUri).existsSync()) {Directory.fromUri(downloadDirectoryUri).deleteSync(recursive: true);}Directory.fromUri(downloadDirectoryUri).createSync();// Create a job to generate the offline map passing in// the parameters and download directory.final generateOfflineMapJob = offlineMapTask.generateOfflineMap(parameters: parameters,downloadDirectoryUri: downloadDirectoryUri,); -
Add code to listen for when the generate offline map job completes or fails and to track the percent complete as the job runs. Add a
try/catchblock to handle exceptions.main.dart > onMapViewReady()// Create a job to generate the offline map passing in// the parameters and download directory.final generateOfflineMapJob = offlineMapTask.generateOfflineMap(parameters: parameters,downloadDirectoryUri: downloadDirectoryUri,);// Listen for status updates as the generate offline map// job runs.generateOfflineMapJob.onStatusChanged.listen((jobStatus) {try {// See next step.} on ArcGISException catch (e) {debugPrint('Error generating offline map: ${e.message}');}}); -
If the job succeeds, set the
arcgisMapproperty on the map view controller with the offline map result. If it fails, print out a message. If the job is running, print the percent complete.Instead of printing messages to the debug console as demonstrated, you could respond to the change in the job status by displaying widgets in the user interface. For example, the Generate offline map sample shows the download progression using the LinearProgressIndicator widget.
main.dart > onMapViewReady() > try{}// Listen for status updates as the generate offline map// job runs.generateOfflineMapJob.onStatusChanged.listen((jobStatus) {try {// If the job succeeds, show the offline map.if (generateOfflineMapJob.status == JobStatus.succeeded) {// Retrieve the offline map.final result = generateOfflineMapJob.result;if (result == null) return;// Set the map view controller's ArcGIS map to the offline map._mapViewController.arcGISMap = result.offlineMap;debugPrint('Generate offline map: Complete');} else if (generateOfflineMapJob.status == JobStatus.failed) {// If the job fails, print out the error message.debugPrint('Unable to generate a map for that area: ${generateOfflineMapJob.error?.message}',);} else {// Otherwise, get the progress and report it.final percentComplete = generateOfflineMapJob.progress;debugPrint('Percent complete: $percentComplete%');}} on ArcGISException catch (e) {debugPrint('Error generating offline map: ${e.message}');}}); -
Start the generate offline map job.
main.dart > onMapViewReady()} on ArcGISException catch (e) {debugPrint('Error generating offline map: ${e.message}');}});// Start the generate offline map job.generateOfflineMapJob.start();} -
Hot restart your app.
You should see an offline map for the specified area of the stormwater network within Naperville, IL, USA. Remove your network connection and you will still be able to pinch, drag, and double-tab the map view to explore this offline map.
Alternatively, you can download the tutorial solution, as follows.
Option 2: Download the solution
-
Click the
Download solutionlink underSolutionand unzip the file to a location on your machine. -
Open the project in VS code.
Since the downloaded solution does not contain authentication code, you must first set up authentication to create credentials, then add authentication code and set the developer credentials to the solution.
Set up authentication
To access the secure ArcGIS location services
You can implement API key authentication or user authentication in this tutorial. Compare the differences below:
API key authentication
- Users are not required to sign in.
- Requires creating an API key credential
API key credentials are an item that contains the parameters used to create and manage long-lived access tokens for API key authentication. They are a type of developer credential. with the correct privileges. - API keys
An API key is a long-lived access token created using API key credentials. They are valid for up to one year and are typically embedded directly into client applications. are long-lived access tokens. - Service usage is billed to the API key owner/developer.
- Simplest authentication method to implement.
- Recommended approach for new ArcGIS developers.
Learn more in API key authentication.
User authentication
- Users are required to sign in with an ArcGIS account
An ArcGIS account is an identity with a user type and set of privileges that can access specific ArcGIS products, tools, APIs, services, and resources. The main account types that can be used for development are an ArcGIS Location Platform account, ArcGIS Online account, and ArcGIS Enterprise account. ArcGIS Location Platform and ArcGIS Online accounts are also associated with a subscription. . - User accounts must have privilege
Privileges are a set of permissions assigned to ArcGIS accounts, developer credentials, and applications that grant access to secure resources and functionality in ArcGIS. to access the ArcGIS servicesA service, also known as an ArcGIS service, is software that supports an ArcGIS REST API and provides geospatial functionality or data. A service can be hosted by Esri or in ArcGIS Enterprise. used in application. - Requires creating OAuth credentials
OAuth credentials are an item that contains parameters required to implement user authentication or app authentication, including a .client_id,client_secret, and redirect URIs. They are a type of developer credential. - Application uses a redirect URL and client ID.
- Service usage is billed to the organization of the user signed into the application.
Learn more in User authentication.
To complete this tutorial, click on the tab in the switcher below for your authentication type of choice, either API key authentication or User authentication.
Create a new API key access token
-
Complete the Create an API key tutorial and create an API key with the following privilege(s)
Privileges are a set of permissions assigned to ArcGIS accounts, developer credentials, and applications that grant access to secure resources and functionality in ArcGIS. :- Privileges
- Location services > Basemaps
- Privileges
-
Copy and paste the API key access token into a safe location. It will be used in a later step.
Create new OAuth credentials to access the secure resources used in this tutorial.
-
Complete the Create OAuth credentials for user authentication tutorial to obtain a Client ID and Redirect URL.
A
Client IDuniquely identifies your app on the authenticating server. If the server cannot find an app with the provided Client ID, it will not proceed with authentication.The
Redirect URL(also referred to as a callback url) is used to identify a response from the authenticating server when the system returns control back to your app after an OAuth login. Since it does not necessarily represent a valid endpoint that a user could navigate to, the redirect URL can use a custom scheme, such asmy-app://auth. It is important to make sure the redirect URL used in your app’s code matches a redirect URL configured on the authenticating server. -
Copy and paste the Client ID and Redirect URL into a safe location. They will be used in a later step.
All users that access this application need account privileges
Add authentication code and set developer credentials
To allow your app users to access ArcGIS location services
-
Open lib/main.dart.
-
In the
main()function, set theArcGISEnvironment.apiKeyvalue to your access tokenAn access token is an authorization string that provides access to secure ArcGIS content, data, and services. Its capabilities are determined by the privileges it supports. It is obtained by implementing API key authentication, User authentication, or App authentication. .main.dartvoid main () {ArcGISEnvironment.apiKey = 'YOUR_ACCESS_TOKEN';runApp(const MaterialApp(home: MainApp()));}
Best Practice: The access token is stored directly in the code as a convenience for this tutorial. Do not store credentials directly in source code in a production environment.
Add the Authenticator toolkit component to manage your OAuth credentials.
-
Add the ArcGIS Maps SDK for Flutter Toolkit package to your app by following these instructions
-
Add the following code to main.dart:
- Define a final class member inside
_MainAppStateclass,_oAuthUserConfiguration, and initialize it withOAuthUserConfigurationpassing in theportalUri,clientId, andredirectUrivalues. - Add code to revoke all tokens,
Authenticator.revokeOAuthTokens(), and clear all credentials,Authenticator.clearCredentials(), from the authenticator’s credential store during application sign-out. - Add the
Authenticatorwidget as a parent of theArcGISMapViewwidget in the existing widget tree found in thebuild(). Set the value of the named parameter,oAuthUserConfigurationsto a list containing the new member variable_oAuthUserConfiguration.
main.dartclass _MainAppState extends State<MainApp> {final _mapViewController = ArcGISMapView.createController();final _oAuthUserConfiguration = OAuthUserConfiguration(portalUri: Uri.parse('https://www.arcgis.com'),clientId: 'YOUR_CLIENT_ID',redirectUri: Uri.parse('YOUR_REDIRECT_URL'),);@overridevoid dispose() {Authenticator.revokeOAuthTokens().whenComplete(Authenticator.clearCredentials,);super.dispose();}@overrideWidget build(BuildContext context) {return Scaffold(body: Column(children: [Expanded(child: Authenticator(oAuthUserConfigurations: [_oAuthUserConfiguration],child: ArcGISMapView(controllerProvider: () => _mapViewController,onMapViewReady: onMapViewReady,),),),],),);}}Best Practice: The OAuth credentials are stored directly in the code as a convenience for this tutorial. Do not store credentials directly in source code in a production environment.
- Define a final class member inside
-
For Android only, a system browser is required for user authentication
User authentication is a type of authentication that allows users with an ArcGIS account to sign into an application and allow it to access ArcGIS content, services, and resources on their behalf. The typical authorization protocol used is OAuth2.0. . Add the following activity to the Android manifest in your project:android/app/src/main/AndroidManifest.xml<!-- ... --><activityandroid:name="com.linusu.flutter_web_auth_2.CallbackActivity"android:exported="true"><intent-filter android:label="flutter_web_auth_2"><action android:name="android.intent.action.VIEW" /><category android:name="android.intent.category.DEFAULT" /><category android:name="android.intent.category.BROWSABLE" /><data android:scheme="YOUR_CALLBACK_URL_SCHEME_HERE" /></intent-filter></activity><!-- ... -->Replace
YOUR_CALLBACK_URL_SCHEME_HEREwith the scheme of the redirect URL used when you set up authentication earlier. This is necessary so that the browser can communicate back to your app after the login workflow completes.For example, if you have a redirect URL, such as: my-app://auth, the value of the scheme would be: my-app. This value would replace
YOUR_CALLBACK_URL_SCHEME_HEREin the activity.
Run the application
Follow these steps to run the application.
-
In VS Code’s terminal, run:
flutter pub upgrade -
Run:
dart run arcgis_maps install -
Make sure you have an Android emulator, iOS simulator or physical device configured and running.
-
In VS Code, select Run > Run Without Debugging.
You should see an offline map for the specified area of the stormwater network within Naperville, IL, USA. Remove your network connection and you will still be able to pinch, drag, and double-tab the map view to explore this offline map.