Connect to a service and list versions of the geodatabase.

Use case
As part of a multi-user editing scenario, you can check with the server to see how many versions of the geodatabase are outstanding before syncing.
How to use the sample
When the sample loads, a list of geodatabase versions and their properties is displayed. Tap Refresh to run the geoprocessing task again and reload the latest version information from the service.
How it works
- Create a
GeoprocessingTaskthat points to aGPServerwith aListVersionstask. - Use the task to create the default
GeoprocessingParameters. - Create a
GeoprocessingJobfrom the parameters and run it. - Get the
GeoprocessingResultfrom the completed job. - Read the
Versionsoutput parameter asGeoprocessingFeatures. - Convert each returned feature’s attributes into displayable version metadata.
Relevant API
- GeoprocessingFeatures
- GeoprocessingJob
- GeoprocessingParameters
- GeoprocessingResult
- GeoprocessingTask
About the data
The sample uses a sample geoprocessing service hosted on ArcGIS Online.
Additional information
ArcGIS Server does not include a geoprocessing service for listing geodatabase versions. You must configure one using the steps defined in Geoprocessing service example: list, create, and delete geodatabase versions in the ArcMap documentation.
Tags
conflict resolution, data management, database, multi-user, sync, version
Sample code
// Copyright 2026 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 'dart:async';import 'package:arcgis_maps/arcgis_maps.dart';import 'package:arcgis_maps_sdk_flutter_samples/common/common.dart';import 'package:flutter/material.dart';import 'package:intl/intl.dart';
class ListGeodatabaseVersions extends StatefulWidget { const ListGeodatabaseVersions({super.key});
@override State<ListGeodatabaseVersions> createState() => _ListGeodatabaseVersionsState();}
class _ListGeodatabaseVersionsState extends State<ListGeodatabaseVersions> with SampleStateSupport { // A geoprocessing task to list geodatabase versions from a sample service. final _geoprocessingTask = GeoprocessingTask( uri: Uri.parse( 'https://sampleserver6.arcgisonline.com/arcgis/rest/services/' 'GDBVersions/GPServer/ListVersions', ), );
// The geoprocessing job created to list versions. Stored here so it can be // cancelled if the widget is disposed. GeoprocessingJob? _job;
// A flag to indicate whether the geoprocessing task is currently running. var _isLoading = false;
// A message to display if an error occurs while loading versions. var _errorMessage = '';
// The list of geodatabase versions returned by the geoprocessing task. var _versions = <_GeodatabaseVersionInfo>[];
@override void initState() { super.initState(); // Load the versions when the widget is first created. _loadVersions().ignore(); }
@override void dispose() { // Cancel the geoprocessing job if it's still running when the widget is disposed. _job?.cancel().ignore(); super.dispose(); }
// Build the UI for the sample, showing a list of geodatabase versions and // a button to refresh the list. @override Widget build(BuildContext context) { return Scaffold( body: SafeArea( minimum: const EdgeInsets.all(12), child: Stack( children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, spacing: 8, children: [ // A row with the title of the sample and a button to refresh // the list of versions. Row( children: [ Expanded( child: Text( 'Geodatabase versions', style: Theme.of(context).textTheme.titleLarge, ), ), ElevatedButton.icon( onPressed: _isLoading ? null : _loadVersions, icon: const Icon(Icons.refresh), label: const Text('Refresh'), ), ], ), // A description of the sample and a count of how many versions have been loaded. Text( 'Connect to a geoprocessing service and list the returned geodatabase versions.', style: Theme.of(context).textTheme.bodyMedium, ), // Show the number of versions loaded, or a message if no versions have been loaded yet. Text( _versions.isEmpty ? 'No versions loaded.' : '${_versions.length} versions found.', style: Theme.of(context).textTheme.titleSmall, ), // Show an error message if one exists. if (_errorMessage.isNotEmpty) ...[ Text( _errorMessage, style: Theme.of(context).textTheme.customErrorStyle, ), ], // Show the list of versions in an expanded widget to fill available space. Expanded(child: _buildVersionList()), ], ), LoadingIndicator(visible: _isLoading, text: 'Listing versions...'), ], ), ), ); }
// Build a list view to display the geodatabase versions. If no versions have // been loaded yet, show a message prompting the user to refresh the list. Widget _buildVersionList() { if (_versions.isEmpty) { return ListView( children: const [ Card( child: Padding( padding: EdgeInsets.all(16), child: Text( 'Tap Refresh to run the geoprocessing task and view the geodatabase versions returned by the service.', ), ), ), ], ); } // Build a list view of cards, one for each geodatabase version. // Each card shows information about the version, // such as its name, access level, creation date, and other relevant details. return RefreshIndicator( onRefresh: _loadVersions, child: ListView.separated( itemCount: _versions.length, separatorBuilder: (_, _) => const SizedBox(height: 8), itemBuilder: (context, index) { final version = _versions[index]; return _VersionCard(version: version); }, ), ); }
// Load the geodatabase versions by running the geoprocessing task. Future<void> _loadVersions() async { // If a load is already in progress, do nothing. if (_isLoading) return;
GeoprocessingJob? activeJob; setState(() { _isLoading = true; _errorMessage = ''; });
try { // Create default parameters for the geoprocessing task. final parameters = await _geoprocessingTask.createDefaultParameters();
// Create a job to execute the task with the parameters. final job = _geoprocessingTask.createJob(parameters); activeJob = job;
// Store the job so it can be cancelled if the widget is disposed before it completes. _job = job;
// Run the job and wait for the result. final result = await job.run();
// Get the "Versions" output from the result, which should be a feature set. final output = result.outputs['Versions']; if (output is! GeoprocessingFeatures) { throw StateError( 'The geoprocessing result did not include a Versions feature set.', ); } // Load the features from the output if they haven't been loaded already. if (output.features == null) { await output.fetchOutputFeatures(); }
final featureSet = output.features; if (featureSet == null) { throw StateError('The Versions output did not contain any features.'); }
// Convert the features to _GeodatabaseVersionInfo objects. final versions = featureSet .features() .map( (feature) => _GeodatabaseVersionInfo.fromAttributes(feature.attributes), ) .whereType<_GeodatabaseVersionInfo>() .toList() ..sort((a, b) { final objectIdCompare = (a.objectId ?? 1 << 30).compareTo( b.objectId ?? 1 << 30, ); return objectIdCompare != 0 ? objectIdCompare : a.name.compareTo(b.name); });
setState(() => _versions = versions); } on ArcGISException catch (e) { setState(() => _errorMessage = e.message); } on Exception catch (e) { setState(() => _errorMessage = e.toString()); } finally { if (identical(_job, activeJob)) { _job = null; setState(() => _isLoading = false); } } }}
// A card widget to display information about a geodatabase version.class _VersionCard extends StatelessWidget { const _VersionCard({required this.version});
// The geodatabase version to display information about. final _GeodatabaseVersionInfo version;
@override Widget build(BuildContext context) { // Build a card to display the version information. return Card( child: Padding( padding: const EdgeInsets.all(16), child: SelectionArea( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(version.name, style: Theme.of(context).textTheme.titleSmall), const SizedBox(height: 8), _InfoRow( label: 'Object ID', value: version.objectId?.toString() ?? 'Unavailable', ), _InfoRow(label: 'Access', value: version.access), _InfoRow(label: 'Created', value: version.createdText), _InfoRow(label: 'Last modified', value: version.lastModifiedText), _InfoRow(label: 'Is owner', value: version.isOwnerText), _InfoRow( label: 'Parent version', value: version.parentVersionName, ), if (version.description.isNotEmpty) _InfoRow(label: 'Description', value: version.description), ], ), ), ), ); }}
// A widget to display a label and value in a consistent style, used to show// information about a geodatabase version in the _VersionCard.class _InfoRow extends StatelessWidget { const _InfoRow({required this.label, required this.value});
final String label; final String value;
@override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.only(bottom: 8), child: RichText( text: TextSpan( style: Theme.of(context).textTheme.bodyMedium, children: [ TextSpan( text: '$label: ', style: Theme.of(context).textTheme.titleSmall, ), TextSpan(text: value), ], ), ), ); }}
// A class to hold information about a geodatabase version,// extracted from the attributes of a geoprocessing result feature.class _GeodatabaseVersionInfo { _GeodatabaseVersionInfo({ required this.name, required this.access, required this.created, required this.description, required this.isOwner, required this.lastModified, required this.objectId, required this.parentVersionName, });
final String name; final String access; final DateTime? created; final String description; final String isOwner; final DateTime? lastModified; final int? objectId; final String parentVersionName;
static final _dateFormatter = DateFormat.yMMMd().add_jm();
String get createdText => _formatDate(created);
String get isOwnerText => isOwner.isEmpty ? 'Unavailable' : isOwner;
String get lastModifiedText => _formatDate(lastModified);
static _GeodatabaseVersionInfo? fromAttributes( Map<String, Object?> attributes, ) { final name = _stringValue(attributes, 'name'); if (name.isEmpty) { return null; }
return _GeodatabaseVersionInfo( name: name, access: _stringValue(attributes, 'access', fallback: 'Unavailable'), created: _dateValue(attributes, 'created'), description: _stringValue(attributes, 'description'), isOwner: _stringValue(attributes, 'isowner', fallback: 'Unavailable'), lastModified: _dateValue(attributes, 'lastmodified'), objectId: _intValue(attributes, 'objectid'), parentVersionName: _stringValue( attributes, 'parentversionname', fallback: 'Unavailable', ), ); }
static DateTime? _dateValue(Map<String, Object?> attributes, String key) { final value = _rawValue(attributes, key); if (value is DateTime) { return value.toLocal(); } if (value is num) { return DateTime.fromMillisecondsSinceEpoch(value.toInt()).toLocal(); } if (value is String && value.isNotEmpty) { return DateTime.tryParse(value)?.toLocal(); } return null; }
static String _formatDate(DateTime? value) { return value == null ? 'Unavailable' : _dateFormatter.format(value); }
static int? _intValue(Map<String, Object?> attributes, String key) { final value = _rawValue(attributes, key); if (value is int) { return value; } if (value is num) { return value.toInt(); } if (value is String) { return int.tryParse(value); } return null; }
static Object? _rawValue(Map<String, Object?> attributes, String key) { final normalizedKey = key.toLowerCase(); for (final entry in attributes.entries) { if (entry.key.toLowerCase() == normalizedKey) { return entry.value; } } return null; }
static String _stringValue( Map<String, Object?> attributes, String key, { String fallback = '', }) { final value = _rawValue(attributes, key); if (value == null) { return fallback; } final text = value.toString().trim(); return text.isEmpty ? fallback : text; }}