Create and share a mobile geodatabase.

Use case
A mobile geodatabase is a collection of various types of GIS datasets contained in a single file (.geodatabase) on disk that can store, query, and manage spatial and nonspatial data. Mobile geodatabases are stored in a SQLite database and can contain up to 2 TB of portable data. Users can create, edit and share mobile geodatabases across ArcGIS Pro, ArcGIS Maps SDKs for Native Apps, or any SQL software. These mobile geodatabases support both viewing and editing and enable new offline editing workflows that don’t require a feature service.
For example, a user would like to track the location of their device at various intervals to generate a heat map of the most visited locations. The user can add each location as a feature to a table and generate a mobile geodatabase. The user can then instantly share the mobile geodatabase to ArcGIS Pro to generate a heat map using the recorded locations stored as a geodatabase feature table.
How to use the sample
Tap on the map to add a feature symbolizing the user’s location. Tap “View Table” to view the contents of the geodatabase feature table. Once you have added the location points to the map, tap on the share button to retrieve the .geodatabase file which can then be imported into ArcGIS Pro or opened in an ArcGIS Maps SDK application.
How it works
- Create the
Geodatabasefrom the mobile geodatabase location on file. - Create a new
TableDescriptionand add the list ofFieldDescriptions to the table description. - Create a
GeodatabaseFeatureTablein the geodatabase from theTableDescriptionusingGeodatabase.makeTable(description:). - Create a feature on the selected map point using
GeodatabaseFeatureTable.makeFeature(attributes:geometry:). - Add the feature to the table using
GeodatabaseFeatureTable.add(_:). - Each feature added to the
GeodatabaseFeatureTableis committed to the mobile geodatabase file. - Close the mobile geodatabase to safely share the “.geodatabase” file using
Geodatabase.close()
Relevant API
- ArcGISFeature
- FeatureLayer
- FeatureTable
- FieldDescription
- Geodatabase
- GeodatabaseFeatureTable
- TableDescription
Additional information
Learn more about mobile geodatabases and how to utilize them on the ArcGIS Pro documentation page. The following mobile geodatabase behaviors are supported in the ArcGIS Maps SDKs for Native Apps: annotation, attachments, attribute rules, contingent values, dimensions, domains, feature-linked annotation, subtypes, utility network and relationship classes.
Learn more about the types of fields supported with mobile geodatabases on the ArcGIS Pro documentation page.
Tags
arcgis pro, database, feature, feature table, geodatabase, mobile geodatabase, sqlite
Sample code
// Copyright 2023 Esri//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at//// https://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License.
import ArcGISimport SwiftUI
struct CreateMobileGeodatabaseView: View { /// The view model for the sample. @StateObject private var model = Model()
/// The point on the map where the user tapped. @State private var tapLocation: Point?
/// A Boolean value indicating whether the feature table sheet is showing. @State private var tableSheetIsShowing = false
/// A Boolean value indicating whether the file explorer interface is showing @State private var fileExporterIsShowing = false
/// The error shown in the error alert. @State private var error: (any Error)?
var body: some View { MapView(map: model.map) .onSingleTapGesture { _, mapPoint in tapLocation = mapPoint } .task(id: tapLocation) { guard let tapLocation else { return } do { // Add a feature at the tap location. try await model.addFeature(at: tapLocation) } catch { self.error = error } } .task(id: model.features.isEmpty) { do { // Create a new feature table when the features are reset. if model.features.isEmpty { try await model.createFeatureTable() } } catch { self.error = error } } .overlay(alignment: .top) { Text("Number of features added: \(model.features.count)") .frame(maxWidth: .infinity, alignment: .center) .padding(8) .background(.thinMaterial, ignoresSafeAreaEdges: .horizontal) } .toolbar { ToolbarItemGroup(placement: .bottomBar) { Button("Feature Table") { tableSheetIsShowing = true } .popover(isPresented: $tableSheetIsShowing) { tableList .presentationDetents([.medium, .large]) .frame(minWidth: 320, minHeight: 380) } .disabled(model.features.isEmpty)
Spacer()
Button { fileExporterIsShowing = true } label: { Label("Export File", systemImage: "square.and.arrow.up") } .disabled(model.features.isEmpty) .fileExporter( isPresented: $fileExporterIsShowing, document: model.geodatabaseFile, contentType: .geodatabase ) { result in switch result { case .success: do { try model.resetFeatures() } catch { self.error = error } case .failure(let error): self.error = error } } } } .errorAlert(presentingError: $error) }
/// The list of features in the feature table. private var tableList: some View { NavigationStack { List { Section("OID and Collection Timestamp") { ForEach(model.features, id: \.self) { feature in HStack { Text(String(feature.oid)) .padding(.trailing, 8) Text(feature.timestamp, format: .collectionTimestamp) } } } } .navigationTitle("Feature Table") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .confirmationAction) { Button("Done") { tableSheetIsShowing = false } } } } }}
private extension FormatStyle where Self == Date.VerbatimFormatStyle { /// The format style for a collection timestamp of a feature in a feature table. static var collectionTimestamp: Self { .init( format: """ \(weekday: .abbreviated) \ \(month: .abbreviated) \ \(day: .defaultDigits) \ \(hour: .twoDigits(clock: .twentyFourHour, hourCycle: .oneBased)):\ \(minute: .twoDigits):\ \(second: .twoDigits) \ \(timeZone: .specificName(.short)) \ \(year: .defaultDigits) """, locale: .current, timeZone: .current, calendar: .current ) }}
#Preview { NavigationStack { CreateMobileGeodatabaseView() }}// Copyright 2023 Esri//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at//// https://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License.
import ArcGISimport SwiftUIimport UniformTypeIdentifiers
extension CreateMobileGeodatabaseView { // MARK: Model
/// The model used to store the geo model and other expensive objects used in the view. @MainActor class Model: ObservableObject { /// A map with a topographic basemap centered on Harpers Ferry, WV, USA. let map: Map = { let map = Map(basemapStyle: .arcGISTopographic) map.initialViewpoint = Viewpoint(latitude: 39.3238, longitude: -77.7332, scale: 1e4) return map }()
/// A geodatabase file that can be exported with the system's file exporter. let geodatabaseFile = GeodatabaseFile()
/// The feature table in the geodatabase. private var featureTable: GeodatabaseFeatureTable?
/// The table description used to create a new feature table in the geodatabase. private let tableDescription: TableDescription = { // Create a description for the feature table. let description = TableDescription( name: "LocationHistory", spatialReference: .wgs84, geometryType: Point.self )
// Create and add the field descriptions for the table. description.addFieldDescriptions([ // `FieldType.OID` is the primary key of the SQLite table. FieldDescription(name: "oid", fieldType: .oid), // `FieldType.DATE` is the date column used to store a calendar date. FieldDescription(name: "collection_timestamp", fieldType: .date) ])
return description }()
/// The list of features in the feature table. @Published private(set) var features: [FeatureItem] = []
/// Creates a new feature table from a geodatabase. func createFeatureTable() async throws { // Create a new geodatabase. try await geodatabaseFile.createGeodatabase()
// Create a feature table in the geodatabase using a table description. guard let table = try await geodatabaseFile.geodatabase?.makeTable( description: tableDescription ) else { return } featureTable = table
// Create a feature layer using the table and add it to the map. let featureLayer = FeatureLayer(featureTable: table) map.addOperationalLayer(featureLayer) }
/// Adds a feature to the feature table at a given map point. /// - Parameter mapPoint: The map point used to make the feature. func addFeature(at mapPoint: Point) async throws { guard let featureTable else { return }
// Create an attribute with the current date. let attributes = ["collection_timestamp": Date()]
// Create a feature with the attributes and point. let feature = featureTable.makeFeature(attributes: attributes, geometry: mapPoint)
// Add the feature to the feature table. try await featureTable.add(feature)
// Add the feature to the list of features. if let oid = feature.attributes["oid"] as? Int64, let timeStamp = feature.attributes["collection_timestamp"] as? Date { features.append(FeatureItem(oid: oid, timestamp: timeStamp)) } }
/// Removes all the existing features from the map. func resetFeatures() throws { // Delete the geodatabase. try geodatabaseFile.deleteGeodatabase()
// Remove the current features and layers. features.removeAll() map.removeAllOperationalLayers() } }
/// A struct representing a feature in the feature table. struct FeatureItem: Hashable { /// The primary key of the feature in the SQLite table. let oid: Int64 /// The collection timestamp of the the feature. let timestamp: Date }
// MARK: GeodatabaseFile
/// A geodatabase file that can be used with the native file exporter. @MainActor final class GeodatabaseFile { /// The mobile geodatabase used to create the geodatabase file. private(set) var geodatabase: Geodatabase?
/// A URL to the temporary geodatabase file. private let geodatabaseURL: URL
/// A URL to the temporary directory containing the geodatabase file. private let directoryURL: URL
init() { // Create the temporary directory using file manager. directoryURL = FileManager.default.temporaryDirectory.appendingPathComponent( ProcessInfo().globallyUniqueString ) try? FileManager.default.createDirectory( at: directoryURL, withIntermediateDirectories: false )
// Create the geodatabase path with the directory URL. geodatabaseURL = directoryURL .appendingPathComponent("LocationHistory", isDirectory: false) .appendingPathExtension("geodatabase") }
deinit { try? FileManager.default.removeItem(at: directoryURL) }
/// Creates an empty mobile geodatabase file. func createGeodatabase() async throws { // Create an empty mobile geodatabase at the given URL. geodatabase = try await Geodatabase.createEmpty(fileURL: geodatabaseURL) }
/// Deletes the geodatabase file. func deleteGeodatabase() throws { // Close the geodatabase to cease all adjustments. geodatabase?.close()
// Remove the geodatabase file if it exists. if FileManager.default.fileExists(atPath: geodatabaseURL.path) { try FileManager.default.removeItem(at: geodatabaseURL) } } }}
extension CreateMobileGeodatabaseView.GeodatabaseFile: FileDocument { /// The file and data types that the document reads from. nonisolated static var readableContentTypes: [UTType] { [.geodatabase] }
/// Creates a document and initializes it with the contents of a file. nonisolated convenience init(configuration: ReadConfiguration) throws { fatalError("Loading geodatabase files is not supported by this sample.") }
/// Serializes a document snapshot to a file wrapper. nonisolated func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper { return try FileWrapper(url: geodatabaseURL) }}
extension UTType { /// A type that represents a geodatabase file. static var geodatabase: Self { UTType(filenameExtension: "geodatabase")! }}