Specifying How Soon a Storage Queue Message Will Be Retried in an Azure Function

By default, if an exception occurs in an Azure Function that uses a Storage Queue trigger, the message will be returned to the queue and automatically retried again in the future (up to a maximum number of times).

By default, there is no delay in how soon the message can be retried.

public static class MakeUppercase
{
    [FunctionName("MakeUppercase")]
    public static void Run(
        [QueueTrigger("input-queue")]CloudQueueMessage inputQueueItem,
        ILogger log)
    {
        log.LogInformation($"Message Dequeued : {inputQueueItem.DequeueCount} time(s)");
        log.LogInformation($"Message Next Visible : {inputQueueItem.NextVisibleTime}");

        throw new Exception("Forced exception for demonstration purposes.");
    }
}

With the preceding function, when a single message is added to the queue, the following (abbreviated) output will be seen:

[15/01/2019 11:55:49 PM] Executing 'MakeUppercase' (Reason='New queue message detected on 'input-queue'.', Id=44f95504-7a99-4f23-81f2-096f0bd434a2)
[15/01/2019 11:55:49 PM] Message Dequeued : 1 time(s)
[15/01/2019 11:55:49 PM] Message Next Visible : 16/01/2019 12:05:49 AM +00:00
[15/01/2019 11:55:50 PM] Executed 'MakeUppercase' (Failed, Id=44f95504-7a99-4f23-81f2-096f0bd434a2)
[15/01/2019 11:55:50 PM] Executing 'MakeUppercase' (Reason='New queue message detected on 'input-queue'.', Id=bbede56f-e22a-461f-945c-3f3b47114de3)
[15/01/2019 11:55:50 PM] Message Dequeued : 2 time(s)
[15/01/2019 11:55:50 PM] Message Next Visible : 16/01/2019 12:05:50 AM +00:00
[15/01/2019 11:55:50 PM] Executed 'MakeUppercase' (Failed, Id=bbede56f-e22a-461f-945c-3f3b47114de3)
[15/01/2019 11:55:50 PM] Executing 'MakeUppercase' (Reason='New queue message detected on 'input-queue'.', Id=10b7495f-9cfb-4d75-bc31-db68581b8055)
[15/01/2019 11:55:50 PM] Message Dequeued : 3 time(s)
[15/01/2019 11:55:50 PM] Message Next Visible : 16/01/2019 12:05:50 AM +00:00
[15/01/2019 11:55:50 PM] Executed 'MakeUppercase' (Failed, Id=10b7495f-9cfb-4d75-bc31-db68581b8055)
[15/01/2019 11:55:50 PM] Executing 'MakeUppercase' (Reason='New queue message detected on 'input-queue'.', Id=385beb36-80b7-47a5-ba65-b0d04f956cc6)
[15/01/2019 11:55:50 PM] Message Dequeued : 4 time(s)
[15/01/2019 11:55:50 PM] Message Next Visible : 16/01/2019 12:05:50 AM +00:00
[15/01/2019 11:55:51 PM] Executed 'MakeUppercase' (Failed, Id=385beb36-80b7-47a5-ba65-b0d04f956cc6)
[15/01/2019 11:55:51 PM] Executing 'MakeUppercase' (Reason='New queue message detected on 'input-queue'.', Id=f6acfb11-41fb-416e-88b1-e113aa4424f5)
[15/01/2019 11:55:51 PM] Message Dequeued : 5 time(s)
[15/01/2019 11:55:51 PM] Message Next Visible : 16/01/2019 12:05:51 AM +00:00
[15/01/2019 11:55:51 PM] Executed 'MakeUppercase' (Failed, Id=f6acfb11-41fb-416e-88b1-e113aa4424f5)
[15/01/2019 11:55:51 PM] Message has reached MaxDequeueCount of 5. Moving message to queue 'input-queue-poison'.

Notice in the preceding output, the next visible times don’t include a delay in when the message can potentially be retried.

The next visible time controls when the message will become visible to be consumed. This default value in Azure Functions is 0. You may want to change this default if you want to add some delay between message retries (for example to help prevent message loss* for transient failures).

* Eventually, failed messages will be moved to a poison message queue.

The next visible time can be configured in the host.json file (we are using Azure Functions V2 in this article):

{
  "version": "2.0",
  "extensions": {
    "queues": {
      "visibilityTimeout": "00:00:30" 
    } 
  }  
}

The visibilityTimeout value represents a timespan (HH:MM:SS) to wait before a message becomes visible next time, in the preceding configuration, 30 seconds. Running again with this new configuration, the following output can be seen:

[16/01/2019 12:13:01 AM] Executing 'MakeUppercase' (Reason='New queue message detected on 'input-queue'.', Id=1f4f7177-4de6-4f4c-98c1-48d318892112)
[16/01/2019 12:13:01 AM] Message Dequeued : 1 time(s)
[16/01/2019 12:13:01 AM] Message Next Visible : 16/01/2019 12:23:00 AM +00:00
[16/01/2019 12:13:01 AM] Executed 'MakeUppercase' (Failed, Id=1f4f7177-4de6-4f4c-98c1-48d318892112)
[16/01/2019 12:13:32 AM] Executing 'MakeUppercase' (Reason='New queue message detected on 'input-queue'.', Id=93e9a90c-dd83-410f-9435-003712f64513)
[16/01/2019 12:13:32 AM] Message Dequeued : 2 time(s)
[16/01/2019 12:13:32 AM] Message Next Visible : 16/01/2019 12:23:32 AM +00:00
[16/01/2019 12:13:33 AM] Executed 'MakeUppercase' (Failed, Id=93e9a90c-dd83-410f-9435-003712f64513)
[16/01/2019 12:14:04 AM] Executing 'MakeUppercase' (Reason='New queue message detected on 'input-queue'.', Id=7ffe157b-7186-4f84-b8eb-02c43a260352)
[16/01/2019 12:14:04 AM] Message Dequeued : 3 time(s)
[16/01/2019 12:14:04 AM] Message Next Visible : 16/01/2019 12:24:04 AM +00:00
[16/01/2019 12:14:04 AM] Executed 'MakeUppercase' (Failed, Id=7ffe157b-7186-4f84-b8eb-02c43a260352)
[16/01/2019 12:14:36 AM] Executing 'MakeUppercase' (Reason='New queue message detected on 'input-queue'.', Id=ba3c186b-8b33-4b4c-b896-724a95fa2b25)
[16/01/2019 12:14:36 AM] Message Dequeued : 4 time(s)
[16/01/2019 12:14:36 AM] Message Next Visible : 16/01/2019 12:24:36 AM +00:00
[16/01/2019 12:14:36 AM] Executed 'MakeUppercase' (Failed, Id=ba3c186b-8b33-4b4c-b896-724a95fa2b25)
[16/01/2019 12:15:07 AM] Executing 'MakeUppercase' (Reason='New queue message detected on 'input-queue'.', Id=3de85cf0-89ad-430f-8219-ebd7b1701d4d)
[16/01/2019 12:15:07 AM] Message Dequeued : 5 time(s)
[16/01/2019 12:15:07 AM] Message Next Visible : 16/01/2019 12:25:07 AM +00:00
[16/01/2019 12:15:07 AM] Executed 'MakeUppercase' (Failed, Id=3de85cf0-89ad-430f-8219-ebd7b1701d4d)
[16/01/2019 12:15:07 AM] Message has reached MaxDequeueCount of 5. Moving message to queue 'input-queue-poison'.

Notice now that the message won’t be retried for 30 seconds between each attempt (look at the “Message Next Visible” lines).

Setting a visibility other than zero will not prevent other messages that come into the queue from being processed while waiting for retried messages to become visible again.

SHARE:

Getting Message Metadata When Using Azure Functions Storage Queue Triggers

When creating an Azure Function that is triggered by incoming messages on a Storage Queue, the type specified for the message parameter can be a simple string as follows:

public static class MakeUppercase
{
    [FunctionName("MakeUppercase")]
    public static void Run(
        [QueueTrigger("%in%")]string inputQueueItem,
        [Queue("%out%")] out string outputQueueItem,
        [Blob("%blobout%")] out string outputBlobItem,
        ILogger log)
    {
        inputQueueItem = inputQueueItem.ToUpperInvariant();
        outputQueueItem = inputQueueItem;
        outputBlobItem = inputQueueItem;
    }
}

In the preceding code, the inputQueueItem represents the content of the message that triggered the function.

If you want additional information about the queue message item itself, rather than use a string you can use CloudQueueMessage. Doing this gives you access to the metadata about the queue message including the following:

  • Message ID
  • Time message was inserted into queue
  • Time the message expires
  • How many times the message has been dequeued (i.e. read off the queue )*
  • Message next visible time
  • Message pop receipt

* A message can be returned to the queue if an exception occurs during execution of the function, this will increment the dequeue count.

In addition to the message metadata, you can still get the message content either as a string or byte array using the AsString or AsBytes properties respectively:

public static class MakeUppercase
{
    [FunctionName("MakeUppercase")]
    public static void Run(
        [QueueTrigger("%in%")]CloudQueueMessage inputQueueItem,
        [Queue("%out%")] out string outputQueueItem,
        [Blob("%blobout%")] out string outputBlobItem,
        ILogger log)
    {
        log.LogInformation($"Message Id: {inputQueueItem.Id}");
        log.LogInformation($"Message Inserted : {inputQueueItem.InsertionTime}");
        log.LogInformation($"Message Expires : {inputQueueItem.ExpirationTime}");
        log.LogInformation($"Message Dequeued : {inputQueueItem.DequeueCount} time(s)");
        log.LogInformation($"Message Next Visible : {inputQueueItem.NextVisibleTime}");
        log.LogInformation($"Message Pop Receipt : {inputQueueItem.PopReceipt}");

        log.LogInformation($"Message content (bytes) : {BitConverter.ToString(inputQueueItem.AsBytes)}");
        log.LogInformation($"Message content (string) : {inputQueueItem.AsString}");

        
        var inputQueueItemContent = inputQueueItem.AsString;
        inputQueueItemContent = inputQueueItemContent.ToUpperInvariant();
        outputQueueItem = inputQueueItemContent;
        outputBlobItem = inputQueueItemContent;
    }
}

SHARE:

Configuring Queue Names and Blob Path Bindings in Azure Functions Configuration

When working with Azure Functions in C# (specifically Azure Functions V2 in this article) you can specify bindings with hard-coded literal values.

For example, the following function has a queue trigger that is reading messages from a queue called “input-queue”, an output queue binding writing messages to “output-queue”, and a blob storage binding to write blobs to “audit/{rand-guid}”:

public static class MakeUppercase
{
    [FunctionName("MakeUppercase")]
    public static void Run(
        [QueueTrigger("input-queue")]string inputQueueItem,
        [Queue("output-queue")] out string outputQueueItem,
        [Blob("audit/{rand-guid}")] out string outputBlobItem,
        ILogger log)
    {
        inputQueueItem = inputQueueItem.ToUpperInvariant();
        outputQueueItem = inputQueueItem;
        outputBlobItem = inputQueueItem;
    }
}

All these binding values in the preceding code are hard coded, if they need to be changed once the Function App is deployed, a new release will be required.

Specifying Azure Function Bindings in Configuration

As an alternative, the %% syntax can be used inside the binding string:

public static class MakeUppercase
{
    [FunctionName("MakeUppercase")]
    public static void Run(
        [QueueTrigger("%in%")]string inputQueueItem,
        [Queue("%out%")] out string outputQueueItem,
        [Blob("%blobout%")] out string outputBlobItem,
        ILogger log)
    {
        inputQueueItem = inputQueueItem.ToUpperInvariant();
        outputQueueItem = inputQueueItem;
        outputBlobItem = inputQueueItem;
    }
}

Notice in the preceding code, parts of the binding configuration strings are specified between %%: "%in%", "%out%", and "%blobout%".

At runtime, these values will be read from configuration instead of being hard coded.

Configuring Bindings at Development Time

When running locally, the configuration values will be read from the local.settings.json file, for example:

{
    "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "UseDevelopmentStorage=true",
    "FUNCTIONS_WORKER_RUNTIME": "dotnet",
    "in": "input-queue",
    "out": "output-queue",
    "blobout" :  "audit/{rand-guid}"
  }
}

Notice the “in”, “out”, and “blobout” configuration elements that map to  "%in%", "%out%", and "%blobout%”.

Configuring Bindings in Azure

Once deployed and running in Azure, these settings will need to be present in the Function App Application Settings as the following screenshot demonstrates:

Specifying Azure Function Bindings in application settings

Now if you want to modify the queue names or blob path you can simply change the values in configuration. It should be noted that you may have to restart the Function App for the changes to take effect. You will also need to manage the switch to new queues, blobs, etc.such as what to do if after the change there are still some messages in the original input queue, etc, etc.

SHARE:

NUnit 3 Quick Tips: Asserting On Collections

When the result you want to check is a collection, you can use NUnit to assert that it has the expected number of items or is empty, that all items are unique, that specific items do/not exist, and that items exist that satisfy some condition or predicate.

Asserting on the Number of Items in a Collection with NUnit Asserts

var names = new[] { "Sarah", "Amrit", "Amanda", "Sarah" };

Assert.That(names, Has.Exactly(4).Items); // pass
Assert.That(names, Is.Empty); // fail
Assert.That(names, Is.Not.Empty); // pass

Asserting That All Items in a Collection are Unique with NUnit Asserts

Assert.That(names, Is.Unique); // fail - 2 Sarah items exist

Asserting That An Item Does or Does Not Exist in a Collection with NUnit Asserts

Assert.That(names, Contains.Item("Sarah")); // pass

// Alternative syntax
Assert.That(names, Does.Contain("Sarah")); // pass
Assert.That(names, Does.Not.Contain("Arnold")); // pass

Asserting That An Item Appears a Specified Number Of Times in a Collection with NUnit Asserts

Assert.That(names, Has.Exactly(1).EqualTo("Sarah")); // fail
Assert.That(names, Has.Exactly(2).EqualTo("Sarah")); // pass
Assert.That(names, Has.Exactly(2).EqualTo("Sarah")
                      .And.Exactly(1).EqualTo("Amrit")); // pass

Asserting That All Items In a Collections Satisfy a Predicate/Condition with NUnit Asserts

Assert.That(names, Is.All.Not.Null); // pass
Assert.That(names, Is.All.Contains("a")); // fail lowercase a
Assert.That(names, Is.All.Contains("a").IgnoreCase); // pass
Assert.That(names, Is.All.Matches<string>(name => name.ToUpperInvariant().Contains("A"))); // pass
Assert.That(names, Is.All.Matches<string>(name => name.Length > 4)); // pass

Asserting That Only One Item In a Collection Satisfies a Predicate with NUnit Asserts

Assert.That(names, Has.Exactly(1).Matches<string>(name => name.Contains("mri"))); // pass
Assert.That(names, Has.Exactly(1).Matches<string>(name => name.Contains("ara"))); // fail (2 Sarah items exist)

To learn more about NUnit 3 check out my Introduction to .NET Testing with NUnit 3 Pluralsight course to learn everything you need to know to get started, including asserts, categories, data-driven tests, customizing NUnit, and reducing duplicate test code.

SHARE:

NUnit 3 Quick Tips: Asserting On Object Reference Equality

When asserting on equality using the EqualConstraint you may not always get the behaviour you want depending on what objects are being asserted on. This can be influenced by whether or not the objects are value or reference types and if the type implements or overrides methods such as IEquatable<T> or object.Equals overrides.

Asserting on Value Type Equality with NUnit

int a = 42;
int b = 42;

Assert.That(a, Is.EqualTo(b)); // pass - values are same, ints are structs with value semantics
Assert.That(a, Is.SameAs(b)); // fail - a and b do not point to the same object in memory

int c = a;

Assert.That(c, Is.EqualTo(a)); // pass - values are same

Asserting on Reference Type Equality with NUnit

By default, 2 instances of a reference type will not pass an equality assert:

class Person
{
    public string Name { get; set; }
}
Person p1 = new Person { Name = "Sarah" };
Person p2 = new Person { Name = "Sarah" };

Assert.That(p1, Is.EqualTo(p2)); // fail, Person is class with reference semantics

Asserting That Two References Point to the Same Object with NUnit

If you want to assert that 2 object references point to the same object you can use the SameAsConstraint:

Assert.That(p1, Is.SameAs(p2)); // fail, p1 and p2 point to different objects in memory Person p3 = p1; Assert.That(p3, Is.SameAs(p1)); // pass, p3 and p1 point to same object in memory Assert.That(p3, Is.Not.SameAs(p2)); // pass, p3 and p2 point to different objects in memory

Customizing Equality Asserts with NUnit

There are a number of ways to influence how NUnit performs equality assertions including implementing IEquatable<T>:

class Employee : IEquatable<Employee>
{
    public string Name { get; set; }

    public bool Equals(Employee other)
    {
        if (other is null)
        {
            return false;
        }

        return Name == other.Name;
    }
}
Employee e1 = new Employee { Name = "Sarah" };
Employee e2 = new Employee { Name = "Sarah" };

Assert.That(e1, Is.EqualTo(e2)); // pass - IEquatable<Employee>.Equals implementation is used

To learn more about NUnit 3 check out my Introduction to .NET Testing with NUnit 3 Pluralsight course to learn everything you need to know to get started, including asserts, categories, data-driven tests, customizing NUnit, and reducing duplicate test code.

SHARE:

NUnit 3 Quick Tips: Asserting Within Ranges

If you are asserting that a value is equal to something and you want to specify some tolerance you can do so.

Specifying a Range for Values with NUnit Asserts (e.g. int)

var i = 42;

Assert.That(i, Is.EqualTo(40)); // fail

Assert.That(i, Is.EqualTo(40).Within(2)); // pass

Assert.That(i, Is.EqualTo(40).Within(1)); // fail "Expected: 40 +/- 1"

Specifying a Range as a Percentage with NUnit Asserts

In addition to specifying a range tolerance as a fixed value you can also specify it as a percentage:

Assert.That(i, Is.EqualTo(40).Within(5).Percent); // pass

Assert.That(i, Is.EqualTo(40).Within(4).Percent); // fail "Expected: 40 +/- 4 Percent"

Specifying a Range for DateTime Objects with NUnit Asserts

When working with DateTimes you can specify the tolerance as a TimeSpan instance:

var newYearsDay2019 = new DateTime(2019, 1, 1);

Assert.That(newYearsDay2019, Is.EqualTo(new DateTime(2019, 1, 2)).Within(TimeSpan.FromDays(1))); // pass

Or instead of using a TimeSpan you can use one of the convenience modifiers:

Assert.That(newYearsDay2019, Is.EqualTo(new DateTime(2019, 1, 2)).Within(1).Days); // pass

Assert.That(newYearsDay2019, Is.EqualTo(new DateTime(2019, 1, 2)).Within(24).Hours); // pass
Assert.That(newYearsDay2019, Is.EqualTo(new DateTime(2019, 1, 2)).Within(23).Hours); // fail

var numberOfMinutesInADay = 24 * 60;
Assert.That(newYearsDay2019, Is.EqualTo(new DateTime(2019, 1, 2)).Within(numberOfMinutesInADay).Minutes); // pass
Assert.That(newYearsDay2019, Is.EqualTo(new DateTime(2019, 1, 2)).Within(numberOfMinutesInADay - 1).Minutes); // fail "Expected: 2019-01-02 00:00:00 +/- 23:59:00"

// Also Within(n).Seconds .Milliseconds and .Ticks
To learn more about NUnit 3 check out my Introduction to .NET Testing with NUnit 3 Pluralsight course to learn everything you need to know to get started, including asserts, categories, data-driven tests, customizing NUnit, and reducing duplicate test code.

SHARE:

Making 2019 Your Best Year Yet

Whilst I’m not personally a fan of New Year’s Resolutions, preferring instead to adopt a mindset of continuous improvement, the end of a calendar year is as good a time as any to do a 12 month retrospective and think about the future.

Below are a number of resources you may find helpful to make 2019 your best year yet, along with some of the things I’m currently learning/implementing in my quest for personal development.

2018 Book List

These are some of the books I read in 2018 that I found helpful and that have influenced my thinking and personal growth:

  • Discipline Equals Freedom: Field Manual (Jocko Willink). Learn to understand that discipline in the present gives you more freedom in the future.
  • The Inner Game of Tennis (W. Timothy Gallwey). More about physical development/performance but the ideas transcend into anything you want to learn.
  • Man's Search for Meaning (Viktor E. Frankl). Harrowing account of Nazi concentration camps infused with what it means to have real meaning in your life.
  • Principles: Life and Work (Ray Dalio). Ray Dalio is the founder of one of the biggest and highest performing hedge funds in the world, this book shows the life and work principles the author has developed and also introduces the fascinating idea that you can run your life based on a set of your own written down principles.
  • Extreme Ownership: How U.S. Navy SEALs Lead and Win (Jocko Willink  & Leif Babin). This should be required reading for anyone managing/leading other people, and for anyone wanting to feel like they are more in control of their life by not making excuses.

Personal Growth Podcasts

My 4 go-to podcasts (in no particular order) for physical, mental, and career growth:

Don’t Code Tired Articles

The following are articles on this blog:

What I’m Working On Implementing At the Moment

These are things I’m trialling, working on implementing, working on improving, or plan to implement in the near future:

  • Regular daily meditation (I’m currently trying the Mindvalley 6 Phase Meditation Quest)
  • Early to bed, early to rise (somewhere between 4:30 AM and 6AM – not sure yet) Effectively swapping some mindless TV/YouTube/XBox gaming at night for productive time in the morning.
  • Learning to deadlift and working on flexibility to allow my to do this
  • Generally: reducing waste, simplification, balanced minimalism in regards to ownership of things, saving/investing more money, reading more, learning more.

Living Your Best Life

I hope the preceding things prove to be useful, inspirational, or interesting to you.

I wish you all the best for the coming year and hope that your 2019 brings you closer to being able to live your best life.

Best wishes, Jason.

SHARE:

Developing Tizen Samsung Galaxy Watch Apps with .NET and C# - Getting Started

This article assumes you have set up the Tizen/Visual Studio development environment as outlined in this previous article.

Installing the Watch Emulator

The first step is to install the relevant emulator so you don’t need a physical Samsung Galaxy Watch. To do this open Visual Studio and click  Tools –> Tizen –> Tizen Emulator Manager

This will bring up the Emulator Manager, click the Create button, then Download new image, check the WEARABLE profile, and click OK. This will open the Package Manager and download the emulator.

Installing the Tizen Wearable emulator in Visual Studio

Once the installation is complete, if you open the Emulator Manager, select Wearable-circle and click Launch you should see the watch emulator load as shown in the following screenshot:

watchemulator

Creating a Watch Project

In Visual Studio, create a new Tizen Wearable Xaml App project  which comes under the Tizen 5.0 section.

Once the project is created and the with the emulator running, click the play button in Visual Studio (this will be something like “W-5.0-circle-x86…” ).

The app will build and be deployed to the emulator – you may have to manually switch back to the emulator if it isn’t brought to the foreground automatically. You should now see the emulator with the text “Welcome to Xamarin.Forms!”.

This text comes from the MainPage.xaml:

<?xml version="1.0" encoding="utf-8" ?>
<c:CirclePage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:c="clr-namespace:Tizen.Wearable.CircularUI.Forms;assembly=Tizen.Wearable.CircularUI.Forms"
             x:Class="TizenWearableXamlApp1.MainPage">
  <c:CirclePage.Content>
    <StackLayout>
      <Label Text="Welcome to Xamarin.Forms!"
          VerticalOptions="CenterAndExpand"
          HorizontalOptions="CenterAndExpand" />
    </StackLayout>
  </c:CirclePage.Content>
</c:CirclePage>

Modifying the Basic Template

As a very simple (and quick and dirty, no databinding, MVVM, etc.) example, the MainPage.xaml can be changed to:

<?xml version="1.0" encoding="utf-8" ?>
<c:CirclePage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:c="clr-namespace:Tizen.Wearable.CircularUI.Forms;assembly=Tizen.Wearable.CircularUI.Forms"
             x:Class="TizenWearableXamlApp1.MainPage">
    <c:CirclePage.Content>
        <StackLayout HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand">
            <Label x:Name="HappyValue" Text="5" HorizontalTextAlignment="Center"></Label>
            <Slider x:Name="HappySlider" Maximum="10" Minimum="1" Value="5" ValueChanged="HappySlider_ValueChanged" ></Slider>
            <Button Text="Go" Clicked="Button_Clicked"></Button>
    </StackLayout>
  </c:CirclePage.Content>
</c:CirclePage>

And the code behind MainPage.xaml.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using Xamarin.Forms;
using Xamarin.Forms.Xaml;
using Tizen.Wearable.CircularUI.Forms;
using System.Net.Http;

namespace TizenWearableXamlApp1
{
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class MainPage : CirclePage
    {
        private int _happyValue = 5;

        public MainPage()
        {
            InitializeComponent();
        }

        private async void Button_Clicked(object sender, EventArgs e)
        {
            HttpClient client = new HttpClient();

            var content = new StringContent($"{{ \"HappyLevel\" : {_happyValue} }}", Encoding.UTF8, "application/json");

            var url = "https://prod-29.australiasoutheast.logic.azure.com:443/workflows/[REST OF URL REDACTED FOR PRIVACY/SECURITY]";

            var result = await client.PostAsync(url, content);
            
        }

        private void HappySlider_ValueChanged(object sender, ValueChangedEventArgs e)
        {
            _happyValue = (int)Math.Round(HappySlider.Value);

            HappyValue.Text = _happyValue.ToString();
        }
    }
}

The preceding code essentially allows the user to specify how happy they are using a slider, and then hit the Go button. This button makes an HTTP POST to a URL, in this example the URL is a Microsoft Flow HTTP request trigger.

The flow is shown in the following screenshot, it essentially takes the JSON data in the HTTP POST, uses the HappyLevel JSON value and sends a mobile notification to the Flow app on my iPhone.

Microsoft Flow triggered from HTTP request

Testing the App

To test the app, run it in Visual Studio:

Xamarin Forms app running in Samsung Galaxy Watch emulator

Tapping the Go button will make the HTTP request and initiate the Microsoft Flow, and after a few moments, the notification being sent to the phone:

Microsoft Flow notification on iPhone

SHARE:

Starting Where You're At

Someone says to you: "you must be doing agile, continuous integration, continuous deployment, automated testing, test driven development, etc. etc. etc. or you're doing it wrong".

For any sufficiently complex application you’re building it's likely that the above things, and more, will be beneficial.

The problem is if you’re not doing any of these things and you feel overwhelmed where do you start?

You start where you're at.

build on each success with subsequent success

I remember watching Ray Mears on TV once and he used the acronym STOP to remember what to do if you get lost or stranded:

  • (S)top
  • (T)hink
  • (O)rient
  • (P)lan

If you feel overwhelmed this may be a useful acronym to help you start where you're at.

Accepting where you are now and starting where you're at can help remove negative feelings and the feeling of being overwhelmed and not being "good enough" at your job.

This of course doesn't mean that you should accept unprofessional practices and not try to improve things, it simply means acknowledging without judgement where you are now and then moving forward to improve things for the future.

You can start with the "big rocks", the more important or foundational things such as making sure you're using adequate source control. Maybe then move to implementing a basic continuous integration build. Maybe then start to add some automated tests, etc. etc.

Do things incrementally and build on each success with subsequent success.

To create the change you desire, you may have to invest in you along the way, develop an understanding that discipline equal freedom, and also ask yourself the question “what would easy be like?”.

SHARE:

Discipline Equals Freedom

One of the books I read this year was Discipline Equals Freedom: Field Manual by Jocko Willink.

The overarching concept in the book is that if you have discipline now, in the present, this will result in greater freedom in the future.

This could be having the discipline to work out/lift weights; in the future this will most likely result in the freedom to move more with less pain/lift heavy things/go trekking/kayaking on holiday, etc.

This could be having the financial discipline to regularly save/invest money rather that spend everything you earn; in the future this will most likely result in having the freedom to (semi)retire early, not have to work in a job you don't like, etc.

I think the reason this book resonated with me personally is that it helped focus the outlook I already had on investing in the future/thinking longer term.

This concept can also be applied to software development. Disciplined software development now, will most probably lead to greater freedom in the future to make changes or add new features.

As an example, having the discipline to create automated tests can help give you the freedom in the future to make changes without having to perform a load of manual testing or worry about what may have broken unknowingly.

Another example: having the discipline to refactor code as you are working on the current feature/bug to keep the code as clean as possible will most probably give you the freedom to change it more easily in the future.

If you are a manager: having the discipline to allocate time for you team to train/learn/get better even when faced with pressure to deliver will most probably result in the freedom to deliver more in the future.

The concept can also be applied to non-coding practices such as having the discipline to hold a stand up meeting every day, engage business/users/stakeholders regularly, etc.

You can also flip this concept on its head and instead ask: in the future, what aspects would I like more freedom in: in one month, in one year, in 10 years? Let the answers to this question guide you when deciding on what it is you need to be more disciplined on today or this week to make manifest those desired freedoms in the future.

SHARE: