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.
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:
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:
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.
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:
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