Xamarin.Forms – Implementing ItemsSource for Custom Control

Here’s some code for ItemsSource:

        public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create(
           nameof(ItemsSource),
           typeof(IEnumerable),
           typeof(MyControl),
           null,
           BindingMode.OneWay,
           propertyChanged: (bindable, oldValue, newValue) => OnItemsSourceChanged(bindable, oldValue, newValue));

        static void OnItemsSourceChanged(BindableObject bindable, object oldValue, object newValue)
        {
            void newValueINotifyCollectionChanged_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
            {
                var wraplayout = bindable as WrapLayout;

                if (e.OldItems != null)
                    foreach (var item in e.OldItems)
                        wraplayout.Children.Remove(item as View);

                if (e.NewItems != null)
                    foreach (var item in e.NewItems)
                        wraplayout.Children.Add(item as View);
            }

            var oldValueINotifyCollectionChanged = oldValue as INotifyCollectionChanged;

            if (null != oldValueINotifyCollectionChanged)
            {
                oldValueINotifyCollectionChanged.CollectionChanged -= newValueINotifyCollectionChanged_CollectionChanged;
            }

            var newValueINotifyCollectionChanged = newValue as INotifyCollectionChanged;

            if (null != newValueINotifyCollectionChanged)
            {
                newValueINotifyCollectionChanged.CollectionChanged += newValueINotifyCollectionChanged_CollectionChanged;
            }
        }
        
        public IEnumerable ItemsSource
        {
            get { return (IEnumerable)GetValue(ItemsSourceProperty); }
            set { SetValue(ItemsSourceProperty, value); }
        }
Advertisements

Xamarin.Forms Responsive Layout from Device Rotation

Intro

This is a brain dump on implementing a responsive layout based on a device being in Portrait or Landscape mode.

In summary, I used a ContentView’s ControlTemplate to host a layout that depends on a device being in Portrait or Landscape mode. In addition, I then overrode the OnSizeAllocated method on the ContentPage.

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:XForms"
             x:Class="XForms.RegistrationPage1">

    <ContentPage.Resources>
        <ResourceDictionary>
            <ControlTemplate x:Key="PortraitTemplate">
                <StackLayout>
                    <StackLayout VerticalOptions="CenterAndExpand">
                        <Entry Style="{StaticResource RegistrationEntry}" Text="{Binding Email}"    Placeholder="email"  />
                        <Entry Style="{StaticResource RegistrationEntry}" Text="{Binding Password}" Placeholder="password" IsPassword="True" Margin="0,15,0,0"  />
                    </StackLayout>

                    <Button Style="{StaticResource RegistrationButton}" Text="Next" FontAttributes="Bold" FontSize="Small" VerticalOptions="Fill" />
                </StackLayout>
            </ControlTemplate>

            <ControlTemplate x:Key="LandscapeTemplate">
                <Label Text="Landscape" />
            </ControlTemplate>
        </ResourceDictionary>
    </ContentPage.Resources>

    <ContentView ControlTemplate="{StaticResource PortraitTemplate}" />

</ContentPage>

Code Behind

Override OnSizeAllocated method of a ContentPage and compare page width to height:

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

        protected override void OnSizeAllocated(double width, double height)
        {
            base.OnSizeAllocated(width, height);

            ControlTemplate = GetTemplate(width, height);
        }

        ControlTemplate GetTemplate(double width, double height) =>
            (height > width) ? TemplateFrom("PortraitTemplate") : TemplateFrom("LandscapeTemplate");

        ControlTemplate TemplateFrom(string templateName) =>
            Resources[templateName] as ControlTemplate;
    }

Conclusion

Brain dump completed.

Building a Trading App (Xamarin.Forms & F#)

Intro

This is my fourth post on my journey of building a trading app using F# and Xamarin.Forms. In my last post, I brain-dumped XAML and F# for selling shares of stock. In this post, I will brain-dump the syntax for buying shares of stock for both the UI and viewmodels.

Solution Explorer

I modeled my solution explorer to reflect my interpretation of Clean Architecture:

UI

The user-experience for buying shares is the following:

XAML

Here’s the XAML for the Buy page:

<?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:manageTrades="clr-namespace:ManageTrades.ViewModels;assembly=ManageTrades"              x:Class="FnTrade.BuyPage">

    <Grid Margin="10,5">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
            <RowDefinition Height="auto" />
        </Grid.RowDefinitions>

        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>

        <Label Grid.Row="0" Grid.Column="0" Text="{Binding Symbol,     StringFormat='Buying ({0:F0})'}}" FontSize="Large" />
        <Label Grid.Row="2" Grid.Column="0" Text="{Binding StockPrice, StringFormat='{}{0:c} per share'}}"        FontSize="Medium" HorizontalTextAlignment="Start" HorizontalOptions="Start" />
        <Label Grid.Row="3" Grid.Column="0" Text="{Binding Total,      StringFormat='{}{0:c} Invested'}}" FontSize="Medium" HorizontalTextAlignment="Start" HorizontalOptions="Start" />
        <Label Grid.Row="4" Grid.Column="1" Text="{Binding Balance,    StringFormat='{}{0:c} balance'}}" FontSize="Large" HorizontalOptions="Start" />

        <Entry Grid.Row="6" Grid.Column="0" Grid.ColumnSpan="2" Text="{Binding BuyQty, Mode=TwoWay}"                 Placeholder="Enter number of shares to buy" HorizontalTextAlignment="Center" Keyboard="Numeric" />

        <Label Grid.Row="7" Grid.Column="1" Text="{Binding BuyValue, StringFormat='{}{0:c}'}" HorizontalTextAlignment="End" />

        <Button Grid.Row="9" Grid.Column="0" Grid.ColumnSpan="2" Text="Confirm"                  Command="{Binding Confirm}" IsEnabled="{Binding CanBuy}" />
    </Grid>

</ContentPage>

The code-behind of the Buy page subscribes to a confirmation-request event:

using ManageTrades.ViewModels;
using Xamarin.Forms;

namespace FnTrade
{
    using static Core.Entities;
    using static Integration.Factories;

    public partial class BuyPage : ContentPage
    {
        public BuyPage(BuyViewModel viewModel)
        {
            InitializeComponent();
            BindingContext = viewModel;

            getDispatcher().ConfirmBuyRequested += async (s, e) =>
                {
                    var requestInfo = e as RequestInfo;
                    var confirmed = await DisplayAlert("Confirmation",
                                          $"Buying ({(requestInfo).Quantity }) shares of {requestInfo.Symbol}?",
                                          "Confirm", "Cancel");
                    if (confirmed) getDispatcher().ExecuteBuy(requestInfo);
                };
        }
    }
}

When the purchase of shares is confirmed, the app presents a purchase request confirmation.

The following is the XAML for the purchase shares request confirmation:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"              xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"              x:Class="FnTrade.RequestBuyConfirmedPage">

    <Grid Margin="10,5">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>

        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>

        <Label Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Text="Request Sent"                 FontSize="Large" TextColor="Green" FontAttributes="Bold" HorizontalOptions="Center" />

        <StackLayout Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" VerticalOptions="Center">
            <StackLayout  Orientation="Horizontal" VerticalOptions="Center">
                <Label Text="{Binding Quantity, StringFormat='Buy ({0:F0}) shares of'}}"                         FontSize="Medium" HorizontalOptions="Center" HorizontalTextAlignment="Center" />

                <Label Text="{Binding Symbol}"                        FontSize="Medium" HorizontalOptions="Start" />
            </StackLayout>

            <Label Grid.Row="2" Grid.Column="1" Text="{Binding Total, StringFormat='{}{0:c}'}}"                    FontSize="24" FontAttributes="Bold" HorizontalOptions="End" />
        </StackLayout>
    </Grid>
</ContentPage>

ViewModels

Here’s the viewmodel for buying shares:

namespace ManageTrades.ViewModels

open System
open System.Windows.Input
open Core.IntegrationLogic
open Core.Entities
open Integration.Factories
open Services

type BuyViewModel(info:SharesInfo) as this =

    inherit ViewModelBase()

    let dispatcher = getDispatcher()
    let accountId =  getAccountId()
    let broker =     getBroker() :> IBroker

    let mutable buyQty = ""
    let mutable canBuy = false
    let mutable buyValue = 0m

    let confirm = DelegateCommand( (fun _ -> this.ConfirmBuy()) ,
                                    fun _ -> true ) :> ICommand

    member this.Symbol     with get() = info.Shares.Symbol
    member this.Balance    with get() = info.Balance
    member this.StockPrice with get() = info.PricePerShare
    member this.Total      with get() = ((decimal)info.Shares.Qty * info.PricePerShare)

    member this.BuyQty
        with get() =      buyQty
        and  set(value) = buyQty <- value
                          let success , validQty = Int32.TryParse buyQty
                          if  success then this.CanBuy <- info.PricePerShare
                                                          *(decimal)validQty  <= this.Balance
                          else this.CanBuy <- false
                          this.UpdateBuyValue()
                          base.NotifyPropertyChanged(<@ this.BuyQty @>)
    member this.BuyValue
        with get() =      buyValue
        and  set(value) = buyValue <- value
                          base.NotifyPropertyChanged(<@ this.BuyValue @>)

    member this.CanBuy   with get() =      canBuy
                         and  set(value) = canBuy <- value
                                           base.NotifyPropertyChanged(<@ this.CanBuy @>)
    member this.Confirm = confirm

    member private this.ConfirmBuy() =
        if this.CanBuy then
            dispatcher.ConfirmBuy { AccountId = accountId
                                    Symbol    = this.Symbol
                                    Quantity  = Int32.Parse this.BuyQty }

    member private this.UpdateBuyValue() =
        let success , validQty = Int32.TryParse buyQty
        if  success
        then this.BuyValue <- (decimal)validQty * info.PricePerShare
        else this.BuyValue <- 0m

Here’s the viewmodel for the purchase request confirmation:

namespace ManageTrades.ViewModels

open Core.IntegrationLogic
open Core.Entities
open Services
open Integration.Factories

type RequestBuyConfirmedViewModel(request:RequestInfo) =

    inherit ViewModelBase()

    let broker = getBroker() :> IBroker
    let mutable total = 0m

    member this.Symbol   with get() = request.Symbol
    member this.Quantity with get() = request.Quantity
    member this.Total    with get() =      total
                         and  set(value) = total <- value
                                           base.NotifyPropertyChanged(<@ this.Total @>)
    member this.Load() =
        match broker.GetInfo request.Symbol with
        | Some info -> this.Total <- info.Price * (decimal) request.Quantity         | None      -> failwith (sprintf "Failed to retrieve stock information for %s" this.Symbol)

Domain Types

The following module reflects the entities that will be used in my trading app:

module Core.Entities

type StockInfo = {
    Symbol  : string
    Price   : decimal
    DayLow  : decimal
    DayHigh : decimal
}

type Shares = {
    AccountId : string
    Symbol    : string
    Qty       : int
}

type SharesInfo ={
    Shares        : Shares
    PricePerShare : decimal
    Total         : decimal
    Balance       : decimal
}

type Owner = { AccountId:string ; Symbol:string }

type RequestInfo = {
    AccountId : string
    Symbol    : string
    Quantity  : int
}

type InsufficientFunds = {
    PurchaseAttempt : RequestInfo
    Balance         : decimal
    StockPrice      : decimal
}    

type PurchaseResult =
    | PurchaseRequested of RequestInfo
    | UnknownSymbol     of RequestInfo
    | InvalidQuantity   of RequestInfo
    | InsufficientFunds of InsufficientFunds

type SellResult =
    | SellRequested        of RequestInfo
    | InsufficientQuantity of RequestInfo

Conclusion

The source code for this app can be found here on GitHub.

Building a Trading App (Xamarin.Forms & F#)

Intro

This post is the third post within my series of posts regarding the construction of a trading application using Xamarin.Forms and F#. In this post, I share my source code for selling shares of a stock.

User Interface

The following reflects the user-experience for selling a stock:

The following XAML reflects the above 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:manageTrades="clr-namespace:ManageTrades.ViewModels;assembly=ManageTrades"              x:Class="FnTrade.SellPage">

    <Grid Margin="10,5">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
            <RowDefinition Height="auto" />
        </Grid.RowDefinitions>

        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>

        <Label Grid.Row="0" Grid.Column="0" Text="{Binding Symbol,     StringFormat='Selling ({0:F0})'}}" FontSize="Large" />
        <Label Grid.Row="2" Grid.Column="0" Text="{Binding Shares,     StringFormat='{0:F0} shares available'}}" FontSize="Medium" HorizontalTextAlignment="Start" HorizontalOptions="Start" />
        <Label Grid.Row="3" Grid.Column="0" Text="{Binding StockPrice, StringFormat='{}{0:c} per share'}}"       FontSize="Medium" HorizontalTextAlignment="Start" HorizontalOptions="Start" />
        <Label Grid.Row="4" Grid.Column="1" Text="{Binding Total,      StringFormat='{}{0:c} Invested'}}" FontSize="Large" HorizontalOptions="Start" />

        <Entry Grid.Row="6" Grid.Column="0" Grid.ColumnSpan="2" Text="{Binding SellQty, Mode=TwoWay}"                 Placeholder="Enter number of shares to sell" HorizontalTextAlignment="Center" Keyboard="Numeric" />

        <Label Grid.Row="7" Grid.Column="1" Text="{Binding SellValue, StringFormat='{}{0:c}'}"                 HorizontalTextAlignment="End" />

        <Button Grid.Row="9" Grid.Column="0" Grid.ColumnSpan="2" Text="Confirm"                  Command="{Binding Confirm}" IsEnabled="{Binding CanSell}" />
    </Grid>

</ContentPage>

Here’s some more UI in regards to the user story of selling shares:

ViewModels

The HomeViewmodel was implemented as follows:

namespace Home.ViewModels

open System.Windows.Input
open Core.IntegrationLogic
open Core.Entities
open Integration.Factories
open Services
open Search
open System.Collections.ObjectModel

type HomeViewModel() as this =

    inherit ViewModelBase()

    let dispatcher = getDispatcher()
    let accountId =  getAccountId()
    let broker =     getBroker() :> IBroker

    let searchCommand =
        DelegateCommand( (fun _ -> this.StockInfo <- getQuote broker this.Symbol) ,                           fun _ -> true) :> ICommand
    let sellCommand =
        DelegateCommand( (fun o -> dispatcher.Sell (o :?> SharesInfo) ) ,
                          fun _ -> true) :> ICommand
    let buyCommand =
        DelegateCommand( (fun o -> dispatcher.Buy  { Owner.AccountId=accountId ; Symbol=o :?> string} ) ,
                          fun _ -> true) :> ICommand

    let mutable symbol = ""
    let mutable stockInfo = None
    let mutable investments = ObservableCollection<SharesInfo>()

    member this.Load() =
        let result = broker.InvestmentsOf accountId
        this.Investments <- ObservableCollection<SharesInfo>(result)

    member this.Symbol
        with get() =      symbol
        and  set(value) = symbol <- value
                          base.NotifyPropertyChanged(<@ this.Symbol @>)
    member this.StockInfo
        with get() =      stockInfo
        and  set(value) = stockInfo <- value
                          base.NotifyPropertyChanged(<@ this.StockInfo @>)
    member this.Investments
        with get() =      investments
        and  set(value) = investments <- value
                          base.NotifyPropertyChanged(<@ this.Investments @>)

    member this.Search = searchCommand
    member this.Sell =   sellCommand
    member this.Buy =    buyCommand

The SellViewModel was implemented as follows:

namespace ManageTrades.ViewModels

open System
open System.Windows.Input
open Core.IntegrationLogic
open Core.Entities
open Integration.Factories
open Services

type SellViewModel(info:SharesInfo) as this =

    inherit ViewModelBase()

    let dispatcher = getDispatcher()
    let accountId =  getAccountId()
    let broker =     getBroker() :> IBroker

    let mutable sellQty = ""
    let mutable canSell = false
    let mutable sellValue = 0m

    let confirm = DelegateCommand( (fun _ -> this.ConfirmSell()) ,
                                    fun _ -> true ) :> ICommand

    member this.Symbol     with get() = info.Shares.Symbol
    member this.Shares     with get() = info.Shares.Qty
    member this.StockPrice with get() = info.PricePerShare
    member this.Total      with get() = ((decimal)info.Shares.Qty * info.PricePerShare)

    member this.SellQty
        with get() =      sellQty
        and  set(value) = sellQty <- value
                          let success , validQty = Int32.TryParse sellQty
                          if  success then this.CanSell <- validQty >  0 &&
                                                           validQty <= this.Shares
                          else this.CanSell <- false
                          this.UpdateSellValue()
                          base.NotifyPropertyChanged(<@ this.SellQty @>)
    member this.SellValue
        with get() =     sellValue
        and set(value) = sellValue <- value
                         base.NotifyPropertyChanged(<@ this.SellValue @>)

    member this.CanSell   with get() =      canSell
                          and  set(value) = canSell <- value
                                            base.NotifyPropertyChanged(<@ this.CanSell @>)
    member this.Confirm = confirm

    member private this.ConfirmSell() =
        if this.CanSell then
            dispatcher.ConfirmSell { AccountId = accountId
                                     Symbol    = this.Symbol
                                     Quantity  = Int32.Parse this.SellQty }

    member private this.UpdateSellValue() =
        let success , validQty = Int32.TryParse sellQty
        if  success
        then this.SellValue <- (decimal)validQty * info.PricePerShare
        else this.SellValue <- 0m

The RequestSellConfirmedViewModel is as follows:

namespace ManageTrades.ViewModels

open Core.IntegrationLogic
open Core.Entities
open Services
open Integration.Factories

type RequestSellConfirmedViewModel(request:RequestInfo) =

    inherit ViewModelBase()

    let broker = getBroker() :> IBroker
    let mutable total = 0m

    member this.Symbol   with get() = request.Symbol
    member this.Quantity with get() = request.Quantity
    member this.Total    with get() =      total
                         and  set(value) = total <- value
                                           base.NotifyPropertyChanged(<@ this.Total @>)
    member this.Load() =
        match broker.GetInfo request.Symbol with
        | Some info -> this.Total <- info.Price * (decimal) request.Quantity
        | None      -> failwith (sprintf "Failed to retrieve stock information for %s" this.Symbol)

Application Logic

My app subscribes to events so that it can manage page navigation:

    public partial class App : Application
    {
        public App()
        {
            InitializeComponent();

            MainPage = new NavigationPage(new HomePage());
        }

        protected override void OnStart()
        {
            getDispatcher().SellRequested +=
                async (s, e) => await MainPage.Navigation.PushAsync(
                    new SellPage(new SellViewModel(e as SharesInfo)));

            getDispatcher().ExecuteSellRequested +=
                async (s, e) => await MainPage.Navigation.PushAsync(
                    new RequestSellConfirmedPage(new RequestSellConfirmedViewModel(e as RequestInfo)));
        }

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

The following are events that are declared within my Dispatcher class:

namespace Services

open System.Diagnostics
open System

type Dispatcher() =

    let sellRequested = new Event<EventHandler<_>,_>()
    let buyRequested =  new Event<EventHandler<_>,_>()
    let confirmSellRequested = new Event<EventHandler<_>,_>()
    let executeSellRequested = new Event<EventHandler<_>,_>()

    [<CLIEvent>]
    member this.SellRequested = sellRequested.Publish
    member this.Sell owner = sellRequested.Trigger(this , owner)

    [<CLIEvent>]
    member this.BuyRequested = buyRequested.Publish
    member this.Buy owner = buyRequested.Trigger(this , owner)

    [<CLIEvent>]
    member this.ConfirmSellRequested = confirmSellRequested.Publish
    member this.ConfirmSell info = confirmSellRequested.Trigger(this , info)

    [<CLIEvent>]
    member this.ExecuteSellRequested = executeSellRequested.Publish
    member this.ExecuteSell info = executeSellRequested.Trigger(this , info)

Domain Types

The following are my domain types:

module Core.Entities

type StockInfo = {
    Symbol  : string
    Price   : decimal
    DayLow  : decimal
    DayHigh : decimal
}

type Shares = {
    AccountId : string
    Symbol    : string
    Qty       : int
}

type SharesInfo ={
    Shares        : Shares
    PricePerShare : decimal
    Total         : decimal
}

type Owner = { AccountId:string ; Symbol:string }

type RequestInfo = {
    AccountId : string
    Symbol    : string
    Quantity  : int
}

type InsufficientFunds = {
    PurchaseAttempt : RequestInfo
    Balance         : decimal
    StockPrice      : decimal
}    

type PurchaseResult =
    | PurchaseRequested of RequestInfo
    | UnknownSymbol     of RequestInfo
    | InvalidQuantity   of RequestInfo
    | InsufficientFunds of InsufficientFunds

type SellResult =
    | SellRequested        of RequestInfo
    | InsufficientQuantity of RequestInfo

Conclusion

The source code to my implementation can be found on GitHub.