This is the third blog post in our "Building the Logify Client App" series. If you are new to this blog series, you can review part 1 and 2 using the links below:
- Xamarin.Forms UI Controls - Building the Logify Client App (Part 1)
- Xamarin.Forms UI Controls - Building the Logify Client App (Part 2)
In our last post, we described data loading/binding for Logify’s Reports list view. In this post, we will customize our Xamarin Grid’s visual elements to deliver the best possible user experience for the Logify mobile client app.
Xamarin Grid Customization for Logify's 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 Logify 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 Logify 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
Logify’s web interface allows users to modify the status of individual crash reports with ease. The Logify mobile client will offer users the same capability (each crash report is associated with one of the following: Active, Ignore, or Close)
Once a crash report/problem has been addressed, Logify 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 Logify 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 Logify 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 LogifyRWA.Services namespace LogifyRWA.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.
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 LogifyRWA.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 Logify 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.