Skip to content

Tasks and jobs

Tasks are bound to online or offline data or services and provide methods to perform asynchronous operations using those resources.

With tasks you can:

  • Download, collect, and update geographic information using GeodatabaseSyncTask
  • Download and display tiled basemaps using ExportTileCacheTask
  • Locate addresses on the map and interpolate addresses from map locations using LocatorTask
  • Calculate point-to-point or multi-stop routes and get driving directions using RouteTask
  • Perform complex GIS analysis by executing geoprocessing models using GeoprocessingTask

Tasks either return their results directly from asynchronous methods on the task, or make use of jobs to provide status updates and results.

Tasks

Some operations return results directly from asynchronous methods on the task. For more complex or longer running operations, tasks make use of jobs instead.

To use tasks that return results directly:

  1. Create the task by initializing it to use the required data or service.
  2. Define the task inputs.
    • Some operations require only simple value inputs (for example a simple geocode operation may only require an address string as input).
    • Others require input parameters (for example, to limit a geocode operation to a specific country).
  3. Call the async operation method, passing in the inputs you defined.
  4. Use the results from the operation as required, for example to display geocode results on a map.

You can create a LocatorTask using the Esri geocode service, and pass in an address to geocode. When the operation is complete, display the resulting location in a GraphicsOverlay.

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
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
    // Create a new locator task from a geocode service endpoint.
    val geocodeServerUri = "https://geocode-api.arcgis.com/arcgis/rest/services/World/GeocodeServer"
    val locatorTask = LocatorTask(geocodeServerUri)

    // Search for the address
    val geocodeResults: List<GeocodeResult> = locatorTask.geocode(
        searchText = "380 New York Street, Redlands, CA",
        parameters = geocodeParameters
    ).getOrElse { error ->
        return showMessage("The locatorTask.geocode() call failed: ${error.message}")
    }
    val firstResult = geocodeResults.firstOrNull()
        ?: return showMessage("The geocodeResults list is empty.")
    val graphic = Graphic(
        geometry = firstResult.displayLocation,
        attributes = firstResult.attributes,
        symbol = symbol
    )
    graphicsOverlay.graphics.apply {
        clear()
        add(graphic)
    }

Define input parameters

Tasks offer numerous options that allow you to tailor the operation to your requirements. For example, when geocoding you can restrict your search to a specific area, country, category of place, and/or number of results. When an author publishes a service or packages a resource, they can choose default values for these options that suit the specific data or the most common use case for the service.

To use these default parameter values, tasks provide helper methods that create parameter objects initialized with service-specific values. You can then make any changes to the parameter values before passing them to an operation. Creating these default parameter objects is useful for operations with many options, such as tracing a utility network.

You could get the default parameters for a RouteTask and ensure that results using these parameters will return both a route and directions. Also the output spatial reference could match that of the map view.

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
    // Get the default route parameters.
    val routeParams = routeTask.createDefaultParameters().getOrElse { error ->
        return showMessage(error.message.toString())
    }

    routeParams.apply {
        // Explicitly set values for some parameters.
        returnDirections = true
        returnRoutes = true
        outputSpatialReference = map.spatialReference
    }

    // Solve the route with these parameters.
    val routeResult = routeTask.solveRoute(routeParameters = routeParams).getOrElse { error ->
        return showMessage(error.message.toString())
    }

    // ... work with results ...

Some parameters objects have constructors that you can use if you know the values of all the input parameters you want to use. This can be more efficient when parameter settings are simple.

For example, the code below creates geocoding parameters that restrict the country within which to geocode, and to limit the maximum returned results.

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
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
    val geocodeParams = GeocodeParameters().apply {
        countryCode = "France"
        maxResults = 5
    }

Work online or offline

Many tasks can work either online by using services, or offline by using local data and resources. For example, you can geocode an address by using the default Esri geocoding service, your own geocoding service, a locator file (.loc), or a mobile map package (.mmpk).

Here's the earlier example that creates a LocatorTask from a URL to a geocoding service.

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
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
    // Create a new locator task from a geocode service endpoint.
    val geocodeServerUri = "https://geocode-api.arcgis.com/arcgis/rest/services/World/GeocodeServer"
    val locatorTask = LocatorTask(geocodeServerUri)

The following code declares a LocatorTask from an offline locator stored on the device.

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
    // Create an offline locator from a local .loc file - coverage will depend on the packaged locator dataset.
    val locatorTask = LocatorTask("localPathToLocatorDataset.loc")

Another option is to use a locator stored with a mobile map package.

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
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
    // Get a locator task from a mobile map package, assuming the package has a locator task.
    mobileMapPackage.load().onFailure { error ->
        return@launch showError("Mobile map package failed to load.")
    }

    val locatorTask = mobileMapPackage.locatorTask
        ?: return@launch showError("Failed to get locator task from mobile map package.")

Tasks and jobs

Some tasks expose operations that have multiple stages (like preparing and downloading a geodatabase), and can generate multiple progress messages (such as percentage complete). These types of tasks are always bound to ArcGIS Server (or Local Server for platforms that support it). An example is GeodatabaseSyncTask.createGenerateGeodatabaseJob().

Instead of returning results directly, these tasks make use of jobs to monitor status, return progress updates, and return their results. Each Job represents a specific operation of a task. Jobs are useful for longer-running operations, because they can also be paused, resumed, and canceled. Your app can support a user action or host OS terminating a running job object, and then recreate and resume the job later.

To use operations like these:

  1. Create the task by initializing it to use the required data or service.
  2. Define the input parameters for the task.
  3. On the task, call the method that returns a job, passing in the input parameters you defined.
  4. Start the job.
  5. Optionally, perform flow-collection of the job's Job.status, Job.messages, or Job.progress, and in response, update a UI, report progress to the user, and so on.
  6. Call the suspend function Job.result(). Check for errors in the job, and if successful, use the results.
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
        val geodatabaseSyncTask = GeodatabaseSyncTask(url = serviceUrl)
        // Create parameters for the geodatabase sync task.
        val syncGeodatabaseParameters = SyncGeodatabaseParameters().apply {
            // The synchronization direction to be used when synchronizing the local geodatabase with the feature service.
            geodatabaseSyncDirection = SyncDirection.Bidirectional
            // When a synchronization fails, whether the attempted changes should be automatically rolled back.
            shouldRollbackOnFailure = true
        }

        val geodatabase = Geodatabase(path = "localPathToGeodatabase.geodatabase")

        geodatabase.load().onFailure {
            return showMessage("Error loading local geodatabase.")
        }

        val syncGeodatabaseJob = geodatabaseSyncTask.createSyncGeodatabaseJob(
            parameters = syncGeodatabaseParameters,
            geodatabase = geodatabase
        )

        // Start the job and wait for Job result
        syncGeodatabaseJob.start()
        coroutineScope {
        // Create a flow-collection for the job's status.
        launch(Dispatchers.Main) {
            syncGeodatabaseJob.status.collect { jobStatus ->
                when (jobStatus) {
                    JobStatus.Succeeded -> showMessage("Job succeeded")
                    JobStatus.Failed -> showMessage("Job failed")
                    JobStatus.NotStarted -> showMessage("Job has not started")
                    JobStatus.Started -> showMessage("Job started")
                    JobStatus.Paused -> showMessage("Job paused.")
                    JobStatus.Canceling -> showMessage("Job in process of canceling.")
                }
            }
        }

        launch(Dispatchers.IO) {
            val result: List<SyncLayerResult> = syncGeodatabaseJob.result().getOrElse {
                Log.e("TAG","Database did not sync correctly.")
                return@launch
            }
            // Use the result list to examine the results of syncing.
        }

Accessing Job.status retrieves the current JobStatus in the job's workflow. Jobs periodically fire a changed event as they are running, usually with decreasingly frequency as a job progresses. More than one JobMessage may appear in a change event. The job complete listener is called as soon as the job finishes. Whether successful or not, jobs cannot be restarted.

Report job progress

A job represents an asynchronously running operation that might take some time to finish. As described previously, you can monitor changes to job status for notification when a job has completed, failed, or been canceled, but what about the time in-between? Users may become frustrated waiting for a long job to complete without getting feedback on its progress. Fortunately, jobs provide a mechanism for reporting the current progress (percentage complete) for the running operation they represent.

The Job.status property is of type StateFlow<JobStatus> and the Job.messages property is of type SharedFlow<JobMessage>. To respond to a status change or a new message added, you will need to call the collect() function and pass it a lambda that handles the change appropriately. You can get the current progress of the job at any point from the job's Job.progress property, an integer representing the percentage of the operation that has been completed. This allows your app to provide more specific information about the status of a running job using UI elements like progress bars, for example.

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
308
309
310
311
312
313
314
315
316
317
318
319
            coroutineScope.launch {
                generateOfflineMapJob.progress.collect {
                    val progressPercentage = generateOfflineMapJob.progress.value
                    showMessage(progressPercentage)
                }
            }

Pause, resume, or cancel a job

Jobs are designed to handle a user exiting an app while the job is running or having the app terminated by the host operating system. Jobs also provide a mechanism for explicitly pausing or canceling the operation.

Cancel a job

Sometimes, the results of a job are no longer required. For example, a user could change their mind about the area of a tile cache they want to download and want to cancel the job and start over.

Calling Job.cancel() changes JobStatus to canceling, cancels the Job, and waits for any asynchronous, server-side operations to be canceled. After all cancelation tasks complete (including any server-side tasks), JobStatus changes to failed and Job.cancel() returns true. If one or more jobs cannot be canceled, Job.cancel() returns false.

For example, GenerateOfflineMapJob is a server-side job that launches several more server-side jobs, depending on the layers in your map. Other examples of server-side jobs include ExportTileCacheJob, ExportVectorTilesJob, GenerateGeodatabaseJob, and GeoprocessingJob.

You should always cancel unneeded jobs (for example when exiting your app) to avoid placing unnecessary load on the server.

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
    // Assume the job has been started elsewhere in your code with offlineMapJob.start()
    // The user changes their mind, and clicks your Cancel the job button.
    Button(
        onClick = {
            coroutineScope.launch {
                offlineMapJob.cancel()
                if (offlineMapJob.status.value == JobStatus.Canceling) {
                    showMessage("User canceled the job")
                }
           }
        }
    ) {
        Text("Cancel the job.")
    }

Pause and resume a job

Jobs can be long-running operations, so there is no guarantee that they will be completed while the app is running. You can pause a job explicitly using Job.pause(). For example, when an app is backgrounded and does not have permissions for background operation. Pausing may also be useful if a user wishes to temporarily stop network access for any reason.

Job changed messages will not be received for a paused job. Pausing a job does not stop any server-side processes from executing. While a job is paused, outstanding requests can complete. Therefore, when resuming a job it may have a different state than when it was paused.

You can serialize a job to JSON to persist it if your app is backgrounded or the process is otherwise terminated. When you deserialize it again the JobStatus will be in the paused state regardless of its state when serialized and should be restarted to resume listening for completion. The job changed listener is a good place to update the job JSON for storage by your app.

You could serialize a job to JSON.

Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    val jsonString = offlineMapJob.toJson()

You could deserialize a job from JSON and then restart the job.

Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    val offlineMapJob = GenerateOfflineMapJob.fromJsonOrNull(jsonString) ?: return showMessage("Job not created from json string.")
    offlineMapJob.start()

Loss of network connection

Additionally, jobs using services are designed to handle situations where network connectivity is temporarily lost without needing to be immediately paused. A started job will ignore errors such as network errors for a period of up to 10 minutes. If errors continue for longer, the job will fail and the message will indicate the loss of network connection.

To handle inconsistent connectivity, you can serialize and pause a job when your app loses connectivity for a few minutes to avoid job failure (as failed jobs cannot be restarted). The job can then be deserialized and resumed when connectivity returns.

From Android 6.0, the power-saving features Doze and App Standby can also affect the network connectivity of an application. Again, you may wish to pause a running job if your app enters these states to avoid job failure due to the lack of persistent network connection. Your app can check the Android PowerManager.isDeviceIdleMode and PowerManager.isPowerSaveMode methods, or use broadcast intents to react when entering or exiting these modes.

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