Thursday, 17 May 2018

Simple App Data

IOS, Droid and UWP all have their own Key-Value paired dictionaries for storing simple data; generally application preferences, but the dictionary can be used to store application data if it's simple enough.
  • IOS: CFPreferences
  • Droid: Preferences
  • UWP: Application Data
Luckily there's also a Xamarin cross platform Nugget solution "Xam.Plugins.settings", install this package into our Portable Class Library (PCL), to leverage cross platform

PM> Install-Package Xam.Plugins.Settings

this Xamarin package abstracts the three platform specific solutions into a write once, use everywhere approach
  • IOS:NSUserDefaults
  • Droid: SharedPreferences
  • UWP: IsoltatedStorageSettings/ApplicationDataContainer
This does limit us to our basic types with a string key
  • Boolean
  • Int32
  • Int64
  • Float
  • Double
  • Decimal
  • Guid
  • String
  • DateTime
Let's take a look this simple UI

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:pav02.SimpleData"
             x:Class="pav02.SimpleData.MainPage">

    <StackLayout>
        <Entry x:Name="FullName_ENTRY" Placeholder="full name"/>
        <Button Text="Save" Clicked="Button_Clicked" />
    </StackLayout>
</ContentPage>


With the corresponding CodeBehind

using Plugin.Settings;
using System;
using Xamarin.Forms;

namespace pav02.SimpleData
{
    public partial class MainPage : ContentPage
    {
        public MainPage()
        {
            InitializeComponent();
            var fullName = CrossSettings.Current.GetValueOrDefault("fullName", "");
            FullName_ENTRY.Text = fullName;
        }

        private void Button_Clicked(object sender, EventArgs e)
        {
            var fullName = FullName_ENTRY.Text;
            CrossSettings.Current.AddOrUpdateValue("fullName", fullName);
        }
    }
}


We simply store our value in the CorssSettings.Current singleton instance of our unified application settings solution. when saving or retrieving data this way under the hood each platform calls their corresponding Settings solution.

Wednesday, 16 May 2018

Cross platform connectivity

For an application that requires data connectivity we must check if we are connect, keep track to see if our connectivity changes and maybe even identify if we are on a metered network. to accomplish all this we are going to leverage the "Xam.Plugin.Connectivity" nugget package.

once we have our nugget package we also have to ensure that our platforms request the adequate permissions.

  • Andriod
    • ACCESS_NETWORK_STATE
    • ACCESS_WIFI_STATE
  • Windows 10 UWP
    • Internet (Client)
    • Private Networks (Client & Server)
    • Internet (Client & Server)

To check if we have connectivity we can use the IsConnected property of the CrossConectivity singleton Current.

using System;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
using NetStatus.Views;
using Plugin.Connectivity;

[assembly: XamlCompilation (XamlCompilationOptions.Compile)]
namespace NetStatus 
{
    public partial class App : Application 
    {
        public App () 
        {
            InitializeComponent();
            var isConnected = CrossConnectivity.Current.IsConnected;
            MainPage = isConnected ? (Page)new NetworkViewPage() : new NoNetworkPage();
        }

        protected override void OnStart (){}
        protected override void OnSleep (){}
        protected override void OnResume (){}
    }
}

we created two pages the NetworkViewPage and the NoNetworkPage, their names are pretty self explanatory.

Next we need to handle a change in connectivity to do this we'll update OnStart Method

protected override void OnStart()
{
    CrossConnectivity.Current.ConnectivityChanged += (s, e) =>
    {
        Type currentPage = this.MainPage.GetType();
        if (e.IsConnected && currentPage != typeof(NetworkViewPage))
            this.MainPage = new NetworkViewPage();
        else if (!e.IsConnected && currentPage != typeof(NoNetworkPage))
            this.MainPage = new NoNetworkPage();
    };

}

what we did was created an event handler for when the connectivity changes, so that if we run our application and toggle the internet on and off we'll flip between our NetworkViewPage and our NoNetworkPage.

next let's look at our NetworkViewPage; here we'll update what type of connectivity we have should we switch betwen 3G, 4G or wifi

using Plugin.Connectivity;
using System.Linq; 
using Xamarin.Forms;
using Xamarin.Forms.Xaml;

namespace NetStatus.Views {
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class NetworkViewPage : ContentPage {
public NetworkViewPage ()=>InitializeComponent ();
             
protected override void OnAppearing()
{
    base.OnAppearing();
    var connectionType = CrossConnectivity.Current.ConnectionTypes.First().ToString();

    ConnectionDetails_Label.Text = connectionType;

    CrossConnectivity.Current.ConnectivityChanged += (s, e) => {
        if (CrossConnectivity.Current.ConnectionTypes != null)
        {
            var connectionType = CrossConnectivity.Current.ConnectionTypes.FirstOrDefault();
            ConnectionDetails_Label.Text = connectionType.ToString();
        }
    };
}}}

the above code will let us know on page load what type of connectivity we are using and will update should it change.

Platform specific

No let's say that we are writing a Xamarin forms app and for some bizarre reason we want our application to have a different background color in our different OS's, let's say for the sake of argument that we wanted:

  • Android background to be Black
  • IOS background to be White
  • UWP background to be DarkGrey

well we could set those values directly in our xaml


<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:pav01.OsSpecific"
             x:Class="pav01.OsSpecific.MainPage">

    <ContentPage.BackgroundColor>
        <OnPlatform x:TypeArguments="Color">
            <On Platform="Android">Black</On>
            <On Platform="IOS">White</On>
            <On Platform="UWP">DarkGray</On>
        </OnPlatform>
    </ContentPage.BackgroundColor>
   
    <StackLayout>
        <Label Text="Welcome to Xamarin.Forms!" 
               HorizontalOptions="Center"
               VerticalOptions="CenterAndExpand" />     
    </StackLayout>
</ContentPage>


and that's it, our apps background color is now different based on the Platform we are deploying our app to.

there's also a non declarative way to execute platform specific logic using pre-processor directives

public static string GetPlatformName() { string result = "notset"; #if __ANDROID__ result = "Droid"; #elif __IOS__ result = "IOS"; #elif WINDOWS_UWP result = "UWP"; #endif return result; }

these can be leveraged inside of Shared projects to compile separate code for different
Symbol What it represents
#if __MOBILE__ Any mobile project (vs. desktop)
#if __ANDROID__ Xamarin.Android - defined by the compiler
#if __IOS__ Xamarin.iOS - defined by the compiler
#if __MAC__ Xamarin.Mac - defined by the compiler
#if __TVOS__ iOS tvOS - defined by the compiler
#if __WATCHOS__ iOS watchOS - defined by the compiler
#if WINDOWS_UWP Windows 10 UWP - defined in build settings
platforms

Dynamic Resource

If you want to update a xaml resource at run time, this can be done using a {DynamicResouce} binding. take a look at our view

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:pav00.DynamicResouceExample"
             x:Class="pav00.DynamicResouceExample.MainPage">

    <ContentPage.Resources>
        <ResourceDictionary>
            <Color x:Key="HighlightColor">Red</Color>
        </ResourceDictionary>
    </ContentPage.Resources>

    <StackLayout>
        <Label Text="Welcome to Xamarin.Forms!" 
               BackgroundColor="{DynamicResource HighlightColor}"
               HorizontalOptions="Center"
               VerticalOptions="CenterAndExpand" />

        <Button x:Name="Red_Button" Clicked="Color_Toggled" Text="Red"
                TextColor="{DynamicResource HighlightColor}" />
        <Button x:Name="Green_Button" Clicked="Color_Toggled" Text="Green" />
        <Button x:Name="Blue_Button" Clicked="Color_Toggled" Text="Blue"  />
    </StackLayout>
</ContentPage>


pretty straight forward, notice that we create a normal resource dictionary with one value for Color in it, we then do a {DynamicResource} binding instead of a {StaticResource} binding, this is what will allow us to then update the value of our HighlighteColor resource from code.

public partial class MainPage : ContentPage
{
    public MainPage() => InitializeComponent();

    private void Color_Toggled(object sender, ToggledEventArgs e)
    {
        var button = sender as Button;
        var buttons = new[] { Red_Button, Green_Button, Blue_Button };
        switch (button.Text.ToLower())
        {
            case "red":
                this.Resources["HighlightColor"] = new Color(255, 0, 0);
                break;
            case "green":
                this.Resources["HighlightColor"] = new Color(0, 255, 0);
                break;
            case "blue":
                this.Resources["HighlightColor"] = new Color(0, 0, 255);
                break;
        }

        foreach (var btn in buttons)
        {
            btn.TextColor = Color.Default;
            btn.RemoveDynamicResource(Button.TextColorProperty);
        }
        button.SetDynamicResource(Button.TextColorProperty, "HighlightColor");
    }

}

and that's all that there's to it,

  • we can update the resource dictionary by simply referring to the item inside it by the key and assigning it a different value.
  • we can remove a reference to the resource using the RemoveDynamicResouce attached property 
  • we can set a resource using the SetDynamicResouce attached property.


Friday, 1 December 2017

Mock IAwaitable

When ever you are unit testing chatbots in the botnet framework it's common place to wait for some feedback from the user.

public async Task StartAsync(IDialogContext context)
{
    //display feedback card
    var feedbackCard = base.GetFeedbackMessage(context.MakeMessage(), Contact);
    await context.PostAsync(feedbackCard);

    //wait for feedback
    context.Wait(GetFeedbackAsync);

}

then your GetFeedbackAsync method would look something like

internal async Task GetFeedbackAsync(IDialogContext context, IAwaitable<object> result)
{
    // Stuff to do
    context.Done(null);

}

now the problem is in the IAwaitable<object>, this has to be mocked

private Mock<IAwaitable<IMessageActivity>> GetMoqAwaitableMessageActivity(string textMessage)
{
    var moqMessage = new Mock<IMessageActivity>(MockBehavior.Loose);
    moqMessage.Setup(x => x.Text)
        .Returns(textMessage);

    var moqAwaiter = new Mock<IAwaiter<IMessageActivity>>(MockBehavior.Loose);
    moqAwaiter.Setup(x => x.GetResult())
        .Returns(() => moqMessage.Object);
    moqAwaiter.Setup(x => x.IsCompleted)
        .Returns(true);
           
    var moqAwaitable = new Mock<IAwaitable<IMessageActivity>>();
    moqAwaitable.Setup(x => x.GetAwaiter())
        .Returns(() => moqAwaiter.Object);

    return moqAwaitable;

}

Without getting to in depth into how to implement the async/await syntactical sugar, before mocking IAwaitable we also need to mock IAwaiter and ensure that the IsCompleted property returns true.

now we can test our GetFeedbackAsync method

[TestMethod]
public async Task Dialog_Feedback_Text_Jibberish()
{
    //arange
    var moqDialogContext = GetMoqDialogContext();
    var moqAwaitableMessage = GetMoqAwaitableMessageActivity("bla bla bla");
    var feedbackDialog = new FeedbackDialog();

   //act
    await feedbackDialog.GetFeedbackAsync(moqDialogContext.Object, moqAwaitableMessage.Object);

   //assert
    moqDialogContext.Verify(x => x.PostAsync(It.IsAny<IMessageActivity>(), default(CancellationToken)), Times.Exactly(2));
    moqDialogContext.Verify(x => x.Wait(It.IsAny<ResumeAfter<IMessageActivity>>()), Times.Once);
}


Obviously your assert section will vary, but by mocking the context and the iawaitable we can achieve some degree of unit testing. 

Oh and if you don't know i'm using the Moq nuget package.