Creating A Generic Report in Xamarin.Forms

I recently had a client who wanted to create simple reports that consisted of a variable number of columns, followed by a “more” button that takes you to a page with the full details. 

The problem was that they didn’t want to hard-code anything.  They wanted the ability to add reports without sending out a new version of the software, and they wanted to be able to pick which columns were visible.  Yikes!

The solution to this came in four files.  The first captured the information about the report itself…

The Model

 public class ReportInformation
    {
       public string ReportID { getset; }
       public string ReportName { getset; }
       public string DisplayName { getset; }
     }

The other model class we need is the GenericItem.  This class has 20 sets of three strings.  Here’s the first and second,

          public string Property1 { getset; }
         public string Property1ColumnName { getset; }
         public string Property1Flag { getset; }
 
         public string Property2 { getset; }
         public string Property2ColumnName { getset; }
         public string Property2Flag { getset; }

This will allow us to generically decide whether to display the column (the PropertyFlag) and display the header (Column name) and of course the property itself.

Placement

With this in hand, we are ready to create our generic grid.  First step: create a place for it.  For that, I used a StackLayout in my xaml file (Foo.xaml)

 <?xml version="1.0" encoding="utf-8" ?>
 <ContentPage   
       xmlns="http://xamarin.com/schemas/2014/forms"
     xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
              x:Class="Foo.Views.GenericReportPage">
     <ContentPage.Content>
         <ScrollView>
             <StackLayout x:Name="GridHolder"
                          Padding="10">
 
             </StackLayout>
          </ScrollView>
     </ContentPage.Content>
 </ContentPage>

The key here is the StackLayout with the name GridHolder.  We’re going to put our grid into that StackLayout.

Creating the Grid

The first thing we agreed on was that there would never be more than 20 columns of information.  This design can handle a bit more than that, but it becomes unwieldy.  If we needed an undetermined number, we’d modify how we go about this; the code would be a bit more complex but we’d have greater flexibility.

The rest of the work is done in the code behind file (foo.xaml.cs).  We start by creating the grid itself:

     Grid grid = new Grid() { ColumnSpacing = 2 };

We then initialize the columnCount (which, as you might guess, keeps track of the number of columns we’re going to display) and we create a List to hold the generic items we’ll be working with.

       private int columnCount;
       private List<GenericItemgenericItems { getset; } = 
                 new List<GenericItem>();

We next call two methods,

           await PopulateGenericReport();
          BuildGrid();

Populate Generic Report

In PopulateGenericReport() we go out to the server and get back a collection of GenericItems as described above.  We get the first item in the collection and then we iterate through each genericItem in the collection looking to see if the flag is set to true (“1”).  If so, we increment the count of columns.

          if (genericItems != null && genericItems.Count > 0)
          {
             GenericItem genericItem = genericItems[0];
             columnCount = 0;
 
             if (genericItem.Property1Flag == "1")
             {
                columnCount++;
             }
 
             if (genericItem.Property2Flag == "1")
             {
                columnCount++;
             }

We do this for all 20 potential columns.  Typically we end up with three that have their flag set to “1” (true).

BuildGrid

We now turn to the heart of the matter: creating the Grid.  We start by telling the StackLayout we saw earlier (named GridHolder) to clear its children.  We next call CreateGrid and then we Populate the Grid, and finally we add the grid to the StackLayout.

       private void BuildGrid()
       {
          GridHolder.Children.Clear();
          CreateGrid();
          PopulateGrid();
          GridHolder.Children.Add(grid);  
      }

To create the Grid we create a new instance of Grid, clear the row and column definitions, and then declare the number of rows we need (how many items are there altogether) and the number of columns (how many had their flag set to “1”…

       private void CreateGrid()
       {
 
          grid = new Grid();
          grid.RowDefinitions.Clear();
          grid.ColumnDefinitions.Clear();
 
          for (int i = 0i < genericItems.Count; i++)
          {
             grid.RowDefinitions.Add(new RowDefinition { 
                  Height = new GridLength( 
                         1GridUnitType.Auto) });
          }

         for (int j = 0j < columnCount; j++)
          {
             grid.ColumnDefinitions.Add(
                      new ColumnDefinition { 
                  Width = new GridLength(
                             1GridUnitType.Star) });
          }
       }
 

It is now time to populate the grid.  We do this by iterating through each GenericItem in our collection.  If the flag is true, we add the header (this gets overwritten a few times) and the data from the column:

  foreach (GenericItem genericDisplayItem    
            in genericItems)
  {
        column = 0;
 
       if (genericDisplayItem.Property1Flag == "1")
       {
 
             headerLabel = new Label { 
              Text = genericItems[genericItemCounter]
                      .Property1ColumnName };
              grid.Children.Add(headerLabelcolumn0);
 
            displayLabel = new Label {
              Text = genericItems[genericItemCounter]
                          .Property1 };
             displayLabel.VerticalOptions = 
                          LayoutOptions.Center;
            displayLabel.FontSize = 10;
            grid.Children.Add(displayLabelcolumn++, row);
    }

Again, we do this for all 20 columns, creating labels only for those whose PropertyFlag is set true.

Done

That’s pretty much all there is to it.  You do have to handle the “more” button, displaying all the columns (whether or not the flag is true). We figure out the row of the button that was pushed, and we send its data to the details page.

       private void HandleMoreButtonClicked( 
               object senderEventArgs e)
       {
          Button theButton = (Button)sender;
          int row = Grid.GetRow(theButton);
          for (int i = 0i < genericItems.Count; i++)
          {
             if (i == row - 1)
             {
                GenericItem genericItem = genericItems[i];
                Navigation.PushAsync(        
                        new GenericDetailPage(genericItem));
             }
          }
       }

Hope this is helpful the next time you need a generic set of reports.

About Jesse Liberty

Jesse Liberty is the Principal Mobile Developer with IFS Core. He has three decades of experience writing and delivering software projects. He is the author of 2 dozen books and a couple dozen Pluralsight & LinkedIn Learning courses, and has been a Senior Technical Evangelist for Microsoft, a Distinguished Software Engineer for AT&T, a VP for Information Services for Citibank and a Software Architect for PBS. He is a Xamarin Certified Mobile Developer and a Xamarin MVP and a Microsoft MVP.
This entry was posted in Essentials, Xamarin, XAML. Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *