Archive

Android

Intro

In the previous post, I discussed my continued journey of learning by building a Xamarin.Forms app using F#.

In that post, I discussed the overall architecture:

SolutionExplorer.png

The architecture above is my attempt at practicing Robert C. Martin’s Clean Architecture. Specifically I make the features of the app the focus of the architecture and I treat everything else as details that are abstracted away via folders.

I treat the integration points of an application as details:

Integration.png

The Integration folder above harbors the modules that integrate with my domain. Such modules would be the application, services, and runtime dependencies.

Types

I began code construction of the application by writing functions to support trade transactions.

The following functions were implemented:

module ManageTradeImpl

open Services
open Core.Entities
open TestAPI

let buyShares (service:IBroker) context balance =
    (context , balance) ||> service.TryPurchase

let sellShares (service:IBroker) context =
    context |> service.TrySell

Amongst, the various types that were flushed out as I wrote functions, I wrote a Broker interface that will eventually rely on IO.

The broker interface was added to my Services library:

services

The Broker Interface is the following:

namespace Services

open Core.Entities

type IBroker =

    abstract member GetInfo       : string -> StockInfo option
    abstract member TryPurchase   : Shares -> decimal -> PurchaseResult
    abstract member TrySell       : Shares -> SellResult
    abstract member InvestmentsOf : string -> SharesWithPrice seq

Here’s how the buyShares function can get executed:

(*Test*)
let context = { AccountId= "Bizmonger"
                Symbol=    "ROK"
                Qty=        100 }

let result = buyShares (MockBroker()) context 5000m

Here’s the result:

val result : PurchaseResult =
  InsufficientFunds {PurchaseAttempt = {AccountId = "Bizmonger";
                                        Symbol = "ROK";
                                        Quantity = 100;};
                     Balance = 5000M;
                     StockPrice = 300M;}

Here’s another example for the buyShares function:

(*Test*)
let context = { AccountId= "Bizmonger"
                Symbol=    "ROK"
                Qty=        5 }

let result = buyShares (MockBroker()) context 5000m

Here’s the result:

val result : PurchaseResult = PurchaseRequested {AccountId = "Bizmonger";
                                                 Symbol = "ROK";
                                                 Quantity = 5;}

The following types have been defined so far for making trades:

module Core.Entities

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

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

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

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

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

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

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

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

type SellResult =
    | SellRequested        of SellInfo
    | InsufficientQuantity of SellInfo

User Interface

A domain is useless if there’s no interaction with it.

Here’s a sample UI that was built in XAML:

Emuator.png

Here’s the XAML that resulted in 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:converters="clr-namespace:FnTrade.Converters;assembly=FnTrade"              xmlns:home="clr-namespace:Home.ViewModels;assembly=Home"              x:Class="FnTrade.HomePage">

    <ContentPage.BindingContext>
        <home:HomeViewModel />
    </ContentPage.BindingContext>

    <ContentPage.Resources>
        <ResourceDictionary>
            <converters:InstanceToBoolConverter x:Key="InstanceToBoolConverter" />
        </ResourceDictionary>
    </ContentPage.Resources>

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>

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

        <ListView x:Name="SharesListView" Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2"                    ItemsSource="{Binding Investments}"                   HorizontalOptions="Center" VerticalOptions="Center">
            <ListView.Header>
                <Grid >
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto" />
                        <RowDefinition Height="Auto" />
                    </Grid.RowDefinitions>

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

                    <Label Grid.Row="0" Grid.Column="0" Text="Symbol" FontAttributes="Bold" HorizontalTextAlignment="Center" />
                    <Label Grid.Row="0" Grid.Column="1" Text="Qty"    FontAttributes="Bold" HorizontalTextAlignment="Center" />
                    <Label Grid.Row="0" Grid.Column="2" Text="Price"  FontAttributes="Bold" HorizontalTextAlignment="Center" />
                    <Label Grid.Row="0" Grid.Column="3" Text="Total"  FontAttributes="Bold" HorizontalTextAlignment="Center" />

                    <BoxView Grid.Row="1" Grid.ColumnSpan="4" Color="Gray" Margin="5,0,5,5"                              HorizontalOptions="FillAndExpand" HeightRequest="1" />
                </Grid>
            </ListView.Header>
            <ListView.ItemTemplate>
                <DataTemplate>
                    <ViewCell>
                        <Grid>
                            <Grid.RowDefinitions>
                                <RowDefinition Height="Auto" />
                            </Grid.RowDefinitions>

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

                            <Label Grid.Column="0" Text="{Binding Shares.Symbol}" HorizontalTextAlignment="Center" />
                            <Label Grid.Column="1" Text="{Binding Shares.Qty}"    HorizontalTextAlignment="Center" />
                            <Label Grid.Column="2" Text="{Binding PricePerShare,  StringFormat='{}{0:c}'}" HorizontalTextAlignment="Center" />
                            <Label Grid.Column="3" Text="{Binding Total,          StringFormat='{}{0:c}'}" HorizontalTextAlignment="Center" />
                        </Grid>

                    </ViewCell>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>

        <Button Grid.Row="1" Grid.Column="0" Text="Buy More"                   Command="{Binding Buy}"                  CommandParameter="{Binding Source={x:Reference SharesListView}, Path=SelectedItem.Shares.Symbol}"                 IsEnabled="{Binding Source={x:Reference SharesListView},                                     Path=SelectedItem, Converter={StaticResource InstanceToBoolConverter}}" />

        <Button Grid.Row="1" Grid.Column="1" Text="Sell"                  Command="{Binding Sell}"                 CommandParameter="{Binding Source={x:Reference SharesListView}, Path=SelectedItem.Shares.Symbol}"                 IsEnabled="{Binding Source={x:Reference SharesListView},                                     Path=SelectedItem, Converter={StaticResource InstanceToBoolConverter}}" />
    </Grid>

</ContentPage>

ViewModel

In order for the UI to present dynamic content, I had to implement a viewmodel.
The following viewmodel was implemented:

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 { Owner.AccountId=accountId ; Symbol=o :?> string} ) ,
                          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<SharesWithPrice>()

    member this.Load() =
        let result = broker.InvestmentsOf accountId
        this.Investments <- ObservableCollection<SharesWithPrice>(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

Conclusion

This solution, which is a Work In Process, can be found here on GitHub.

I decided to publish a conversation that I had with a frustrated developer…

Frustrated Dev: Oh, and do you have a lead on any Xamarin jobs? I really like the stack and our office just got sick with an illness I’ve seen before.

Frustrated Dev: You know, the one where a new boss is hired and decides everything but the one technology he knows is shit and must be thrown out.

Frustrated Dev: So, after 3+ years and at 90% completion, they’re trashing our Xamarin project. Awesome, right?

Scott Nimrod: Smh
Frustrated Dev: New boss is a web developer, so we’re on the Ionic2 train now (it’s a fork of a fork built with a fork of a fork of a fork of Cordova). I don’t hate JavaScript, just the stupid fucking kids that think they need to reinvent (more like rediscover) software development every three weeks.
Scott Nimrod: Smh
Scott Nimrod: Did anyone get fired?

Frustrated Dev: No, they don’t do that here except with management.

Frustrated Dev: For the people doing the actual work they just demoralize and marginalize you until you can’t stand it and leave on your own.
Frustrated Dev: Turnover has been near 50% in the year and a half I’ve been here.

Frustrated Dev: Uh… Ok… Not turnover, because they don’t replace people so much.

Frustrated Dev: They tell those of us left how much we suck because we’re too slow and need to do more overtime–but staffing is at a perfect level we’re assured.

Frustrated Dev: Either management are total idiots with no people skills or they’re hope to abuse us all enough to get rid of us without worrying about severance or unemployment.

Scott Nimrod: I assume you guys didn’t do TDD…

Frustrated Dev: On the Xamarin team we did.
Frustrated Dev: Until the deadlines management forced on us turned that to a dirty word.

Frustrated Dev: Refactoring (the crap we inherited needs it badly) was also a forbidden word.

Scott Nimrod: How is that possible unless developers blamed TDD for their incompetence.

Frustrated Dev: We had our hands full fixing code from India and coping with the bad architecture we weren’t allowed to fix–most of it near impossible to unit test in the code blobs it was made of.

Frustrated Dev: I just want to find a place that isn’t run by dumbshits and work there until I retire.