Microsoft teamed up with HighView Software, a Xamarin consulting firm located at Beijing China which provide cross-platform and native mobile solutions with Xamarin, to develop a golf scoring app which revolutionize the way that golfers are scoring their games of golf.

Core Team:

  • James Zhou – CEO, HighView Software
  • Tingting An – Senior Xamarin Engineer, HighView Software
  • Hao Zhang – Senior Xamarin Engineer, HighView Software
  • Yan Zhang – Audience Evangelism Manager, Microsoft DX
  • Junqian Zhuang – Technical Solution Professional, Microsoft DX
  • Tory Xu – Senior Technical Evangelist, Microsoft DX
  • Michael Li – Technical Evangelist, Microsoft DX
  • Zepeng She – Technical Evangelist, Microsoft DX

    Hackfest Image

Customer profile

High View Software is a Xamarin consulting firm located at Beijing China, providing cross-platform and native mobile solutions with Xamarin for clients from US, Australia and China. The company also provides developer training services for app development with Xamarin.

ScoreCapture is a company that provides system for all golf scoring formats and leaderboard types, no matter whether is multi-round tournament with multiple teams and different formats each day, or going on golf tour with a few mates.

Problem statement

As the rapidly business growing, ScoreCapture would like to revolutionize the way that golfers are scoring their games of golf. As to make a better experience for golf scoring, it’s a prerequisite to achieve that goal with having good-quality apps on the major mobile platforms instead of just through the web. But they don’t would like to have double cost to hire a new iOS dev team and a new Android dev team, they just would like to deliver the app just in time and have the same UI experience in different platform. In addition, to enhance the experience and simplifying the tedious task of scribbling the scores and results down after every hole, the golf scoring logic is very complex. They want to bring their scoring app to both Android and iOS with a productive, cost-effective way, and want to make their core scoring logic code in a more manageable way and cross platforms.

Solution, steps, and delivery

We split into three separate work streams: one to create the front end (Android and iOS app in Xamarin), one to create the browser web app, and one to create the back end (Azure SQL Database, Azure blob storage and API app).

Xamarin Architecture Diagram

What we achieved in this Ascend+ program:

  1. Provided a solution by using Xamarin.Forms. Both the Android and iOS apps can share same scoring logic code, furthermore most of the app screens also share same UI code by the power of Xamarin.Forms.

  2. Use Xamarin to develop the mobile app on iOS & Android, which make that golf scores are captured on any smart device, no matter IOS or Android operating systems through a user-friendly application. The scores can then be presented live, on any customized leaderboards which have been personally optimized by the golf event.

  3. Use Azure Notification Hubs to push notification for mobile apps to maintain the app as a low cost and effective way.

  4. Deploy the Web App to Azure and users can browse the golt scores in any browser.

  5. Use Blob Storage and SQL database to store the pictures and the golf score information.

Front End

We used Xamarin.Forms to lay out all the application’s screens. This allowed us to reuse UI code across both Android and iOS.

Splash Screen

Splash Screen

Golf Screenshot

Golf Screenshot

Golf Screenshot

Golf Screenshot

Custom Renderers

Xamarin Forms allows you to customize the user interface beyond the built-in controls using Custom Renderers. There is also a custom component store that lets you use community-created controls quickly. In our development experience, if you would like to use Xamarin.Forms to build more custom controls, you should use cusome renders or use 3rd-party component. Custom renderers provide a powerful approach for customizing the appearance and behavior of Xamarin.Forms controls. For example, we would like to create a circle and place some controls inside (such as label, Image, etc.), we use the Custom Renderers to do that.

```C sharp public class Circle : BoxView { public static readonly BindableProperty StrokeProperty = BindableProperty.Create<Circle, Color>( p => p.Stroke, Color.Transparent);

    public Color Stroke
    {
        get { return (Color)GetValue(StrokeProperty); }
        set { SetValue(StrokeProperty, value); }
    }

    public static readonly BindableProperty StrokeThicknessProperty =
        BindableProperty.Create<Circle, double>(
            p => p.StrokeThickness, default(double));

    public double StrokeThickness
    {
        get { return (double)GetValue(StrokeThicknessProperty); }
        set { SetValue(StrokeThicknessProperty, value); }
    }

    public new static readonly BindableProperty X =
     BindableProperty.Create<Circle, float>(
         p => p.Cx, default(float));

    public float Cx
    {
        get { return (float)GetValue(X); }
        set { SetValue(X, value); }
    }

    public new static readonly BindableProperty Y =
    BindableProperty.Create<Circle, float>(
    p => p.Cy, default(float));

    public float Cy
    {
        get { return (float)GetValue(Y); }
        set { SetValue(Y, value); }
    }

    public static readonly BindableProperty Radius =
    BindableProperty.Create<Circle, float>(
    p => p.radius, default(float));

    public float radius
    {
        get { return (float)GetValue(Radius); }
        set { SetValue(Radius, value); }
    } }

Android App:

```C sharp

[assembly: ExportRenderer(typeof(Circle), typeof(CircleRenderer))]

namespace Score_Capture.Droid.Custom_Renderers
{
    public class CircleRenderer : BoxRenderer
    {
        public CircleRenderer()
        {
            SetWillNotDraw(false);
        }

        public override void Draw(Canvas canvas)
        {
            var rbv = (Circle)Element;
            var p = new Paint()
            {
                Color = rbv.Color.ToAndroid(),
                AntiAlias = true,
            };

            var radius = Resources.GetDimension(Resource.Dimension.circleRadius);

            p.Color = rbv.Stroke.ToAndroid();
            p.StrokeWidth = (float)rbv.StrokeThickness;
            p.SetStyle(Paint.Style.Stroke);

            var height = canvas.Height;
            var width = canvas.Width;

            canvas.DrawCircle(width / 2, height / 2, radius, p);
        }
    }
}


Android Screenshot

Golf Screenshot

iOS App:

```C sharp

[assembly: ExportRenderer(typeof(Circle), typeof(CircleRenderer))]

namespace Score_Capture.iOS.Custom_Renderers { public class CircleRenderer : BoxRenderer { public override void Draw(CGRect rect) { var circle = (Circle)Element;

        using (var context = UIGraphics.GetCurrentContext())
        {
            context.SetFillColor(circle.Color.ToCGColor());
            context.SetStrokeColor(circle.Stroke.ToCGColor());
            context.SetLineWidth((float)circle.StrokeThickness);
            nfloat s = (float)circle.StrokeThickness;
            if (rect.Width >= rect.Height)
            {
                s += (rect.Width - rect.Height) / 2;
            }
            var rc = rect.Inset((float)s, (float)s);
            rc = rc.Inset(0, (rc.Height - rc.Width) / 2);
            var radius = 0f;
            radius = (float)Math.Min(rc.Height / 2, rc.Width / 2);

            var path = CGPath.FromRoundedRect(rc, radius, radius);
            context.AddPath(path);
            context.DrawPath(CGPathDrawingMode.FillStroke);
        }
    }

    protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        base.OnElementPropertyChanged(sender, e);

        if (e.PropertyName == RoundedBoxView.CornerRadiusProperty.PropertyName
         || e.PropertyName == RoundedBoxView.StrokeProperty.PropertyName
         || e.PropertyName == RoundedBoxView.StrokeThicknessProperty.PropertyName)
        {
            SetNeedsDisplay();
        }
    }

} }

*iOS Screenshot*

  ![Golf Screenshot](/images/2017-04-22-HighView/custom_render_circle_ios.jpg)

#### Multi-line text in Button ####

In this project, we would like to build a button that can display multi-line text.
In Android app, we did it just a few lines code.

```C Sharp

var myGamesListButton = new Button
{
      Text = "My\nGames\nList",
      TextColor = Xamarin.Forms.Color.White,
      Font = smallFont,
      BackgroundColor = Helpers.Color.Positive.ToFormsColor(),
      BorderRadius = 5
};


But we found that it didn’t work for iOS due to the default setting of LineBreakMode for Button is different. So we used the Custom Renderer again to make multi-line text in a Button in iOS app.

```C Sharp

public class MultiLineButton:Button { public static readonly BindableProperty BageValueProperty = BindableProperty.Create<MultiLineButton, int>( p => p.BageValue, default(int));

    public int BageValue
    {
        get { return (int)GetValue(BageValueProperty); }
        set { SetValue(BageValueProperty, value); }
    }
}

```C Sharp

[assembly: ExportRenderer(typeof(MultiLineButton), typeof(MultiLineButtonRenderer))]
namespace Score_Capture.iOS.Custom_Renderers
{
    public class MultiLineButtonRenderer:ButtonRenderer
    {
        protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.Button> e)
        {
            base.OnElementChanged(e);

            Control.TitleLabel.LineBreakMode = UILineBreakMode.CharacterWrap;
            Control.TitleLabel.TextAlignment = UITextAlignment.Center;
        }
    }
}


Multi-line text Screenshot in iOS

Golf Screenshot

Entry Max Length Limit

The Behaviors in Xamarin.Forms is extremely useful for attaching a piece of functionality to an existing element in a view. Technically, they are C# classes that inherit from Behavior where T is the UI element to which a functionality is attached. Behaviors are powerful because they are reusable and easy to incorporate into unit testing since they are an independent piece of functionality. They can be used from the simplest example, like adding an email text validator to the Entry element, to advanced situations like creating a rating control using tap gesture recognizer in a view. When add the limit for the entry length, we found that there's no existing method for the entry, so we use behavior to create a class to set up a max length limit for the entry.

```C Sharp public class MaxLengthValidator : Behavior { public static readonly BindableProperty MaxLengthProperty = BindableProperty.Create("MaxLength", typeof(int), typeof(MaxLengthValidator), 0);

    public int MaxLength
    {
        get { return (int)GetValue(MaxLengthProperty); }
        set { SetValue(MaxLengthProperty, value); }
    }

    protected override void OnAttachedTo(Entry bindable)
    {
        bindable.TextChanged += bindable_TextChanged;
    }

    private void bindable_TextChanged(object sender, TextChangedEventArgs e)
    {
        if (e.NewTextValue.Length > 0 && e.NewTextValue.Length > MaxLength)
            ((Entry)sender).Text = e.NewTextValue.Substring(0, MaxLength);
    }

    protected override void OnDetachingFrom(Entry bindable)
    {
        bindable.TextChanged -= bindable_TextChanged;
    } }

nick.Text = player.Nickname; nick.Behaviors.Add(new MaxLengthValidator { MaxLength = 7 });


#### Different Background depends on Font Color ####

As you see, the app is with many diffent color in diffent button and background due to the app is using under bright sun outside so we need quite sharp contrast color in app. Thus, we made a function to generate the color depends on the background color to make sure we have the best experience for using the app under bright sun.

```C Sharp

public class TeamColorCalc
{
    public static Color GetForeColor(Color foreColor, Color backColor)
    {
        double luminance = 0.30 * backColor.R + 0.59 * backColor.G + 0.11 * backColor.B;
        if (luminance > 0.5)
        {
            return Color.Black;
        }
        else
        {
            return Color.White;
        }
    }



    private static double GetColourVariance(Color color1, Color color2)
    {
        if (color1.Luminosity > color2.Luminosity)
        {
            return color1.Luminosity - color2.Luminosity;
        }
        else
        {
            return color2.Luminosity - color1.Luminosity;
        }
    }
}


Different Background depends on Font Color

Golf Screenshot

Back End

Notification

The Mobile Apps feature of Azure App Service uses Azure Notification Hubs to send pushes, we set up a notification hub for the mobile app ot make push notification very easy.

  1. In the Azure portal, go to web+mobile to find the “notification hub” resource to the app. Click to create a notification hub.

  2. Click the “Notification Services” to set up channel, Apple and Google.

  3. Update code in existing Mobile Apps back-end project to send a push notification every time a new item is added.

```C sharp

private async Task CreateAzureNotificationAsync( long? recipientUserId, string message, string pns) { List tags = new List(); tags.Add(recipientUserId.ToString());

Microsoft.Azure.NotificationHubs.NotificationOutcome outcome = null;

var alertMessage = $"{senderDisplayName}: {message}";
switch (pns.ToLower())
{
     case "apns":
     // iOS
        var alertObj = new
        {
            aps = new Dictionary<string, object>
            {
                ["alert"] = alertMessage,
                ["content-available"] = 1,
                ["sound"] = "default"
            }
        };
        var alert = JsonConvert.SerializeObject(alertObj);
        outcome = await _hub.SendAppleNativeNotificationAsync(alert, tags).ConfigureAwait(false);
        break;
    case "gcm":
        var notifObj = new
        {
            data = new
            {
                message = alertMessage,
                priority = "high"
            }
        };
        var notif = JsonConvert.SerializeObject(notifObj);
        outcome = await _hub.SendGcmNativeNotificationAsync(notif, tags).ConfigureAwait(false);
        break;
}
return outcome.State; }

#### Blob ####

We use the Blob to store some score result file and image.

```C Sharp

namespace Score_Capture
{
    public class BlobOperation
    {
        public BlobOperation ()
        {
        }

        public static async Task UploadImgToAzure(Image img)
        {
            // Retrieve storage account from connection string.
            CloudStorageAccount storageAccount = CloudStorageAccount.Parse("DefaultEndpointsProtocol=https;AccountName=your_account_name_here;AccountKey=your_account_key_here");

            // Create the blob client.
            CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();

            // Retrieve reference to a previously created container.
            CloudBlobContainer container = blobClient.GetContainerReference("golfimgcontainer");

            // Create the container if it doesn't already exist.
            await container.CreateIfNotExistsAsync();

            string date = DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss");

            // Retrieve reference to a blob named "GolfImgBlob".
            CloudBlockBlob blockBlob = container.GetBlockBlobReference("golfimgblob_"+date);

            Bitmap newImage = new Bitmap(img);
            newImage.Save(stream, ImageFormat.png);
            stream.Seek(0, SeekOrigin.Begin);
            blob.Properties.ContentType = "image/png";

            using (stream)
            {
                await blob.UploadFromStream(stream);
            }
        }
    }
}

Challenges

We met some issues and challenges to solve:

Challenge #1: Loading API data is too slow

We use the ModernHttpClient and Fusillade to replace the default HttpHandler, greatly increase the speed of loading API data.

```C Sharp

static ApiService() { BackGroundClient = new HttpClient(new RateLimitedHttpMessageHandler(new ModernHttpClient.NativeMessageHandler(), Priority.Background)); UserInitiatedClient = new HttpClient(new RateLimitedHttpMessageHandler(new ModernHttpClient.NativeMessageHandler(), Priority.UserInitiated)); SpeculativeClient = new HttpClient(new RateLimitedHttpMessageHandler(new ModernHttpClient.NativeMessageHandler(), Priority.Speculative));

BackGroundClient.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type: application/x-www-form-urlencoded", "application/json");
UserInitiatedClient.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type: application/x-www-form-urlencoded", "application/json");
SpeculativeClient.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type: application/x-www-form-urlencoded", "application/json");

NetCache.Speculative.ResetLimit(1048576 * 5/*MB*/);  }

```

Challenge #2: Local storage make UI stuck

We use the SQLite.Net.Async-PCL to read and write the data asynchronously to solve the problem.

Conclusion

Due to HighView is professional Xamarin consulting firm, so the main goal of this engagement was to provide a sample solution in Xamarin area and certainty Xamarin+Azure is a quite good solution for the simple UI and complex logic. The project has provided Xamarin.Forms application and web site, both working with a Mobile Apps backend.

During the Xamarin hackfest in Beijing, we were able to verify that the notification works great to built into Mobile Apps. We were also able to verify that the blob storage can be used in a Xamarin.Form application. These two verifications were very important to the Highview team as they had set the foundation for the architecture of the Xamarin solution.

Thank the HighView team! Finally we published the website, Android App and iOS App:

Web: http://member.scorecapture.com/ScoreCapture/

Android app: https://play.google.com/store/apps/details?id=co.za.scorecapture

iOS app: https://itunes.apple.com/us/app/scorecapture/id1026714564?mt=8

“Xamarin.Forms makes us quickly delivery a high quality app and at the same time, integrated Azure App Service and Storage we can quickly make the App connect to the Cloud.” - James Zhou, CEO of HighView Software

Additional resources

Develop Cloud Connected Mobile Apps with Xamarin and Microsoft Azure

Add push notifications to your Xamarin.Forms app

How to use Blob Storage from Xamarin

Create your first ASP.NET web app in Azure in five minutes