Statistical query group and sort

View inAndroidFormsUWPWPFWinUIiOSView on GitHub

Query a feature table for statistics, grouping and sorting by different fields.

Image of statistical query group and sort

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

  1. Create a ServiceFeatureTable using the URL of a feature service and load the table.
  2. Get the feature tables field names list with featureTable.Fields.
  3. Create StatisticDefinitions specifying the field to compute statistics on and the StatisticType to compute.
  4. Create StatisticsQueryParameters passing in the list of statistic definitions.
  5. To have the results grouped by fields, add the field names to the query parameters' GroupByFieldNames collection.
  6. To have the results ordered by fields, create OrderBys, specifying the field name and SortOrder. Pass these OrderBys to the parameters' OrderByFields collection.
  7. To execute the query, call featureTable.QueryStatisticsAsync(queryParameters).
  8. Get the StatisticQueryResult. From this, you can get an iterator of StatisticRecords 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

StatsQueryGroupAndSort.cs
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
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
// 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;
        }
    }
}

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