Cross Platform C#
Customizing a Xamarin Forms Application, Part 2
Last time, I created the app. This time, I take care of a few annoying but important issues with it, especially in the area of the user's experience with application connectivity.
Also read:
In an earlier column on Xamarin Forms app customizing, I looked at how realistic it is to create a Xamarin Forms (XF) application and how close it can tie to the platform. How close does an XF application look in comparison to an iOS and Android application? Now that my startup has been using the application, we found a few items that were "wanting":
- Saving log-in information. When an app is loaded, no one wants to continually type in a name and password. Users want to just load the application and go with it.
- Network requests. When a network request is ongoing, such as an upload, what kind of information is communicated back to the user? Is it just the activity indicator or is more possible?
- Network availability. Is there some mechanism to check for a 3G/4G/Wi-Fi network? If there is no network, the application should communicate this to the user or at least not crash.
I'll look at these nagging features and how to improve the UX of the application.
Saving Settings
One of the first annoying issues I found was that having to re-log in to my application was a major pain. Sometimes an upload would fail, an exception would be generated and the application would fail. As a user, I had to go back and log back in. This was a hassle. What I wanted was a simple way to automatically log back in if there was a failure of the application. Specifically, I wanted the application to automatically log back in. As a side note, there is also the need to log out, so that a user can log in as a different user.
There's a NuGet package from Xamarin Evangelist James Montemagno that allows a developer to save user settings. The first step is to set up the Settings class in the project, as shown in Listing 1.
Listing 1: Saving User Settings
public static class Settings
{
private const string UserNameKey = "username";
private static readonly string UserNameDefault = string.Empty;
private const string UserPasswordKey = "password";
private static readonly string UserPasswordDefault = String.Empty;
public static string UserName
{
get { return AppSettings.GetValueOrDefault<string>(UserNameKey, UserNameDefault); }
set { AppSettings.AddOrUpdateValue<string>(UserNameKey, value); }
}
public static string Password
{
get { return AppSettings.GetValueOrDefault<string>(UserPasswordKey, UserNameDefault); }
set { AppSettings.AddOrUpdateValue<string>(UserPasswordKey, value); }
}
private static Plugin.Settings.Abstractions.ISettings AppSettings
{
get
{
return CrossSettings.Current;
}
}
}
The next step is to call to save the UserName and Password. Because these are properties, this is rather easy.
Note: This is not a full discussion on security. Developers should not save their user names and passwords directly in a device without understanding the full repercussions of this action. Another option is to return a token representing the user, so that the clear test of the user name and the password are not saved.
The final step is to create a logout button. In this example, this is done in the Tournaments.cs file. Here's the code to perform this:
ToolbarItems.Add(new ToolbarItem
{
Text = "Logout",
Order = ToolbarItemOrder.Primary,
Command = new Command(() => {
var thisApp = (GolfGameClubApp.App)Application.Current;
thisApp.Logout();
})
});
In this example, a toolbar item is created. When the user selects it, the values in the user name and password settings are emptied out, and the MainPage property of the App class is set to the Login class.
Long-Running Tasks
Users hate to have long running happening and not know that something is occurring. Is the app hung? Is there something else going on? Was there a failure? What's going on? These are some of the thoughts going through a user's mind when an application is performing a long running task with no feedback.
With Xamarin.Forms, there's the ActivityIndicator that uses the platform-specific mechanisms to communicate that something is happening. For me, I never could seem to make it work like I wanted. I wanted a big heads-up display (HUD). (I cannot lie: I like big HUDs.) Unfortunately, the functionality that was needed isn't currently available in Xamarin.Forms. Thankfully, Xamarin.Forms allows developers to add this functionality in. The basic steps to use a HUD in XF is:
- Add an Android HUD component into the Droid project. We used AndroidHUD.
- Add an iOS HUD component into the iOS project. We used BigTed.
- Define an interface to call the HUD. This interface is within the PCL project. This is shown in the associated listing.
- Implement the method calls within the Droid and iOS projects. These are shown in the associated listing.
- Call the methods to display the HUD, as well as dismiss it.
Let's look at the code. Here's the Interface Definition:
using System;
namespace GolfGameClubApp
{
public interface IHud
{
void Show();
void Show(string message);
void Dismiss();
}
}
Listing 2 is the Android HUD implementation; it's within the Droid project.
Listing 2: Android HUD Implementation
using System;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using AndroidHUD;
[assembly: Dependency(typeof(GolfGameClubApp.Droid.DependencyServices.Hud))]
namespace GolfGameClubApp.Droid.DependencyServices
{
public class Hud : IHud
{
public Hud()
{
}
public void Show()
{
AndroidHUD.AndHUD.Shared.Show(Xamarin.Forms.Forms.Context);
}
public void Show(string message)
{
AndHUD.Shared.Show(Xamarin.Forms.Forms.Context, message);
}
public void Dismiss()
{
AndHUD.Shared.Dismiss(Xamarin.Forms.Forms.Context);
}
}
}
Listing 3 shows the iOS HUD implementation; it's within the iOS project.
Listing 3: iOS HUD Implementation
using System;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
using BigTed;
[assembly: Dependency(typeof(GolfGameClubApp.iOS.DependencyServices.Hud))]
namespace GolfGameClubApp.iOS.DependencyServices
{
public class Hud : IHud
{
public Hud()
{
}
public void Show()
{
BTProgressHUD.Show();
}
public void Show(string message)
{
BTProgressHUD.Show(message);
}
public void Dismiss()
{
BTProgressHUD.Dismiss();
}
}
}
Finally, to call the methods to display and dismiss the HUD, an example call is shown next:
var username = UserName.Text;
var pwd = PassWord.Text;
hud.Show("Logging In.");
var res = await WebServices.ws.Login(username, pwd);
hud.Dismiss();
In this example code, the user name and password are retrieved from the UI, and a Web service is called to log the user in. The HUD is shown and dismissed when the Web service is called.
Network Availability
When you have an application that's running on a mobile device, it's fairly important to know if the network is available. If the network isn't available, then it doesn't make much sense to attempt to do anything network related, because there will be some type of error. Instead, the application shouldn't attempt the operation and should instead present the user with some type of information that a connection isn't available.
Thankfully, there is a NuGet package that handles this that's also authored by Montemagno. In the case of my application, I just needed to test to make sure that a network connection was available. If an Internet connection is available, the application would continue on. Otherwise, a message would be presented to the user that an Internet connection isn't available. Testing for the Internet connection is accomplished by the following code:
var isOnline = Plugin.Connectivity.CrossConnectivity.Current.IsConnected;
The call to get the IsConnected property will return a Boolean that can be used in code to execute, the code if a phone is connected or the code for when a phone isn't connected.
For the purposes of this application, this was all that was necessary. However, the component supports much more. For a list of the other options available, check the references at the end of this article.
Thoughts
As I have often said, I'm not the biggest fan of cross-platform development tools. I don't like UIs that are made from a single set of UI widgets. These UI widgets tend to look out of place on every platform. Thankfully, Xamarin.Forms UI widgets map to platform-specific widgets. With the improvements in the sample application discussed here and last month, I have an application that works fairly well and is easy enough for my non-technical cofounder to use.
Wrapping Up
What has been done? Some functionality was added to the Xamarin.Forms application that a user would expect.
- The username and password have been saved, so that the user doesn't have to re-enter them every time. This makes it quicker for the user to effectively point and shoot to take a picture. This is important, because the process of getting people to stand together, take their hats off and take the picture can actually take one to two minutes. Even during a friendly golf tournament, players don't want to do this. Making this quicker is a great help.
- Uploading a picture over a 4G network is quick. Uploading images from the application takes no more than about 5 seconds at my home golf course. Not every golf course is in an area with fast connectivity, so if it takes longer, the user needs to know that something is happening so they don't think the app is somehow locked up.
- As amazing as it sounds, network connectivity can vary across a small area (a golf course in our situation and even across a single golf hole). Verifying network connectivity can be really helpful in making your application more robust for the user.
These changes (along with the changes from last month's article) helped build a fairly good first cut of an application for taking pictures of golfers and their teams at charity event golf tournaments. I hope that these ideas will help you improve your mobile applications.
About the Author
Wallace (Wally) B. McClure has authored books on iPhone programming with Mono/Monotouch, Android programming with Mono for Android, application architecture, ADO.NET, SQL Server and AJAX. He's a Microsoft MVP, an ASPInsider and a partner at Scalable Development Inc. He maintains a blog, and can be followed on Twitter.