Create mobile geodatabase

View on GitHubSample viewer app

Create and share a mobile geodatabase.

Create 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 Runtime, 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, click on "Create and share mobile geodatabase" to retrieve the .geodatabase file which can then be imported into ArcGIS Pro or opened in an ArcGIS Runtime application.

How it works

  1. Create the Geodatabase from the mobile geodatabase location on file.
  2. Create a new TableDescription and add the list of FieldDescriptions to the table description.
  3. Create a GeodatabaseFeatureTable in the geodatabase from the TableDescription using Geodatabase.createTableAsync().
  4. Create a feature on the selected map point using GeodatabaseFeatureTable.createFeature(featureAttributes, mapPoint).
  5. Add the feature to the table using GeodatabaseFeatureTable.addFeatureAsync(feature).
  6. Each feature added to the GeodatabaseFeatureTable is committed to the mobile geodatabase file.
  7. 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 ArcGIS Runtime: 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

MainActivity.kt
Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
/* Copyright 2022 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
 *
 *    http://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.
 *
 */

package com.esri.arcgisruntime.sample.createmobilegeodatabase

import android.app.Dialog
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.view.MotionEvent
import android.widget.Button
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.FileProvider
import com.esri.arcgisruntime.ArcGISRuntimeEnvironment
import com.esri.arcgisruntime.concurrent.ListenableFuture
import com.esri.arcgisruntime.data.*
import com.esri.arcgisruntime.geometry.GeometryType
import com.esri.arcgisruntime.geometry.Point
import com.esri.arcgisruntime.geometry.SpatialReferences
import com.esri.arcgisruntime.layers.FeatureLayer
import com.esri.arcgisruntime.loadable.LoadStatus
import com.esri.arcgisruntime.mapping.ArcGISMap
import com.esri.arcgisruntime.mapping.BasemapStyle
import com.esri.arcgisruntime.mapping.Viewpoint
import com.esri.arcgisruntime.mapping.view.DefaultMapViewOnTouchListener
import com.esri.arcgisruntime.mapping.view.MapView
import com.esri.arcgisruntime.sample.createmobilegeodatabase.databinding.ActivityMainBinding
import com.esri.arcgisruntime.sample.createmobilegeodatabase.databinding.TableLayoutBinding
import com.esri.arcgisruntime.sample.createmobilegeodatabase.databinding.TableRowBinding
import java.io.File
import java.util.*


class MainActivity : AppCompatActivity() {

    private val TAG = MainActivity::class.java.simpleName

    private val activityMainBinding by lazy {
        ActivityMainBinding.inflate(layoutInflater)
    }

    private val mapView: MapView by lazy {
        activityMainBinding.mapView
    }

    private val createButton: Button by lazy {
        activityMainBinding.createButton
    }

    private val viewTableButton: Button by lazy {
        activityMainBinding.viewTableButton
    }

    private val featureCount: TextView by lazy {
        activityMainBinding.featureCount
    }

    // feature table created using mobile geodatabase and added to the MapView
    private var featureTable: GeodatabaseFeatureTable? = null

    // mobile geodatabase used to create and store the feature attributes (LocationHistory.geodatabase)
    private var geodatabase: Geodatabase? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(activityMainBinding.root)

        // authentication with an API key or named user is
        // required to access basemaps and other location services
        ArcGISRuntimeEnvironment.setApiKey(BuildConfig.API_KEY)

        mapView.apply {
            //  create a map with a topographic basemap and set the map to the mapview
            map = ArcGISMap(BasemapStyle.ARCGIS_TOPOGRAPHIC)
            setViewpoint(Viewpoint(34.056295, -117.195800, 10000.0))
            // listen for single taps on the map
            onTouchListener =
                object : DefaultMapViewOnTouchListener(this@MainActivity, mapView) {
                    override fun onSingleTapConfirmed(motionEvent: MotionEvent?): Boolean {
                        motionEvent?.let { event ->
                            // get the map point from the screen point
                            val mapPoint = mapView.screenToLocation(
                                android.graphics.Point(
                                    event.x.toInt(),
                                    event.y.toInt()
                                )
                            )
                            // create a point from where the user clicked
                            addFeature(mapPoint)
                        }
                        return super.onSingleTapConfirmed(motionEvent)
                    }
                }
        }

        viewTableButton.setOnClickListener {
            // displays table dialog with the values in the feature table
            displayTable()
        }

        // opens a share-sheet with the "LocationHistory.geodatabase" file
        createButton.setOnClickListener {
            try {
                // close the mobile geodatabase before sharing
                geodatabase?.close()
                // get the URI of the geodatabase file using FileProvider
                val geodatabaseURI = FileProvider.getUriForFile(
                    this, getString(R.string.file_provider_package), File(
                        geodatabase?.path.toString()
                    )
                )
                // set up the sharing intent with the geodatabase URI
                val geodatabaseIntent = Intent(Intent.ACTION_SEND).apply {
                    type = "*/*"
                    putExtra(Intent.EXTRA_STREAM, geodatabaseURI)
                }
                // open the Android share sheet
                startActivity(geodatabaseIntent)
            } catch (e: Exception) {
                showError("Error sharing file: ${e.message}")
            }
        }

    }

    /**
     * Create and load a new geodatabase file with TableDescription fields
     */
    private fun createGeodatabase() {
        // define the path and name of the geodatabase file
        // note: the path defined must be non-empty, available,
        // allow read/write access, and end in ".geodatabase"
        val file = File(getExternalFilesDir(null)?.path + "/LocationHistory.geodatabase")
        if (file.exists()) {
            file.delete()
        }
        // close the existing geodatabase
        geodatabase?.close()
        // create a geodatabase file at the file path
        val geodatabaseFuture = Geodatabase.createAsync(file.path)
        geodatabaseFuture.addDoneListener {
            // get the instance of the mobile geodatabase
            geodatabase = geodatabaseFuture.get()
            // construct a table description which stores features as points on map
            val tableDescription =
                TableDescription(
                    "LocationHistory",
                    SpatialReferences.getWgs84(),
                    GeometryType.POINT
                )
            // set up the fields to the table,
            // Field.Type.OID is the primary key of the SQLite table
            // Field.Type.DATE is a date column used to store a Calendar date
            // FieldDescriptions can be a SHORT, INTEGER, GUID, FLOAT, DOUBLE, DATE, TEXT, OID, GLOBALID, BLOB, GEOMETRY, RASTER, or XML.
            tableDescription.fieldDescriptions.addAll(
                listOf(
                    FieldDescription("oid", Field.Type.OID),
                    FieldDescription("collection_timestamp", Field.Type.DATE)
                )
            )

            // set any properties not needed to false
            tableDescription.apply {
                setHasAttachments(false)
                setHasM(false)
                setHasZ(false)
            }

            // add a new table to the geodatabase by creating one from the tableDescription
            val tableFuture = geodatabase?.createTableAsync(tableDescription)
            if (tableFuture != null) {
                setupMapFromGeodatabase(tableFuture)
            } else {
                showError("Error adding FieldDescriptions to the mobile geodatabase")
            }
        }
    }

    /**
     * Set up the MapView to display the Feature layer
     * using the loaded [tableFuture] GeodatabaseFeatureTable
     */
    private fun setupMapFromGeodatabase(tableFuture: ListenableFuture<GeodatabaseFeatureTable>) {
        tableFuture.addDoneListener {
            // get the result of the loaded "LocationHistory" table
            featureTable = tableFuture.get()
            // create a feature layer for the map using the GeodatabaseFeatureTable
            val featureLayer = FeatureLayer(featureTable)
            mapView.map.operationalLayers.add(featureLayer)
            // display the current count of features in the FeatureTable
            featureCount.text = "Number of features added: ${featureTable?.totalFeatureCount}"
        }
    }

    /**
     * Create a feature with attributes on map click and it to the [featureTable]
     * Also, updates the TotalFeatureCount on the screen
     */
    private fun addFeature(mapPoint: Point) {
        // set up the feature attributes
        val featureAttributes = mutableMapOf<String, Any>()
        featureAttributes["collection_timestamp"] = Calendar.getInstance()

        // create a new feature at the mapPoint
        val feature = featureTable?.createFeature(featureAttributes, mapPoint)
        // add the feature to the feature table
        val addFeatureFuture = featureTable?.addFeatureAsync(feature)
        addFeatureFuture?.addDoneListener {
            try {
                // if feature wasn't added successfully "addFeatureFuture.get()" will throw an exception
                addFeatureFuture.get()
                // feature added successfully, update count
                featureCount.text = "Number of features added: ${featureTable?.totalFeatureCount}"
                // enable table button since at least 1 feature loaded on the GeodatabaseFeatureTable
                viewTableButton.isEnabled = true
            } catch (e: Exception) {
                showError(e.message.toString())
            }
        }
    }

    /**
     * Displays a dialog with the table of features
     * added to the GeodatabaseFeatureTable [featureTable]
     */
    private fun displayTable() {
        // query all the features loaded to the table
        val queryResultFuture = featureTable?.queryFeaturesAsync(QueryParameters())
        queryResultFuture?.addDoneListener {
            val queryResults = queryResultFuture.get()
            // inflate the table layout
            val tableLayoutBinding = TableLayoutBinding.inflate(layoutInflater)
            // set up a dialog to be displayed
            Dialog(this).apply {
                setContentView(tableLayoutBinding.root)
                setCancelable(true)
                // grab the instance of the TableLayout
                val table = tableLayoutBinding.tableLayout
                // iterate through each feature to add to the TableLayout
                queryResults.forEach { feature ->
                    // prepare the table row
                    val tableRowBinding = TableRowBinding.inflate(layoutInflater).apply {
                        oid.text = feature.attributes["oid"].toString()
                        collectionTimestamp.text = (feature.attributes["collection_timestamp"] as Calendar).time.toString()
                    }

                    // add the row to the TableLayout
                    table.addView(tableRowBinding.root)
                }
            }.show()
        }
    }

    /**
     * Called on app launch or when Android share sheet is closed
     */
    private fun resetMap() {
        mapView.map.addDoneLoadingListener {
            if (mapView.map.loadStatus == LoadStatus.LOADED) {
                // clear any feature layers displayed on the map
                mapView.map.operationalLayers.clear()
                // disable the button since no features are displayed
                viewTableButton.isEnabled = false
                // create a new geodatabase file to add features into the feature table
                createGeodatabase()
            } else {
                showError("Error loading MapView: ${mapView.map.loadError.message}")
            }
        }
    }

    private fun showError(message: String?) {
        Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
        Log.e(TAG, message.toString())
    }

    override fun onPause() {
        mapView.pause()
        super.onPause()
    }

    override fun onResume() {
        super.onResume()
        mapView.resume()
        resetMap()
    }

    override fun onDestroy() {
        mapView.dispose()
        super.onDestroy()
    }
}

Your browser is no longer supported. Please upgrade your browser for the best experience. See our browser deprecation post for more details.