Setting Picker ItemsSource results in "Object must implement IConvertible." - xamarin.forms

I can't figure out why I'm receiving the following error when setting the ItemsSource property on a picker control:
"Object must implement IConvertible."
XAML
My XAML is as follows:
** Error: "Object must implement IConvertible." **
<Picker ItemsSource="{Binding Roles}" />
What's interesting, is that I can set the ItemsSource property for a listview control with no issues in the same file.
Different control but same ItemsSource binding in same XAML file:
** This works! **
<ListView ItemsSource="{Binding Roles}" />
In conclusion, why do I only receive an exception when setting the ItemsSource for a Picker but not for a ListView control?
Appendix:
//--------------------------------
// Initializing viewmodel instance
//--------------------------------
var viewmodel = new Dashboard(user, _account.Query);
var page = new DashboardPage() { BindingContext = viewmodel };
await viewmodel.LoadAsync();
.
.
.
//---------------------
// Viewmodel definition
//---------------------
type Dashboard(user:AuthenticatedUser, query:Query) =
inherit ViewModelBase()
let mutable name = sprintf "%s %s" user.FirstName user.LastName
let mutable primaryRole = user.Role
let mutable roles = seq []
let mutable role = ""
let mutable positions = seq []
let mutable position = ""
let mutable incidents = seq []
let mutable incident = ""
let mutable accountabilities = seq []
let mutable accountability = ""
member x.Name with get() = name
and set(v) = name <- v
base.NotifyPropertyChanged(<# x.Name #>)
member x.PrimaryRole with get() = primaryRole
and set(v) = primaryRole <- v
base.NotifyPropertyChanged(<# x.PrimaryRole #>)
member x.Roles with get() = roles
and set(v) = roles <- v
base.NotifyPropertyChanged(<# x.Roles #>)
member x.Role with get() = role
and set(v) = role <- v
base.NotifyPropertyChanged(<# x.Role #>)
member x.Positions with get() = positions
and set(v) = positions <- v
base.NotifyPropertyChanged(<# x.Positions #>)
member x.Position with get() = position
and set(v) = position <- v
base.NotifyPropertyChanged(<# x.Position #>)
member x.Incidents with get() = incidents
and set(v) = incidents <- v
base.NotifyPropertyChanged(<# x.Incidents #>)
member x.Incident with get() = incident
and set(v) = incident <- v
base.NotifyPropertyChanged(<# x.Incident #>)
member x.Accountabilities with get() = accountabilities
and set(v) = accountabilities <- v
base.NotifyPropertyChanged(<# x.Accountabilities #>)
member x.Accountability with get() = accountability
and set(v) = accountability <- v
base.NotifyPropertyChanged(<# x.Accountability #>)
member x.LoadAsync() =
async {
do! async {
match! user |> query.Roles with
| Error _ -> failwith "Query for 'roles' failed"
| Ok result -> x.Roles <- result
}
do! async {
match! user |> query.Positions with
| Error _ -> failwith "Query for 'positions' failed"
| Ok result -> x.Positions <- result
}
do! async {
match! user |> query.Incidents with
| Error _ -> failwith "Query for 'incidents' failed"
| Ok result -> x.Incidents <- result
}
do! async {
match! user |> query.Accountabilities with
| Error _ -> failwith "Query for 'accountabilities' failed"
| Ok result -> x.Accountabilities <- result
}
} |> Async.StartAsTask

I had to use an ObservableCollection instead of IEnumerable (i.e. seq).
Note: I'm not sure why ListView worked without this.
let mutable roles = ObservableCollection<string>() // Updated
...
member x.LoadAsync() =
async {
do! async {
match! user |> query.Roles with
| Error _ -> failwith "Query for 'roles' failed"
| Ok result -> x.Roles <- ObservableCollection<string>(result) // Updated
}

Related

Get value of optional types when retrieving value of record fields in F#

I have a type defined as follows:
type Employee = {
Id: Guid
Name: string
Phone: string
Email: Option<string>
}
and an instance of this type:
let emp = {
Id = Guid "bc07e94c-b376-45a2-928b-508b888802c9"
Name = "A"
Phone = "B"
Email = Some "E"
}
I want to extract the field names and values from this record type using reflection like the following:
let getFieldValueMappingOfARecordType (data: 'T) : seq<string * obj> =
let fieldValueMapping =
data.GetType()
|> FSharpType.GetRecordFields
|> Seq.map (
fun propertyInfo ->
(propertyInfo.Name, data |> propertyInfo.GetValue)
)
fieldValueMapping
Then invoking the above function with the instance of employee type
let mapping = getFieldValueMappingOfARecordType emp
|> Seq.toList
gives us:
val mapping : (string * obj) list =
[("Id", bc07e94c-b376-45a2-928b-508b888802c9); ("Name", "A"); ("Phone", "B");
("Email", Some "E")]
So far it's working well with non-optional type. But in case of optional types, it's returning the value of the field as either Some value or None. What I would like to do is to get the value when the field has Some value or make it null when it's None.
Essentially like the follwing:
val mapping : (string * obj) list =
[("Id", bc07e94c-b376-45a2-928b-508b888802c9); ("Name", "A"); ("Phone", "B");
("Email", "E")]
Or if the employee instance is like the following:
let emp = {
Id = Guid "bc07e94c-b376-45a2-928b-508b888802c9"
Name = "A"
Phone = "B"
Email = None
}
Then,
val mapping : (string * obj) list =
[("Id", bc07e94c-b376-45a2-928b-508b888802c9); ("Name", "A"); ("Phone", "B");
("Email", null)]
This is what I have so far (non-working code):
open System
open Microsoft.FSharp.Reflection
open System.Reflection
type Employee = {
Id: Guid
Name: string
Phone: string
Email: Option<string>
}
let emp = {
Id = Guid "bc07e94c-b376-45a2-928b-508b888802c9"
Name = "A"
Phone = "B"
Email = Some "E"
}
let getSomeOrNull (t: Type) (o: obj) =
let opt = typedefof<option<_>>.MakeGenericType [| t |]
match (o :?> opt) with
| Some s ->
s
| None ->
null
let getValues (data: 'T) =
let values =
data.GetType()
|> FSharpType.GetRecordFields
|> Array.map (
fun propertyInfo ->
let value =
data |> propertyInfo.GetValue
let isOption =
propertyInfo.PropertyType.IsGenericType && propertyInfo.PropertyType.GetGenericTypeDefinition() = typedefof<Option<_>>
match isOption with
| true ->
(propertyInfo.Name, (getSomeOrNull propertyInfo.PropertyType value))
| false ->
(propertyInfo.Name, value)
)
values
getValues emp
|> printfn "%A"
I think the only way to do this is with reflection:
let getSomeOrNull (t: Type) (o: obj) =
if isNull o then null
else t.GetProperty("Value").GetValue(o)
I think this should do the trick:
let getSomeOrNull (o: obj) =
match o with
| :? Option<string> as o -> a |> Option.toObj > box
| _ -> null

Why is is the compiler telling me "Type misMatch for App message" when they are the same type

So, I've been fighting with the compiler on a type error.
This code was working as of a couple days ago.
Type misMatch for App level message
App.fs snippets
module App =
type Msg =
| ConnectionPageMsg of ConnectionPage.Msg
| CodeGenPageMsg of CodeGenPage.Msg
//...
let update (msg : Msg) (model : Model) =
match msg with
| ConnectionPageMsg msg ->
let m, cmd = ConnectionPage.update msg model.ConnectionPageModel
{ model with ConnectionPageModel = m }, cmd
| CodeGenPageMsg msg ->
let m, cmd = CodeGenPage.update msg model.CodeGenPageModel
{ model with CodeGenPageModel = m }, cmd
//...
let runner =
Program.mkProgram init update view
|> Program.withConsoleTrace
|> XamarinFormsProgram.run app
I've added explicit aliases and the original error :
Type mismatch. Expecting a
'App.Msg -> App.Model -> App.Model * Cmd<App.Msg>'
but given a
'App.Msg -> App.Model -> App.Model * Cmd<Msg>'
The type 'App.Msg' does not match the type 'Msg'
Became these:
App.fs(50,50): Error FS0001: The type 'PocoGen.Page.ConnectionPage.Msg' does not match the type 'PocoGen.Page.CodeGenPage.Msg' (FS0001) (PocoGen)
App.fs(32,32): Error FS0001: Type mismatch.
Expecting a 'App.Msg -> App.Model -> App.Model * Cmd<App.Msg>'
but given a 'App.Msg -> App.Model -> App.Model * Cmd<Msg>'
The type 'App.Msg' does not match the type 'Msg' (FS0001) (PocoGen)
Other remarks
Right before these errors started appearing I was working on converting a blocking syncronous call to a async command in the ConnectionTestPage and removed the calling code for the cmd hoping that would fix it. (It did not)
ConnectionPage.fs Messages
type Msg =
| UpdateConnectionStringValue of string
| UpdateConnectionStringName of string
| TestConnection
| TestConnectionComplete of Model
| SaveConnectionString of ConnectionStringItem
| UpdateOutput of string
ConnectionPage.fs update
let update (msg : Msg) (m : Model) : Model * Cmd<Msg> =
match msg with
| UpdateConnectionStringValue conStringVal ->
{ m with
ConnectionString =
{ Id = m.ConnectionString.Id
Name = m.ConnectionString.Name
Value = conStringVal }
CurrentFormState =
match hasRequredSaveFields m.ConnectionString with
| false -> MissingConnStrValue
| _ -> Valid }, Cmd.none
| UpdateConnectionStringName conStringName ->
{ m with
ConnectionString =
{ Id = m.ConnectionString.Id
Name = conStringName
Value = m.ConnectionString.Value }
CurrentFormState =
match hasRequredSaveFields m.ConnectionString with
| false -> MissingConnStrValue
| _ -> Valid }, Cmd.none
| UpdateOutput output -> { m with Output = output }, Cmd.none
| TestConnection -> m, Cmd.none
| TestConnectionComplete testResult -> { m with Output = testResult.Output + "\r\n" }, Cmd.none
| SaveConnectionString(_) -> saveConnection m, Cmd.none
I've played with the Fsharp Version (because incidentally I did update to 4.7.2 a bit before getting this error)
The Full Repo:
https://github.com/musicm122/PocoGen_Fsharp/tree/master/PocoGen
The two branches of the match inside App.update have different types. The first branch has type App.Model * Cmd<ConnectionPage.Msg> and the second page has type App.Model * Cmd<CodeGenPage.Msg>.
You can't generally do that. This, for example, wouldn't compile:
let x =
match y with
| true -> 42
| false -> "foo"
What type is x here? Is it int or is it string? Doesn't compute. A match expression has to have all branches of the same type.
To convert Cmd<ConnectionPage.Msg> into a Cmd<App.Msg> (by wrapping the message in ConnectionPageMsg) you can use Cmd.map:
let update (msg : Msg) (model : Model) =
match msg with
| ConnectionPageMsg msg ->
let m, cmd = ConnectionPage.update msg model.ConnectionPageModel
{ model with ConnectionPageModel = m }, Cmd.map ConnectionPageMsg cmd
| CodeGenPageMsg msg ->
let m, cmd = CodeGenPage.update msg model.CodeGenPageModel
{ model with CodeGenPageModel = m }, Cmd.map CodeGenPageMsg cmd

Elm: populating a record with List values

Trying to learn Elm, and it's pretty hard :)
What I'm trying to accomplish:
I have a model that's a record with a number of key value pairs. I want to populate those keys with values from a list of strings.
module Main exposing (..)
import List
import Html exposing (Html, program, div, text)
type alias Model =
{ one: String
, two: String
, three: String
}
fakeData: List String
fakeData = ["foo", "bar", "baz", "bad", "baf"]
populate: Model -> List String -> Model
populate model data =
case List.head data of
Just str ->
case model.one of
"" ->
let updatedModel =
{ model | one = str }
in
case List.tail data of
Just items ->
populate updatedModel items
Nothing ->
model
_ ->
case model.two of
"" ->
let updatedModel =
{ model | two = str }
in
case List.tail data of
Just items ->
populate updatedModel items
Nothing ->
model
_ ->
case model.three of
"" ->
let updatedModel =
{ model | three = str }
in
case List.tail data of
Just items ->
populate updatedModel items
Nothing ->
model
_ ->
model
Nothing ->
model
init: ( Model, Cmd Msg)
init =
( populate { one = "", two = "", three = "" } fakeData, Cmd.none )
type Msg =
NoOp
view: Model -> Html Msg
view model =
div []
[ text (toString model) ]
update: Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
NoOp ->
( model, Cmd.none )
subscriptions: Model -> Sub Msg
subscriptions model =
Sub.none
main: Program Never Model Msg
main =
program
{ init = init
, view = view
, update = update
, subscriptions = subscriptions
}
This program prints out:
{ one = "foo", two = "bar", three = "baz" }
I guess I have a hard time figuring out how to make this code less repetitive and easier to reason about. What if I have 20 keys in the model that all need to be populated? The above code would get pretty insane.
You could use pattern matching on the list instead:
populate : Model -> List String -> Model
populate model data =
case data of
one :: two :: three :: _ ->
{ model | one = one, two = two, three = three }
_ ->
model

F# Async - An item with the same key has already been added

I am trying some async ops in f# but w/o much luck. I am trying to grab records from the db and perform operations on each record in Parallel.
let IsA1 companyId =
query { for comp in db.Company do
join cc in db.CC on (comp.CompanyId = int(cc.CompanyId))
join pp in db.PP on (cc.PartId = pp.PartId)
join tl in db.TL on (pp.CompanyId = tl.CompanyId)
where (comp.CompanyId = companyId)
select (comp.CompanyId > 0)
}
|> Seq.length |> fun len -> len > 0
let IsA2 companyId =
query { for t in db.Title do
join pp in db.PP on (t.Tid = pp.Tid)
join comp in db.Company on (pp.CompanyId = comp.CompanyId)
where (comp.CompanyId = companyId)
select (comp.CompanyId > 0)
}
|> Seq.length |> fun len -> len > 0
let GetAffiliations id =
async {
if (IsA1 id) then return "AffilBBB"
elif (IsA2 id) then return "AffilCCD"
else return Unknown
}
let ProcessCompany (company:dbSchema.ServiceTypes.Company) =
async {
let grp = GetAffiliations company.CompanyId
let result = { Id=company.CompanyId; Name=company.Name; Affiliations=grp; ContactType="ok"; }
return result
}
let GetCompanyNames =
let companies = db.Company |> Seq.distinctBy(fun d -> d.CompanyId)
companies
|> Seq.map(fun co -> ProcessCompany co)
|> Async.Parallel
|> Async.RunSynchronously
When I run the above code, I get error:
System.ArgumentException: An item with the same key has already been added.
The error is occurring as a result of another function call inside async { }:
let grp = GetAffiliations company.CompanyId
I am sure its a newbie issue, but I am not sure what the issue is. I even tried making the call inside of the async{ } another async call and used let! grp = (GetAffiliations company.CompanyId) but that does not resolve.
Because the two concurrent queries are sharing the same context, when the second result is added to the same context, you get an error saying that the context already has an item with the same key.
Using distinct instances of the 'db' context for each of the queries, should solve your issue.

IO and parallel async in Fsharp

I have some computation intensive tasks, which are now only running on 1 core, so 1/8th of my machine capacity. At the end of each task, I write a log in a file.
What would be the most graceful way to handle this IO using parallel tasks ?
Having my write be itself async ?
Sending messages to an agent who'd process the write sequentially ?
[<Fact>]
let boom () =
let tasks = [1 .. 10]
|> Seq.map (fun components -> async { //do compute intensive stuff
use writer = new StreamWriter("samefile")
writer.WriteLine "toto" }
)
tasks |> Async.Parallel |> Async.RunSynchronously
Edit
I ended up doing this, and replacing the new Stream in my async to be code by synchronous call to the agent.
[<Fact>]
let pasBoom () =
let tasks = [2 .. 2 .. 17]
|> Seq.map (fun components -> async { //do compute intensive stuff
//use writer = new StreamWriter("samefile")
use writerhanlde = repoFileHandle.PostAndReply(fun replyChannel -> GetFile(#"samefile", replyChannel))
printfn "%A" (writerhanlde.getWriter().ToString())
writerhanlde.getWriter().WriteLine "toto" }
)
tasks |> Async.Parallel |> Async.RunSynchronously
and the agent (there might be bugs please be careful, I just need something quick myself)
type IDisposableWriter =
inherit IDisposable
abstract getWriter : unit -> StreamWriter
type StreamMessage = | GetFile of string * AsyncReplyChannel<IDisposableWriter>
let repoFileHandle =
let writerCount = new Dictionary<string, int>()
let writerRepo = new Dictionary<string, StreamWriter> ()
Agent.Start(fun inbox ->
async { while true do
let! msg = inbox.Receive()
match msg with
| GetFile(filename, reply) ->
if not (writerRepo.ContainsKey(filename)) then
writerRepo.[filename] <- new StreamWriter(filename,true)
writerCount.[filename] <- 0
writerCount.[filename] <- writerCount.[filename] + 1
let obj = {new IDisposableWriter with
member this.getWriter () = writerRepo.[filename]
member IDisposable.Dispose() =
writerCount.[filename] <- writerCount.[filename] - 1
if writerCount.[filename] = 0 then
writerRepo.[filename].Dispose()
writerRepo.Remove(filename) |> ignore
}
reply.Reply(obj) })
and to avoid concurrent write
type WriteToStreamMessage = | WriteToStream of string * string
let fileWriterAgent =
Agent.Start(fun inbox ->
async { while true do
let! msg = inbox.Receive()
match msg with
| WriteToStream(filename, content) ->
use writerhanlde = repoFileHandle.PostAndReply(fun replyChannel -> GetFile(filename, replyChannel))
writerhanlde.getWriter().WriteLine content
})
Can you change your computation to return the message to be logged instead of writing it to a file? Then you could use PSeq in PowerPack, which is a thin wrapper over TPL:
open Microsoft.FSharp.Collections
let work n = sprintf "running task %d" n
let msgs = PSeq.init 10 work |> PSeq.toList
use writer = System.IO.StreamWriter(#"C:\out.log")
msgs |> List.iter writer.WriteLine

Resources