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.
In this post, I attempt to demonstrate how cool I am at writing proven code for a user’s registration form. Please note that I use the term proven and testable interchangeably.
The viewmodel is the root of the testable codebase for business logic.
As a result, I injected the function (aka: implementation) that handles the submit request for a registration form inside my viewmodel.
Here’s code that shows a workflow accepting the implementation of a submit request inside my viewmodel:
type ViewModel(implementation:Try.SubmitFn) as x =
...
let submit() =
let publishEvents events =
events |> List.iter(fun event -> eventOccurred.Trigger(event))
validatedForm |> function
| Some form ->
form |> Command.Execute
|> In.SubmitRegistration.workflow implementation
|> publishEvents
| None -> ()
Here’s the entire viewmodel:
namespace Nikeza.Mobile.UILogic.Registration
open System.Windows.Input
open Nikeza.Mobile.UILogic
open Nikeza.Mobile.Profile.Commands.Registration
open Nikeza.Mobile.Profile.Events
open Adapter
open In
type Form = Registration.Types.Form
module Updates =
let statusOf formValidated events =
events |> List.exists formValidated
type ViewModel(implementation:Try.SubmitFn) as x =
let mutable validatedForm = None
let eventOccurred = new Event<_>()
let validate() =
let isValidated = function
| FormValidated form -> validatedForm <- Some form; true
| _ -> false
{ Form.Email= x.Email
Form.Password= x.Password
Form.Confirm= x.Confirm
}
|> ofUnvalidated
|> Validate.Execute
|> In.ValidateRegistration.workflow
|> Updates.statusOf isValidated
let submit() =
let publishEvents events =
events |> List.iter(fun event -> eventOccurred.Trigger(event))
validatedForm |> function
| Some form ->
form |> Command.Execute
|> In.SubmitRegistration.workflow implementation
|> publishEvents
| None -> ()
let validateCommand = DelegateCommand( (fun _ -> x.IsValidated <- validate()) , fun _ -> true)
let submitCommand = DelegateCommand( (fun _ -> submit() |> ignore),
fun _ -> x.IsValidated <- validate(); x.IsValidated )
let mutable email = ""
let mutable password = ""
let mutable confirm = ""
let mutable isValidated = false
member x.Validate = validateCommand :> ICommand
member x.Submit = submitCommand :> ICommand
[<CLIEvent>]
member x.EventOccured = eventOccurred.Publish
member x.Email
with get() = email
and set(value) = email <- value
member x.Password
with get() = password
and set(value) = password <- value
member x.Confirm
with get() = confirm
and set(value) = confirm <- value
member x.IsValidated
with get() = isValidated
and set(value) = isValidated <- value
Test
An automated test targeting business logic should provide its own implementation for submitting a registration request.
My test for submitting a registration form is as follows:
[<Test>]
let ``Registration submitted after being validated`` () =
// Setup
let mutable registrationSucceeded = false
let registration = ViewModel(mockSubmit)
registration.FillIn()
.EventOccured
.Add(fun event ->
event |> function
| RegistrationSucceeded _ -> registrationSucceeded <- true
| RegistrationFailed _ -> registrationSucceeded <- false)
registration.Validate.Execute()
// Test
registration.Submit.Execute()
// Verify
registrationSucceeded |> should equal true
Test API
As I stated earlier, I try to be cool when I write code. Thus, to validate my coolness, I used a type extension to initialize the registration viewmodel.
Here’s my test API:
module TestAPI
open Nikeza.Mobile.UILogic.Registration
open Try
let someEmail = "scott@abc.com"
let somePassword = "some_password"
let mockSubmit : SubmitFn =
fun _ -> Ok { Id = ""
FirstName = ""
LastName = ""
Email = ""
Bio = ""
ImageUrl = ""
Sources = []
}
type ViewModel with
member x.FillIn () =
x.Email <- someEmail
x.Password <- somePassword
x.Confirm <- somePassword
x
Usage
The workflow below relies on a SubmitFn which was originally injected into my viewmodel.
The workflow that receives the implementation of this dependency is as follows:
module SubmitRegistration =
type private SubmitWorkflow = SubmitFn -> Command -> RegistrationSubmissionEvent list
let workflow : SubmitWorkflow =
fun submitFn command -> command |> function
Command.Execute form ->
form |> submitFn
|> ResultOf.Submit.Executed
|> Are.Registration.Submission.events
I recently connected a viewmodel to a workflow that I was inspired to write as a result of reading the book: Domain Driven Design Made Functional. All of my source code can be found on GitHub/Nikeza.
ViewModel
In the viewmodel, I handle validating and submitting a form. The interesting part of this exercise is that I receive domain events as a response to requesting validation and submission.
Here’s the logic for validating and submitting a form:
type ViewModel() as x =
let mutable validatedForm = None
let eventOccurred = new Event<_>()
let validate() =
let isValidated = function
| FormValidated form -> validatedForm <- Some form; true
| _ -> false
{ Form.Email= x.Email
Form.Password= x.Password
Form.Confirm= x.Confirm
}
|> ofUnvalidated
|> Validate.Execute
|> In.ValidateRegistration.workflow
|> Updates.statusOf isValidated
let submit() =
let publishEvents events =
events |> List.iter(fun event -> eventOccurred.Trigger(event))
validatedForm |> function
| Some form ->
form |> Submit.Execute
|> In.SubmitRegistration.workflow
|> publishEvents
| None -> ()
let validateCommand = DelegateCommand( (fun _ -> x.IsValidated <- validate()) , fun _ -> true)
let submitCommand = DelegateCommand( (fun _ -> submit() |> ignore),
fun _ -> x.IsValidated <- validate(); x.IsValidated )
let mutable email = ""
let mutable password = ""
let mutable confirm = ""
let mutable isValidated = false
member x.Validate = validateCommand :> ICommand
member x.Submit = submitCommand :> ICommand
[<CLIEvent>]
member x.EventOccured = eventOccurred.Publish
The remaining viewmodel is the following:
type ViewModel() as x =
...
member x.Email
with get() = email
and set(value) = email <- value
member x.Password
with get() = password
and set(value) = password <- value
member x.Confirm
with get() = confirm
and set(value) = confirm <- value
member x.IsValidated
with get() = isValidated
and set(value) = isValidated <- value
Here’s some viewmodel helpers:
type UIForm = Registration.Types.Form
type DomainForm = Nikeza.Mobile.Profile.Registration.Form
module That =
let generatesPage events =
let eventToPage = function
| RegistrationSucceeded _ -> () // displayPortal()
| RegistrationFailed _ -> () // displayError()
events |> List.iter eventToPage
module Updates =
let statusOf formValidated events =
events |> List.exists formValidated
Registration Workflow
Here’s the Registration workflow:
module In
open Nikeza.Mobile.Profile.Commands
open Nikeza.Mobile.Profile.Commands.Registration
open Nikeza.Mobile.Profile.Events
open Logic
module SubmitRegistration =
type private SubmitWorkflow = Submit -> RegistrationSubmissionEvent list
let workflow : SubmitWorkflow =
fun command -> command |> function
Submit.Execute form ->
form |> Try.submit
|> ResultOf.Submit.Executed
|> Are.Registration.Submission.events
module ValidateRegistration =
type private ValidateWorkflow = Validate -> RegistrationValidationEvent list
let workflow : ValidateWorkflow =
fun command -> command |> function
Validate.Execute form ->
form |> Registration.validate
|> ResultOf.Validation.Executed
|> Are.Registration.Validation.events
Domain Events
The domain events are structured as follows:
module Nikeza.Mobile.Profile.Events
open Nikeza.Common
open Nikeza.DataTransfer
type UnvalidatedForm = Nikeza.Mobile.Profile.Registration.UnvalidatedForm
type ValidatedForm = Nikeza.Mobile.Profile.Registration.ValidatedForm
type RegistrationValidationEvent =
| FormValidated of ValidatedForm
| FormNotValidated of UnvalidatedForm
type RegistrationSubmissionEvent =
| RegistrationSucceeded of Profile
| RegistrationFailed of ValidatedForm
Here’s how these events get emitted:
module internal Are.Registration
open Nikeza.Mobile.Profile.Commands
open Nikeza.Mobile.Profile.Events
module Submission =
type private RegistrationSubmission = ResultOf.Submit -> RegistrationSubmissionEvent list
let events : RegistrationSubmission =
fun resultOf ->
resultOf |> function
ResultOf.Submit.Executed result ->
result |> function
| Ok profile -> [ RegistrationSucceeded profile]
| Error form -> [ RegistrationFailed form ]
module Validation =
type private RegistrationValidation = ResultOf.Validation -> RegistrationValidationEvent list
let events : RegistrationValidation =
fun resultOf ->
resultOf |> function
ResultOf.Validation.Executed result ->
result |> function
| Error form -> [FormNotValidated form]
| Ok form -> [FormValidated form]
Tests
Here’s the test that I wrote:
module Tests.Registration
open FsUnit
open NUnit.Framework
open Nikeza.Mobile.UILogic.Registration
open TestAPI
open Nikeza.Mobile.Profile.Events
[<Test>]
let ``Registration validated with email and matching passwords`` () =
// Setup
let registration = ViewModel()
registration.Email <- someEmail
registration.Password <- somePassword
registration.Confirm <- somePassword
// Test
registration.Validate.Execute()
// Verify
registration.IsValidated |> should equal true
[<Test>]
let ``Registration submitted after being validated`` () =
// Setup
let mutable registrationSucceeded = false
let registration = ViewModel()
Apply.valuesTo registration
registration.EventOccured.Add(fun event ->
event |> function
| RegistrationSucceeded _ -> registrationSucceeded <- true
| RegistrationFailed _ -> registrationSucceeded <- false)
registration.Validate.Execute()
// Test
registration.Submit.Execute()
// Verify
registrationSucceeded |> should equal true