Query a feature table for statistics, grouping and sorting by different fields.
Use case
You can use statistical queries, grouping and sorting to process large amounts of data saved in feature tables. This is helpful for identifying trends and relationships within the data, which can be used to support further interpretations and decisions. For example, a health agency can use information on medical conditions occurring throughout a country to identify at-risk areas or demographics, and decide on further action and preventive measures.
How to use the sample
The sample will start with some default options selected. You can immediately tap the "Get Statistics" button to see the results for these options. There are several ways to customize your queries:
- You can add statistic definitions to the top-left table using the combo boxes and "Add" button. Select a table row and tap "Remove" to remove a definition.
- To change the Group-by fields, check the box by the field you want to group by in the bottom-left list view.
- To change the Order-by fields, select a Group-by field (it must be checked) and tap the ">>" button to add it to the Order-by table. To remove a field from the Order-by table, select it and tap the "<<" button. To change the sort order of the Order-by field, the cells of the "Sort Order" column are combo-boxes that may be either ASCENDING or DESCENDING.
How it works
- Create a
ServiceFeatureTable
using the URL of a feature service and load the table. - Get the feature tables field names list with
featureTable.Fields
. - Create
StatisticDefinition
s specifying the field to compute statistics on and theStatisticType
to compute. - Create
StatisticsQueryParameters
passing in the list of statistic definitions. - To have the results grouped by fields, add the field names to the query parameters'
GroupByFieldNames
collection. - To have the results ordered by fields, create
OrderBy
s, specifying the field name andSortOrder
. Pass theseOrderBy
s to the parameters'OrderByFields
collection. - To execute the query, call
featureTable.QueryStatisticsAsync(queryParameters)
. - Get the
StatisticQueryResult
. From this, you can get an iterator ofStatisticRecord
s to loop through and display.
Relevant API
- Field
- OrderBy
- QueryParameters
- ServiceFeatureTable
- StatisticDefinition
- StatisticRecord
- StatisticsQueryParameters
- StatisticsQueryResult
- StatisticType
About the data
This sample uses a Diabetes, Obesity, and Inactivity by US County feature layer hosted on ArcGIS Online.
Tags
correlation, data, fields, filter, group, sort, statistics, table
Sample Code
// Copyright 2017 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.
using Android.App;
using Android.Content;
using Android.OS;
using Android.Util;
using Android.Views;
using Android.Widget;
using Esri.ArcGISRuntime.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using ContextThemeWrapper = AndroidX.AppCompat.View.ContextThemeWrapper;
namespace ArcGISRuntime.Samples.StatsQueryGroupAndSort
{
[Activity(ConfigurationChanges = Android.Content.PM.ConfigChanges.Orientation | Android.Content.PM.ConfigChanges.ScreenSize)]
[ArcGISRuntime.Samples.Shared.Attributes.AndroidLayout("GroupedResultsList_DataItem.axml", "GroupedResultsList_GroupItem.axml")]
[ArcGISRuntime.Samples.Shared.Attributes.Sample(
name: "Statistical query group and sort",
category: "Data",
description: "Query a feature table for statistics, grouping and sorting by different fields.",
instructions: "The sample will start with some default options selected. You can immediately tap the \"Get Statistics\" button to see the results for these options. There are several ways to customize your queries:",
tags: new[] { "correlation", "data", "fields", "filter", "group", "sort", "statistics", "table" })]
public class StatsQueryGroupAndSort : Activity
{
// URI for the US states map service
private Uri _usStatesServiceUri = new Uri("https://services.arcgis.com/jIL9msH9OI208GCb/arcgis/rest/services/Counties_Obesity_Inactivity_Diabetes_2013/FeatureServer/0");
// US states feature table
private FeatureTable _usStatesTable;
// List of field names from the table
private List<string> _fieldNames = new List<string>();
// Selected fields for grouping results
private Dictionary<string, bool> _groupByFields = new Dictionary<string, bool>();
// Collection to hold fields to order results by
private List<OrderFieldOption> _orderByFields = new List<OrderFieldOption>();
// List of statistics definitions to use in the query
private List<StatisticDefinition> _statisticDefinitions = new List<StatisticDefinition>();
// Linear layout UI control for arranging query controls
private LinearLayout _controlsLayout;
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
Title = "Statistical query group and sort";
// Create the UI
CreateLayout();
// Initialize the service feature table
Initialize();
}
private void CreateLayout()
{
// Create a new vertical layout for the app
_controlsLayout = new LinearLayout(this) { Orientation = Orientation.Vertical };
// Button for launching the UI to view or define statistics definitions for the query
Button showStatDefinitionsButton = new Button(this)
{
Text = "Statistic Definitions"
};
showStatDefinitionsButton.Click += ShowStatDefinitions;
// Button to choose fields with which to group results
Button showGroupFieldsButton = new Button(this)
{
Text = "Group Fields"
};
showGroupFieldsButton.Click += ShowGroupFields;
// Button to choose fields with which to sort results (must be one of the 'group by' fields)
Button showOrderByFieldsButton = new Button(this)
{
Text = "Order By Fields"
};
showOrderByFieldsButton.Click += ShowOrderByFields;
// Create a Button to execute the statistical query
Button getStatsButton = new Button(this)
{
Text = "Execute Query"
};
getStatsButton.Click += ExecuteStatisticsQuery;
// Define additional space (margin) between the execute button and the others
Space space = new Space(this);
space.SetMinimumHeight(200);
// Add the query controls to the layout
_controlsLayout.AddView(showStatDefinitionsButton);
_controlsLayout.AddView(showGroupFieldsButton);
_controlsLayout.AddView(showOrderByFieldsButton);
_controlsLayout.AddView(space);
_controlsLayout.AddView(getStatsButton);
// Show the layout in the app
SetContentView(_controlsLayout);
}
private async void Initialize()
{
// Create the US states feature table
_usStatesTable = new ServiceFeatureTable(_usStatesServiceUri);
try
{
// Load the table
await _usStatesTable.LoadAsync();
// Get a list of field names from the table
_fieldNames = _usStatesTable.Fields.Select(field => field.Name).ToList();
// Create a dictionary of fields the user can select for grouping
// The value for each is set to false initially, as nothing is selected by default
_groupByFields = _fieldNames.ToDictionary(name => name, name => false);
// Create a list of field options for ordering results (initially empty)
_orderByFields = new List<OrderFieldOption>();
}
catch (Exception e)
{
new AlertDialog.Builder(this).SetMessage(e.ToString()).SetTitle("Error").Show();
}
}
private void ShowStatDefinitions(object sender, EventArgs e)
{
// Create a dialog for choosing statistics (field names and statistic types)
StatDefinitionsDialog statsDefDialog = new StatDefinitionsDialog(_fieldNames, _statisticDefinitions);
// Begin a transaction to show a UI fragment (stats definitions dialog)
FragmentTransaction trans = FragmentManager.BeginTransaction();
statsDefDialog.Show(trans, "stats_defs");
}
private void ShowGroupFields(object sender, EventArgs e)
{
// Create a dialog for choosing fields to group results with
GroupFieldsDialog groupFieldsDialog = new GroupFieldsDialog(_groupByFields);
// Handle the dialog closing event to read the selected fields
groupFieldsDialog.GroupFieldDialogClosed += (s, args) =>
{
// Update the dictionary of group fields from the dialog
_groupByFields = args;
// Get the current list of group fields and create/update the sort field choices
// (only fields selected for grouping can be used to order results)
List<KeyValuePair<string, bool>> currentGroupFields = _groupByFields.Where(field => field.Value == true).ToList();
// Loop through the group fields
foreach (KeyValuePair<string, bool> groupField in currentGroupFields)
{
// Check if this field is missing from the current sort field options
OrderFieldOption existingOption = _orderByFields.Find((opt) => opt.OrderInfo.FieldName == groupField.Key);
if (existingOption == null)
{
// If the field is missing, create a new OrderFieldOption and add it to the list
existingOption = new OrderFieldOption(false, new OrderBy(groupField.Key, SortOrder.Ascending));
_orderByFields.Add(existingOption);
}
}
// Also make sure to remove any 'order by' fields that were removed from the 'group by' list
for (int i = _orderByFields.Count - 1; i >= 0; i--)
{
// If the order by field is not also one of the group fields, remove it from the list
OrderFieldOption opt = _orderByFields.ElementAt(i);
KeyValuePair<string, bool> existingGroupField = currentGroupFields.FirstOrDefault(field => field.Key == opt.OrderInfo.FieldName);
if (existingGroupField.Key == null)
{
_orderByFields.RemoveAt(i);
}
}
};
// Begin a transaction to show a UI fragment (group fields dialog)
FragmentTransaction trans = FragmentManager.BeginTransaction();
groupFieldsDialog.Show(trans, "group_flds");
}
private void ShowOrderByFields(object sender, EventArgs e)
{
// If there are no available order fields, don't show the (empty) list
if (_orderByFields.Count == 0)
{
// Warn the user to choose group fields first
ShowMessage("Results can only be ordered by group fields.", "No Group Fields");
return;
}
// Create a new dialog for choosing fields to order with
OrderByFieldsDialog orderFieldsDialog = new OrderByFieldsDialog(_orderByFields);
// Handle the dialog closing event to capture the current order field choices
orderFieldsDialog.OrderFieldDialogClosed += (s, args) =>
{
_orderByFields = args;
};
// Begin a transaction to show a UI fragment (order by fields dialog)
FragmentTransaction trans = FragmentManager.BeginTransaction();
orderFieldsDialog.Show(trans, "order_flds");
}
private async void ExecuteStatisticsQuery(object sender, EventArgs e)
{
// Verify that there is at least one statistic definition
if (!_statisticDefinitions.Any())
{
// Warn the user to define a statistic to query
ShowMessage("Please define at least one statistic for the query.", "Statistical Query");
return;
}
// Create the statistics query parameters, pass in the list of statistic definitions
StatisticsQueryParameters statQueryParams = new StatisticsQueryParameters(_statisticDefinitions);
// Specify the selected group fields (if any)
if (_groupByFields != null)
{
// Find fields in the dictionary with a 'true' value and add them to the group by field names
foreach (KeyValuePair<string, bool> groupField in _groupByFields.Where(field => field.Value))
{
statQueryParams.GroupByFieldNames.Add(groupField.Key);
}
}
// Specify the fields to order by (if any)
if (_orderByFields != null)
{
foreach (OrderFieldOption orderBy in _orderByFields)
{
statQueryParams.OrderByFields.Add(orderBy.OrderInfo);
}
}
// Ignore counties with missing data
statQueryParams.WhereClause = "\"State\" IS NOT NULL";
try
{
// Execute the statistical query with these parameters and await the results
StatisticsQueryResult statQueryResult = await _usStatesTable.QueryStatisticsAsync(statQueryParams);
// Get results formatted as a dictionary (group names and their associated dictionary of results)
Dictionary<string, IReadOnlyDictionary<string, object>> resultsLookup = statQueryResult.ToDictionary(r => string.Join(", ", r.Group.Values), r => r.Statistics);
// Create an instance of a custom list adapter that has logic to show results as expandable groups
ExpandableResultsListAdapter expandableListAdapter = new ExpandableResultsListAdapter(this, resultsLookup);
// Create an expandable list view and assign the expandable adapter
ExpandableListView expandableResultsListView = new ExpandableListView(this);
expandableResultsListView.SetAdapter(expandableListAdapter);
// Show the expandable list view in a dialog
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
dialogBuilder.SetView(expandableResultsListView);
dialogBuilder.Show();
}
catch (Exception ex)
{
new AlertDialog.Builder(this).SetMessage(ex.ToString()).SetTitle("Error").Show();
}
}
private void ShowMessage(string message, string title)
{
AlertDialog.Builder alertBuilder = new AlertDialog.Builder(this);
alertBuilder.SetTitle(title);
alertBuilder.SetMessage(message);
alertBuilder.Show();
}
}
// Simple class to describe an "order by" option
public class OrderFieldOption
{
// Whether or not to use this field to order results
public bool OrderWith { get; set; }
// The order by info: field name and sort order
public OrderBy OrderInfo { get; set; }
public OrderFieldOption(bool orderWith, OrderBy orderInfo)
{
OrderWith = orderWith;
OrderInfo = orderInfo;
}
}
// A class that defines a custom dialog for defining statistics to use in the query
public class StatDefinitionsDialog : DialogFragment
{
// List of field names from the table
private List<string> _fieldNames;
// List of statistic definitions for the query
private List<StatisticDefinition> _statisticDefinitions;
// Spinner (drop down) to display fields from the table
private Spinner _fieldSpinner;
// Spinner to display available statistic types (average, sum, maximum, etc.)
private Spinner _statSpinner;
// ListView to show chosen statistic definitions (field name and statistic type)
private ListView _statDefListView;
public StatDefinitionsDialog(List<string> fieldNames, List<StatisticDefinition> statisticDefs)
{
// Store field names for the table being queried
_fieldNames = fieldNames;
// Store a list of the current statistic definitions
_statisticDefinitions = statisticDefs;
}
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
// Dialog UI to display
LinearLayout dialogView = null;
// Get the context for creating the dialog controls
Android.Content.Context ctx = Activity.ApplicationContext;
ContextThemeWrapper ctxWrapper = new ContextThemeWrapper(ctx, Android.Resource.Style.ThemeMaterialLight);
// Set a dialog title
Dialog.SetTitle("Statistics Definitions");
// Call OnCreateView on the base
base.OnCreateView(inflater, container, savedInstanceState);
// The container for the dialog is a vertical linear layout
dialogView = new LinearLayout(ctxWrapper)
{
Orientation = Orientation.Vertical
};
// Spinner for choosing a field to get statistics for
_fieldSpinner = new Spinner(ctxWrapper);
// Create an array adapter to display the fields
ArrayAdapter fieldsAdapter = new ArrayAdapter(ctxWrapper, Android.Resource.Layout.SimpleSpinnerItem);
foreach (string field in _fieldNames)
{
fieldsAdapter.Add(field);
}
// Set the drop down style for the array adapter, then assign it to the field spinner control
fieldsAdapter.SetDropDownViewResource(Android.Resource.Layout.SimpleSpinnerDropDownItem);
_fieldSpinner.Adapter = fieldsAdapter;
// Create a horizontal layout to display the field spinner (with a label)
LinearLayout fieldView = new LinearLayout(ctxWrapper)
{
Orientation = Orientation.Horizontal
};
// Create a label for the spinner
TextView fieldLabel = new TextView(ctxWrapper)
{
Text = "Field:",
LabelFor = _fieldSpinner.Id
};
// Add field controls to the horizontal layout
fieldView.AddView(fieldLabel);
fieldView.AddView(_fieldSpinner);
fieldView.SetPadding(140, 0, 0, 0);
dialogView.AddView(fieldView);
// Spinner for selecting the statistic type
_statSpinner = new Spinner(ctx);
// Create an array adapter to display the statistic types
ArrayAdapter statTypeAdapter = new ArrayAdapter(ctxWrapper, Android.Resource.Layout.SimpleSpinnerItem);
// Read the statistic types from the StatisticType enum
Array statTypes = Enum.GetValues(typeof(StatisticType));
foreach (object stat in statTypes)
{
// Add each statistic type to the adapter
statTypeAdapter.Add(stat.ToString());
}
// Set the drop down style for the array adapter, then assign it to the statistic type spinner control
statTypeAdapter.SetDropDownViewResource(Android.Resource.Layout.SimpleSpinnerDropDownItem);
_statSpinner.Adapter = statTypeAdapter;
// Create a horizontal layout to display the statistic type spinner (with a label)
LinearLayout statTypeView = new LinearLayout(ctxWrapper)
{
Orientation = Orientation.Horizontal
};
// Create the label for the statistic type list
TextView typeLabel = new TextView(ctxWrapper)
{
Text = "Type:",
LabelFor = _statSpinner.Id
};
// Add statistic type controls to the horizontal layout
statTypeView.AddView(typeLabel);
statTypeView.AddView(_statSpinner);
statTypeView.SetPadding(140, 0, 0, 0);
// Add the statistic type layout to the dialog
dialogView.AddView(statTypeView);
// Create a button to add a new statistic definition (selected field and statistic type)
Button addStatDefButton = new Button(ctxWrapper)
{
Text = "Add"
};
addStatDefButton.Click += AddStatisticDefinition;
// Create a button to remove the selected statistic definition
Button removeStatDefButton = new Button(ctxWrapper)
{
Text = "Remove"
};
removeStatDefButton.Click += RemoveStatisticDefinition;
// Create a horizontal layout to contain the add and remove buttons
LinearLayout buttonView = new LinearLayout(ctxWrapper)
{
Orientation = Orientation.Horizontal
};
buttonView.AddView(addStatDefButton);
buttonView.AddView(removeStatDefButton);
// Add the button layout to the dialog
dialogView.AddView(buttonView);
// Create a list view and an instance of a custom list adapter to show the statistic definitions
StatDefinitionListAdapter listAdapter = new StatDefinitionListAdapter(Activity, _statisticDefinitions);
_statDefListView = new ListView(ctxWrapper)
{
Adapter = listAdapter,
// Only allow one choice in the statistic definitions list ('remove' button will work on the selected row)
ChoiceMode = ChoiceMode.Single
};
// Add the statistic definitions list to the dialog
dialogView.AddView(_statDefListView);
// Return the new view for display
return dialogView;
}
// Handler for the RemoveStatisticDefinitionButton click event
private void RemoveStatisticDefinition(object sender, EventArgs e)
{
// Check for a selected row in the list view
int selectedPosition = _statDefListView.CheckedItemPosition;
if (selectedPosition >= 0)
{
// Call a function in the custom list adapter that will remove the statistic definition at this position (and update the data in the list view)
((StatDefinitionListAdapter)_statDefListView.Adapter).RemoveStatisticDefinitionAt(selectedPosition);
}
}
// Handler for the AddStatisticDefinitionButton click event
private void AddStatisticDefinition(object sender, EventArgs e)
{
// Get the selected field name in the dialog
string fieldName = _fieldSpinner.SelectedItem.ToString();
// Get the selected statistic type name in the dialog and get the corresponding enum value
string statTypeName = _statSpinner.SelectedItem.ToString();
StatisticType statType = (StatisticType)Enum.Parse(typeof(StatisticType), statTypeName);
// Build a field alias for the statistic results that use the field name and statistic type
string alias = fieldName + "_" + statTypeName;
// Create a new StatisticDefinition with the field name, statistic type, and output field alias
StatisticDefinition statisticDefinition = new StatisticDefinition(fieldName, statType, alias);
// Call a function in the custom list adapter that will add the new statistic definition (and update the data in the list view)
((StatDefinitionListAdapter)_statDefListView.Adapter).AddStatisticDefinition(statisticDefinition);
}
}
// A class that defines a custom list adapter for displaying statistic definitions
public class StatDefinitionListAdapter : BaseAdapter<StatisticDefinition>
{
// Store the current activity (passed into the constructor)
private Activity _ctx;
// List of statistic definitions to display
private List<StatisticDefinition> _statisticDefinitions;
// Constructor that takes the current activity and list of statistic definitions
public StatDefinitionListAdapter(Activity context, List<StatisticDefinition> statDefs) : base()
{
_ctx = context;
_statisticDefinitions = statDefs;
}
// Return the statistic definition at the specified position in the list
public override StatisticDefinition this[int position]
{
get
{
return _statisticDefinitions[position];
}
}
// Add a new statistic definition to the internal list
public void AddStatisticDefinition(StatisticDefinition statDef)
{
// See if this definition already exists in the list (output alias name is unique)
StatisticDefinition existingDef = _statisticDefinitions.Find((d) => d.OutputAlias == statDef.OutputAlias);
// If the definition is not found in the list, add it
if (existingDef == null)
{
_statisticDefinitions.Add(statDef);
// Raise a notification that the data have changed
NotifyDataSetChanged();
}
}
// Remove the statistic definition at the specified position from the internal list
public void RemoveStatisticDefinitionAt(int position)
{
// Verify that the position is within the correct range
if (position >= 0 && position < _statisticDefinitions.Count)
{
// Remove the definition from the list
_statisticDefinitions.RemoveAt(position);
// Raise a notification that the data have changed
NotifyDataSetChanged();
}
}
// Return the count of statistic definitions in the list
public override int Count
{
get
{
return _statisticDefinitions.Count;
}
}
// Return an item ID (just use the position in the list)
public override long GetItemId(int position)
{
return position;
}
// Return a view to display each item (statistic definition)
public override View GetView(int position, View convertView, ViewGroup parent)
{
// Use a list item style with two text areas and the ability to be activated (selected)
View cellView = _ctx.LayoutInflater.Inflate(Android.Resource.Layout.SimpleListItemActivated2, null);
// Find the text view for the main text and use it to display the field name
cellView.FindViewById<TextView>(Android.Resource.Id.Text1).Text = _statisticDefinitions[position].OnFieldName;
// Find the text view for the details text and use it to display the statistic type
cellView.FindViewById<TextView>(Android.Resource.Id.Text2).Text = _statisticDefinitions[position].StatisticType.ToString();
// Return the view
return cellView;
}
}
// A class that defines a custom dialog for choosing fields to group results on
public class GroupFieldsDialog : DialogFragment
{
// Dictionary of field names from the table and whether or not to use them to group results
private Dictionary<string, bool> _potentialGroupByFields;
// ListView to display the available group fields
private ListView _groupFieldsListView;
// Constructor that takes a dictionary of available group fields
public GroupFieldsDialog(Dictionary<string, bool> groupByFields)
{
_potentialGroupByFields = groupByFields;
}
// Event that fires when the dialog closes (passes back the updated dictionary of group fields)
public event EventHandler<Dictionary<string, bool>> GroupFieldDialogClosed;
// Handle the dialog dismiss event to raise a custom event that passes back the updated fields dictionary
public override void OnDismiss(IDialogInterface dialog)
{
base.OnDismiss(dialog);
// If the event has listeners
if (GroupFieldDialogClosed != null)
{
// Get an array of all checked row positions in the list
SparseBooleanArray checkedItemsArray = _groupFieldsListView.CheckedItemPositions;
// Loop through all fields in the dictionary
for (int i = 0; i < _potentialGroupByFields.Count; i++)
{
// Set the corresponding value for the field to false (will not be used for grouping results)
string key = _potentialGroupByFields.Keys.ElementAt(i);
_potentialGroupByFields[key] = false;
// If the corresponding row in the list view is checked, set the field's value in the dictionary to true
if (checkedItemsArray.KeyAt(i) == i && checkedItemsArray.ValueAt(i))
{
_potentialGroupByFields[key] = true;
}
}
// Raise the GroupFieldDialogClosed event to pass back the updated dictionary
GroupFieldDialogClosed(this, _potentialGroupByFields);
}
}
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
// Dialog UI to display
LinearLayout dialogView = null;
// Get the context for creating the dialog controls
Android.Content.Context ctx = this.Activity.ApplicationContext;
ContextThemeWrapper ctxWrapper = new ContextThemeWrapper(ctx, Android.Resource.Style.ThemeMaterialLight);
// Set a dialog title
this.Dialog.SetTitle("Group Results By");
// Call OnCreateView on the base
base.OnCreateView(inflater, container, savedInstanceState);
// The container for the dialog is a vertical linear layout
dialogView = new LinearLayout(ctxWrapper)
{
Orientation = Orientation.Vertical
};
// Create an instance of a custom list adapter to show the available group fields
GroupFieldListAdapter listAdapter = new GroupFieldListAdapter(this.Activity, _potentialGroupByFields);
// Create a new list view that uses the adapter and allows for multiple row selection
_groupFieldsListView = new ListView(ctxWrapper)
{
Adapter = listAdapter,
ChoiceMode = ChoiceMode.Multiple
};
// Loop through all the available fields
for (int i = 0; i < _potentialGroupByFields.Count; i++)
{
// See if this field have been selected for grouping results or not
bool chosenForGroup = _potentialGroupByFields.ElementAt(i).Value;
// Set the checked state in the list view to show the chosen fields
_groupFieldsListView.SetItemChecked(i, chosenForGroup);
}
// Add the list view to the dialog UI
dialogView.AddView(_groupFieldsListView);
// Return the new view for display
return dialogView;
}
}
// A class that defines a custom list adapter to show fields for grouping results
public class GroupFieldListAdapter : BaseAdapter<KeyValuePair<string, bool>>
{
// Store the current activity
private Activity _ctx;
// Dictionary of field names and a value to indicate whether they are used for grouping
private Dictionary<string, bool> _groupFields;
// Constructor that takes the current activity and a dictionary of fields
public GroupFieldListAdapter(Activity context, Dictionary<string, bool> groupFields) : base()
{
_ctx = context;
_groupFields = groupFields;
}
// Get the group field option at the specified position
public override KeyValuePair<string, bool> this[int position]
{
get
{
return _groupFields.ElementAt(position);
}
}
// Return the count of fields in the dictionary
public override int Count
{
get
{
return _groupFields.Count;
}
}
// Get an ID for the item at the specified position
public override long GetItemId(int position)
{
return position;
}
// Create a view to display an item in the dictionary (key-value pair)
public override View GetView(int position, View convertView, ViewGroup parent)
{
// Re-use an existing view, if one is supplied (otherwise create a new one)
View cellView = convertView;
if (cellView == null)
{
// Create a list item that shows one text view and a check
cellView = _ctx.LayoutInflater.Inflate(Android.Resource.Layout.SimpleListItemChecked, null);
}
// Set the text with the field name (checked value is set in the dialog UI)
cellView.FindViewById<TextView>(Android.Resource.Id.Text1).Text = _groupFields.ElementAt(position).Key;
return cellView;
}
}
// Class that defines a dialog for choosing fields for ordering (sorting) results
public class OrderByFieldsDialog : DialogFragment
{
// List of fields and whether or not to use them to order (sort) results
private List<OrderFieldOption> _potentialOrderByFields;
// List view to display available fields for ordering (only those fields chosen for grouping)
private ListView _orderFieldsListView;
// Constructor that takes a list of order fields
public OrderByFieldsDialog(List<OrderFieldOption> orderByFields)
{
_potentialOrderByFields = orderByFields;
}
// Event that returns the updated list of order options when the dialog closes
public event EventHandler<List<OrderFieldOption>> OrderFieldDialogClosed;
// Handle the dismiss event on the dialog to raise a custom event that passes the updated order fields back
public override void OnDismiss(IDialogInterface dialog)
{
base.OnDismiss(dialog);
// Verify the event has listeners
if (OrderFieldDialogClosed != null)
{
// Get an array of checked list item positions (indices)
SparseBooleanArray checkedItemsArray = _orderFieldsListView.CheckedItemPositions;
// Loop through all the available order fields
for (int i = 0; i < _potentialOrderByFields.Count; i++)
{
// Initially set each order option to false
OrderFieldOption orderOption = _potentialOrderByFields[i];
orderOption.OrderWith = false;
// If the item was checked in the list view, set the order option to true
if (checkedItemsArray.KeyAt(i) == i && checkedItemsArray.ValueAt(i))
{
orderOption.OrderWith = true;
}
}
// Raise the event and pass back the updated list of order field options
OrderFieldDialogClosed(this, _potentialOrderByFields);
}
}
// Create the dialog UI
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
// Dialog UI to display
LinearLayout dialogView = null;
// Get the context for creating the dialog controls
Android.Content.Context ctx = this.Activity.ApplicationContext;
ContextThemeWrapper ctxWrapper = new ContextThemeWrapper(ctx, Android.Resource.Style.ThemeMaterialLight);
// Set a dialog title
this.Dialog.SetTitle("Order Results");
// Call OnCreateView on the base
base.OnCreateView(inflater, container, savedInstanceState);
// The container for the dialog is a vertical linear layout
dialogView = new LinearLayout(ctxWrapper)
{
Orientation = Orientation.Vertical
};
// Create an instance of a custom list adapter for showing the order fields
OrderFieldListAdapter listAdapter = new OrderFieldListAdapter(this.Activity, _potentialOrderByFields);
// Create a new list view that uses the adapter
_orderFieldsListView = new ListView(ctxWrapper)
{
Adapter = listAdapter,
// Allow the user to select multiple fields in the list view
ChoiceMode = ChoiceMode.Multiple
};
// Loop through all order fields in the list
for (int i = 0; i < _potentialOrderByFields.Count; i++)
{
// If this field has been selected to order with, check it in the list view
bool chosenForOrder = _potentialOrderByFields[i].OrderWith;
_orderFieldsListView.SetItemChecked(i, chosenForOrder);
}
// Add the list view to the dialog
dialogView.AddView(_orderFieldsListView);
// Return the new view for display
return dialogView;
}
}
// Class to define a custom list adapter to show order field options
public class OrderFieldListAdapter : BaseAdapter<OrderFieldOption>
{
// Store the current activity
private Activity _ctx;
// Store a list of the available order field options
private List<OrderFieldOption> _orderFields;
// Constructor that takes the current activity and list of order fields
public OrderFieldListAdapter(Activity context, List<OrderFieldOption> orderFields) : base()
{
_ctx = context;
_orderFields = orderFields;
}
// Return the order field option at the specified position
public override OrderFieldOption this[int position]
{
get
{
return _orderFields[position];
}
}
// Return the count of order field options in the list
public override int Count
{
get
{
return _orderFields.Count;
}
}
// Return the ID for the item at the specified position
public override long GetItemId(int position)
{
return position;
}
// Create a view to display each order field option
public override View GetView(int position, View convertView, ViewGroup parent)
{
// Create a list item view that shows a single text view and a check box
View cellView = _ctx.LayoutInflater.Inflate(Android.Resource.Layout.SimpleListItemChecked, null);
// Set the list item text with the field name (checked value will be set in the dialog UI)
cellView.FindViewById<TextView>(Android.Resource.Id.Text1).Text = _orderFields[position].OrderInfo.FieldName;
return cellView;
}
}
// Class that defines a custom list adapter for displaying an expandable list of grouped items
public class ExpandableResultsListAdapter : BaseExpandableListAdapter
{
// Store the current context
private Context _ctx;
// Store a dictionary of results: group name, results dictionary
private Dictionary<string, IReadOnlyDictionary<string, object>> _resultsDictionary;
// Store the group names
private string[] _groupNames;
// Constructor that takes the current context and a dictionary of group names and results
public ExpandableResultsListAdapter(Context context, Dictionary<string, IReadOnlyDictionary<string, object>> results)
{
// Store the context and results
_ctx = context;
_resultsDictionary = results;
// Get the group names from the results dictionary
_groupNames = new string[results.Count];
results.Keys.CopyTo(_groupNames, 0);
}
// Return the count of groups in the results
public override int GroupCount
{
get
{
return _groupNames.Length;
}
}
// No IDs for the items
public override bool HasStableIds
{
get
{
return false;
}
}
// Return the result at the specified index
public override Java.Lang.Object GetChild(int groupPosition, int childPosition)
{
// Get the result dictionary for the specified group
IReadOnlyDictionary<string, object> result = new Dictionary<string, object>();
_resultsDictionary.TryGetValue(_groupNames[groupPosition], out result);
// Return a string concatenated from the field name and value at the specified position
return result.ElementAt(childPosition).Key + " : " + result.ElementAt(childPosition).Value;
}
// Return the ID for the specified item
public override long GetChildId(int groupPosition, int childPosition)
{
return childPosition;
}
// Return the count of items in the specified group
public override int GetChildrenCount(int groupPosition)
{
return _resultsDictionary.ElementAt(groupPosition).Value.Count;
}
// Return a view to display a child item (key-value string within a group)
public override View GetChildView(int groupPosition, int childPosition, bool isLastChild, View convertView, ViewGroup parent)
{
// Reuse the current view, if available
if (convertView == null)
{
// Inflate a view from a resource that defines a result list item
LayoutInflater inflator = (LayoutInflater)_ctx.GetSystemService(Context.LayoutInflaterService);
convertView = inflator.Inflate(Resource.Layout.GroupedResultsList_DataItem, null);
}
// Get the text view from the data item layout
TextView textViewItem = convertView.FindViewById<TextView>(Resource.Id.item);
// Get content for this item and add it to the text view
string content = (string)GetChild(groupPosition, childPosition);
textViewItem.Text = content;
return convertView;
}
// Return the group name
public override Java.Lang.Object GetGroup(int groupPosition)
{
// Find the group name in the array
string groupName = _groupNames[groupPosition];
// If the group name is empty (maybe results weren't grouped), return "Results" for the group name
if (String.IsNullOrEmpty(groupName))
{
groupName = "Results";
}
return groupName;
}
// Return the ID for the specified group
public override long GetGroupId(int groupPosition)
{
return groupPosition;
}
// Create a view to display the group heading
public override View GetGroupView(int groupPosition, bool isExpanded, View convertView, ViewGroup parent)
{
// Reuse the view, if available
if (convertView == null)
{
// Inflate a view from a resource that defines a result group item
LayoutInflater inflator = (LayoutInflater)_ctx.GetSystemService(Context.LayoutInflaterService);
convertView = inflator.Inflate(Resource.Layout.GroupedResultsList_GroupItem, null);
}
// Get the group name for this position
string textGroup = (string)GetGroup(groupPosition);
// Display the group in the text view
TextView textViewGroup = convertView.FindViewById<TextView>(Resource.Id.group);
textViewGroup.Text = textGroup;
return convertView;
}
// Return if the item at the specified position is selectable
public override bool IsChildSelectable(int groupPosition, int childPosition)
{
return false;
}
}
}