Xamarin UI Controls - Case Study: Building the Error Monitoring Client App (Part 3)

This is the third blog post in our "Building the Error Monitoring Client App" series. We continue to document how we developed a custom crash reporting mobile client by leveraging DevExpress Xamarin.Forms UI Controls. If you are new to this blog series, you can review part 1 and 2 using the links below:

In our last post, we described data loading/binding for the Reports list view. In this post, we will customize our Xamarin Grid’s visual elements to deliver the best possible user experience for the Error Monitoring mobile client app.

NOTE: To reserve your free copy of our Xamarin Forms UI suite, simply point your browser to: https://www.devexpress.com/xamarin-free/. If you have never seen or used our Xamarin Forms UI controls, please review our YouTube videos and our online documentation for more information.

Xamarin Grid Customization for the Crash Reports List

As you know, we rely on the DevExpress Xamarin Data Grid and its TemplateColumn to do most of the heavy lifting in our Error Monitoring mobile client app.

The following image details each UI element/field within an individual grid row.

Using TemplateColumn

To implement the custom design pictured above, the Error Monitoring client app uses our Xamarin Grid’s TemplateColumn object. This object provides a DisplayTemplate property which accepts Xamarin.Forms.DataTemplate. Once cell content has been created from this template, a CellData object is assigned to its BindingContext. CellData has both Value and Source properties. Value holds the current cell value, and Source stores a user’s data object. Since the current template is quite detailed, we are going to use the Source object to access the Report object.

<dxg:TemplateColumn>
  <dxg:TemplateColumn.DisplayTemplate>
    <DataTemplate>
      <Grid>
        <Grid.ColumnDefinitions>
          <ColumnDefinition Width="*"/>
          <ColumnDefinition Width="7"/>
        </Grid.ColumnDefinitions>
        <Grid Grid.Column="0">
          <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
          </Grid.RowDefinitions>
          <Grid Grid.Row="0">
            <StackLayout Orientation="Horizontal" Grid.Column="0">
              <StackLayout Orientation="Horizontal" IsVisible="{Binding Source.HasAffectedUsers}">
                <controls:IconView ImageSource="Users.svg"/>
                <Label Text="{Binding Source.AffectedUsersCount}"/>
              </StackLayout>
              <StackLayout Orientation="Horizontal" IsVisible="{Binding Source.HasCounter}">
                <controls:IconView ImageSource="Count.svg"/>
                <Label Text="{Binding Source.Counter}"/>
              </StackLayout>
            </StackLayout>
            <StackLayout Orientation="Horizontal" HorizontalOptions="End">
              <Label Text="{Binding Source.Version}" HorizontalTextAlignment="End"/>
              <Label Text="{Binding Source.DateTimeLastReport}" HorizontalTextAlignment="End" IsVisible="{Binding Source.HasDateTimeLastReport}" />
            </StackLayout>
          </Grid>
          <Label Grid.Row="1" MaxLines="2" Text="{Binding Source.ApplicationName}"/>
          <Label Grid.Row="2" MaxLines="1" Text="{Binding Source.ReportsListInfo}"/>
        </Grid>
        <BoxView Grid.Column="1" Color="{Binding Source.StatusColor}"/>
      </Grid>
    </DataTemplate>
  </dxg:TemplateColumn.DisplayTemplate>
</dxg:TemplateColumn>

This is an abridged version of our source code. You can review the full code here.

Swipe Actions

The Error Monitoring mobile client will allow users to modify the status of individual crash reports with ease (each crash report is associated with one of the following: Active, Ignore, or Close).

Once a crash report/problem has been addressed, users can close the report (change its status to closed) or ignore the problem (change its status to ignore).

Since we believe that swipe actions help reduce UI complexity and improve an app’s overall user experience, the Error Monitoring mobile client will use swipe actions to allow users to change a report’s status. As you might expect, we will use our Xamarin Grid’s Swipe Actions feature to activate swipe support within the Error Monitoring app.

We will define our swipe actions in XAML.

<ContentPage>
  <dxg:DataGridView>
    <dxg:DataGridView.EndSwipeItems>
      <dxg:SwipeItem Image="Ignore.png" Caption="Ignore" Command="{Binding IgnoreCommand}" />
      <dxg:SwipeItem Image="Close.png" Caption="Close" Command="{Binding CloseCommand}" />
    </dxg:DataGridView.EndSwipeItems>
  </dxg:DataGridView>
</ContentPage>

The default appearance of swipe actions are displayed in the screenshot above. Because we have a slightly different UI design, we will use a custom template to modify this default appearance:

<ContentPage>
  <dxg:DataGridView>
    <dxg:DataGridView.EndSwipeItems>
      <dxg:SwipeItem Template="{StaticResource SwipeItemIgnoreTemplate}" Command="{Binding IgnoreCommand}" />
      <dxg:SwipeItem Template="{StaticResource SwipeItemCloseTemplate}" Command="{Binding CloseCommand}" />
    </dxg:DataGridView.EndSwipeItems>
  </dxg:DataGridView>
</ContentPage>

One of our custom SwipeItem templates:

<DataTemplate x:Key="SwipeItemIgnoreTemplate">
  <StackLayout Margin="0,8,1,7.5" BackgroundColor="{DynamicResource SwipeItemBackgroundColor}">
    <controls:IconView ImageSource="Ignore.svg" ForegroundColor="{DynamicResource SwipeItemForegroundColor}" VerticalOptions="EndAndExpand" HorizontalOptions="Center" WidthRequest="24" HeightRequest="24"/>
    <Label Text="Ignore" Style="{StaticResource GridCellMainText}" TextColor="{DynamicResource SwipeItemForegroundColor}" VerticalOptions="StartAndExpand" HorizontalOptions="Center" Margin="0,8,0,0"/>
  </StackLayout>
</DataTemplate>

With our UI changes in place, we’ll now focus on the functionality associated with IgnoreCommand and CloseCommand.

IgnoreCommand = new Command(report => ((ReportViewModel)report).UpdateStatus(ReportStatus.IgnoredOnce));
CloseCommand = new Command(report => ((ReportViewModel)report).UpdateStatus(ReportStatus.ClosedOnce));

To update report status, we need to send a request to LogifyRestApiEndpoint.

POST https://logifyRestApiEndpoint/status
{
  "ReportId": reportId,
  "Status": status
}

Both commands update the report status via the ReportDataProvider.

using Logify.Services

namespace Logify.ViewModels {
  public class ReportViewModel : NotificationObject {
    ReportDataProvider dataProvider;

    ...

    public void UpdateStatus(ReportStatus status) {
      if (status != Report.Status) {
        Task.Run(() => dataProvider.UpdateStatus(Report, status));
        Report.Status = status;
        OnPropertyChanged("StatusColor");
      }
    }
  }
}

In our UI mockup, each crash report record includes a status indicator (a colored stripe on the rightmost edge of each report record). The following colors are used:

  • active – red

  • closed – yellow

  • ignored – gray

Our ReportViewModel maintains this info within its StatusColor property. The property is bound to our custom template. Once a given report status changes, the OnPropertyChanged(“StatusColor”) method updates the color for the report status indicator.

NOTE: Complete source code is available for download on GitHub.

As mentioned earlier, we’ve used our data provider instance to update the report’s status in (see source code above). To do this, ReportDataProvider sends a request to Logify’s HTTP API:

using System.Net.Http;

namespace Logify.Services {
  public class ReportDataProvider {
    readonly HttpClient httpClient = new HttpClient();

    ...

    public async Task<string> UpdateStatus(Report report, ReportStatus status) {
      IDictionary<string, string> data = BuildPostData(report);
      data["Status"] = ((int)status).ToString();
      return await Post("logifyRestApiEndpoint/status", data);
    }

    public async Task<string> Post(string url, IDictionary<string, string> data) {
      try {
        var content = new FormUrlEncodedContent(data);
        var response = await httpClient.PostAsync(url, content);
        var result = await response.Content.ReadAsStringAsync();
        return result;
      } catch (Exception e) {
        return string.Empty;
      }
    }
  }
}

As you can see in the image above, we’ve used our Xamarin Grid’s TemplateColumn to create a compact/efficient UX for our app’s custom cell layout. We’ve also used swipe actions to deliver the best possible user experience.

This concludes part 3 of this series. In our next post, we’ll implement the main menu used within the Error Monitoring app.

Should you have any questions about our Xamarin Grid’s TemplateColumn or its support for swipe actions, leave your comments below or contact us at info@devexpress.com. We’ll be happy to follow up.

Free DevExpress Products - Get Your Copy Today

The following free DevExpress product offers remain available. Should you have any questions about the free offers below, please submit a ticket via the DevExpress Support Center at your convenience. We'll be happy to follow-up.
No Comments

Please login or register to post comments.