{"id":13714511,"url":"https://github.com/cboudereau/fabulous-weather","last_synced_at":"2025-04-11T19:32:30.852Z","repository":{"id":40908559,"uuid":"160152392","full_name":"cboudereau/fabulous-weather","owner":"cboudereau","description":null,"archived":false,"fork":false,"pushed_at":"2022-07-13T21:51:15.000Z","size":2647,"stargazers_count":9,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-25T17:47:54.806Z","etag":null,"topics":["elm-architecture","fsharp","fsharp-exercises","fsharp-samples","functional-programming","weather-service","xamarin","xamarin-forms"],"latest_commit_sha":null,"homepage":null,"language":"F#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cboudereau.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-12-03T07:50:14.000Z","updated_at":"2022-12-08T23:31:41.000Z","dependencies_parsed_at":"2022-07-14T06:40:30.866Z","dependency_job_id":null,"html_url":"https://github.com/cboudereau/fabulous-weather","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cboudereau%2Ffabulous-weather","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cboudereau%2Ffabulous-weather/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cboudereau%2Ffabulous-weather/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cboudereau%2Ffabulous-weather/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cboudereau","download_url":"https://codeload.github.com/cboudereau/fabulous-weather/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248467077,"owners_count":21108591,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["elm-architecture","fsharp","fsharp-exercises","fsharp-samples","functional-programming","weather-service","xamarin","xamarin-forms"],"created_at":"2024-08-03T00:00:23.361Z","updated_at":"2025-04-11T19:32:29.736Z","avatar_url":"https://github.com/cboudereau.png","language":"F#","funding_links":[],"categories":["Sample apps"],"sub_categories":[],"readme":"# 2018 Fsharp Advent\r\n\r\n## Christmas Day 2018 weather forecast (5 next days)\r\nFor the 2018 fsharp advent, I would like to build an Android and iOS weather mobile app by myself with end to end properties.\r\n\r\nI use for sure FSharp and a little framework called [Fabulous](https://fsprojects.github.io/Fabulous/).\r\n\r\n\u003cimg src=\"https://pbs.twimg.com/media/DtgylCUW0AANbV1.jpg\" width=\"200\" /\u003e\r\n\r\nI attempt to describe the full process even if you are not a mobile dev nor a fsharp dev. \r\nIt is really hard, so pull requests are accepted!\r\n\r\n# What is Fabulous ?\r\nThe aim of Fabulous is : \"Never write a ViewModel class again!\".\r\n\r\nFabulous is not a MVVM architecture it uses in fact a Model View Update architecture inspired by the elm architecture.\r\n\r\nThere is tons of articles that explain it in details.\r\n\r\n- The powerful of elm architecture with this [famous elm video](https://elm-lang.org/blog/time-travel-made-easy)\r\n- The [elmish architecture](https://elmish.github.io/elmish/)\r\n- Implementations : \r\n  - Web development with [Fable](https://fable.io/) : [fsharp](https://fsharp.org/) + [Babel](https://babeljs.io/), by transpiling fsharp\r\n  - App development with [Fabulous](https://fsprojects.github.io/Fabulous/) and [Xamarin](https://visualstudio.microsoft.com/xamarin/) : fsharp + xamarin dynamic form\r\n  \r\n \u003e What are key concepts ?\r\n\r\n## Discrete time\r\n  Events are discrete time, not continuous time : For example when you use timer trigger, by separating the await process and the action by sending commands, you can see each Timeout event and observe the application outside.\r\nIt is important, because, now you can keep the state of your architecture without any technical debugging components because you can record the state + the timeline of your domain events. \r\n  \r\n  In mobile Xamarin development, LiveUpdate could be plugged in an easiest way.\r\n\r\n## Domain driven design\r\n  Events are no more technical messages (INotifyPropertyChanged, ...) but domain events. \r\nThis architecture loves domain driven design. In our case we bind the position changed event by sending a command to notify things.\r\n\r\n  Those commands are not technical commands but a part of our domain.\r\n\r\n## Responsive design\r\n  It is possible to send a command in the future by using fsharp async pattern\r\n  \r\n  Response design + race condition don't exist anymore !\r\n\r\n  For the next part, we will replay the development life time of a mobile app.\r\n\r\n## Setup\r\n  We use Xamarin, you have to modify Visual Studio by adding Xamarin and fsharp for desktop.\r\nIf it is not yet done, please configure your visual studio by following this instructions:\r\n\r\nhttps://docs.microsoft.com/en-us/xamarin/cross-platform/get-started/installation/windows\r\n\r\n \u003e As a mobile app developper, I start with the domain, then view by playing with some mockups and then implement behavior + associated services\r\n\r\n## Configure Android emulator\r\n  You can use the android device manager by using a kitkat (lightweight one) emulator to debug.\r\nFor iOS, you need a Mac build host (on cloud, or local if you already have a Mac)\r\n\r\n## The Domain\r\n  Here is the domain that we will use for our application :\r\n\r\n  I need to describe the weather and select the worst weather (8 measures for one day) : \r\n\r\n```fsharp\r\ntype Weather = \r\n    | Sunny\r\n    | PartialCloudy \r\n    | Cloudy \r\n    | Rainy \r\n    | Stormy \r\n    | Snowy \r\n\r\nmodule Weather = \r\n    let worst x y =\r\n        match x, y with\r\n        | Snowy, _ | _, Snowy -\u003e Snowy\r\n        | Stormy, _ | _, Stormy -\u003e Stormy\r\n        | Rainy, _ | _, Rainy -\u003e Rainy\r\n        | Cloudy, _ | _, Cloudy -\u003e Cloudy\r\n        | PartialCloudy, _ | _, PartialCloudy-\u003e PartialCloudy\r\n        | Sunny, _ | _, Sunny -\u003e Sunny\r\n```\r\n\r\nI use the GPS sensor to get the current 5 days forecast \r\n\r\n```fsharp\r\ntype Position = \r\n    { Longitude: float\r\n      Latitude: float }\r\n```\r\n\r\nThe API uses SI unit systems that fsharp has in heart!\r\n\r\nI defined my model by using SI unit system, and defined functions that converts SI -\u003e other unit system.\r\nIn our app, we use celcius degree instead of Kelvin and kilometer per hour instead meter per second.\r\nThanks to fsharp, unit of measure and conversion function are strong typed!\r\n\r\n```fsharp\r\nopen FSharp.Data.UnitSystems.SI.UnitSymbols\r\n\r\nlet km = 1000m\u003cm\u003e\r\nlet hour = 3600m\u003cs\u003e\r\n\r\nmodule Temperature = \r\n    let kelvin x = x * 1m\u003cK\u003e\r\n    let celcius x = \r\n        x - 273.15m\u003cK\u003e\r\n        |\u003e decimal\r\n\r\nmodule Speed = \r\n    let mps x = x * 1m\u003cm/s\u003e\r\n    let kmph (x:decimal\u003cm/s\u003e) = x * hour / km \r\n```\r\n\r\nThe main domain that we will use on our app.\r\n\r\n```fsharp\r\ntype Wind = \r\n    { Degree : decimal\r\n      Speed : decimal\u003cm/s\u003e }\r\n\r\ntype Forecast = \r\n    { Date:System.DateTime\r\n      Weather:Weather\r\n      MinTemp:decimal\u003cK\u003e\r\n      MaxTemp:decimal\u003cK\u003e\r\n      Humidity:int\r\n      Wind:Wind }\r\n\r\ntype CityForecast = \r\n    { City:string\r\n      Country:string\r\n      Days:Forecast list }\r\n```\r\n\r\nNow that we have the domain in mind, let's start with the next step: designing and building view of our phone app.\r\n\r\n## Designing app with type driven development\r\nLet's change our habits by drawing our app with functions.\r\n\r\nWe will stack in a Page\r\n- A header that contains city and country names\r\n- 5 columns (5 days forecast) that contains the day, weather, temps, humidity, wind\r\n\r\nWe will use type driven development to build our view :  \r\n\r\n```fsharp\r\n///In type driven development we build things step by step without any implementation first\r\nlet fiveDaysForecast model = failwith \"not yet implemented\" \r\nlet home content = failwith \"not yet implemented\" \r\n\r\n///We built this function by separating things : 5 columns inside a home that contains a background image\r\nlet view (model: CityForecast) dispatch = model |\u003e fiveDaysForecast |\u003e home\r\n```\r\n\r\nNow we can build the home function by using Fabulous helper over Xamarin forms.\r\n\r\n```fsharp\r\n    ///This function contains the Background and the title of the home page\r\n    let home content = \r\n        View.ContentPage(\r\n          title = \"Weather\",\r\n          inputTransparent = true,\r\n          backgroundImage = \"bluesky.png\",\r\n          content = content)\r\n```\r\n\r\nWe have to build the fiveDaysForecast : \r\nWe can see that the pattern consists of a stack that contains a list of elements.\r\n\r\n```fsharp\r\nlet fiveDaysForecast (model: CityForecast) = \r\n\tlet fiveDays = model.Days |\u003e List.truncate 5\r\n\tlet city = if model.Country |\u003e String.IsNullOrWhiteSpace then model.City else sprintf \"%s, %s\" model.City model.Country \r\n\tView.StackLayout(padding = 20.0, verticalOptions = LayoutOptions.FillAndExpand,\r\n\t\tchildren = [ \r\n\t\t\tView.Label(text=city.ToUpper(), textColor=Color.Beige, backgroundColor=Color.FromHex(\"#0F4D8FAC\"), fontSize=40, fontAttributes=FontAttributes.Bold, horizontalTextAlignment=TextAlignment.Center)\r\n\t\t\tempty 20.\r\n\t\t\tView.Grid(\r\n\t\t\t\trowdefs=[box \"*\"],\r\n\t\t\t\tcoldefs=[ for _ in fiveDays -\u003e box \"*\" ],\r\n\t\t\t\tchildren = (fiveDays |\u003e List.mapi (day 0) ) )\r\n\t\t\t])\r\n```\r\nWe go deeper and deeper inside our components now we have to build the function day.\r\n\r\nYou can see that we used the partial application over the column index and the forecast data.\r\n\r\nWe have now to build the day function.\r\n\r\nThe parameter x and y are the row and column index for grid forms\r\n\r\n```fsharp\r\nlet day x y (forecast:Forecast) = \r\n    View.StackLayout(\r\n        backgroundColor=Color(0., 0., 0., 0.4),\r\n\t    children=\r\n\t\t\t[ forecast.Date |\u003e dayOfWeek |\u003e text 20. Color.Beige \r\n\t\t\t  forecast.Date.Day |\u003e ordinalDay |\u003e bold 20. Color.Beige \r\n\t\t\t  empty 15.\r\n\t\t\t  forecast.Weather |\u003e weatherIcon |\u003e image\r\n\t\t\t  forecast.MaxTemp |\u003e Temperature.celcius |\u003e temp 22. Color.Beige\r\n\t\t\t  separator Color.SkyBlue\r\n\t\t\t  forecast.MinTemp |\u003e Temperature.celcius |\u003e temp 22. Color.SkyBlue\r\n\t\t\t  empty 30.\r\n\t\t\t  forecast.Humidity |\u003e humidity\r\n\t\t\t  empty 25.\r\n\t\t\t  forecast.Wind.Degree |\u003e wind |\u003e text 22. Color.LightBlue\r\n\t\t\t  empty 5.\r\n\t\t\t  forecast.Wind.Speed |\u003e Speed.kmph |\u003e int |\u003e sprintf \"%i km/h\" |\u003e text 14. Color.Beige \r\n\t\t\t  empty 10.\r\n\t\t\t]).GridRow(x).GridColumn(y)\r\n```\r\n\r\nWe have now the big picture. For example the bold and text functions are customized label that we reuse constantly inside our view!\r\n\r\nthe rest of the code can be found inside the WeatherApp/WeatherApp.fs\r\n\r\nNow that we built a view, we want to deploy / check if it works really. \r\n\r\nLet's start by using a mockup first!\r\n\r\n## Mockup\r\n\r\nBy changing the program function we can supply the init one or mockup.\r\n\r\n```fsharp\r\n    let program = Program.mkProgram mockup update view\r\n```\r\nYou can locate this function by replacing init by mockup.\r\n\r\nThe mock part : Note that thanks to unit of measure, our domain is really clear (ubiquitous language!)\r\n\r\n```fsharp\r\nopen FSharp.Data.UnitSystems.SI.UnitSymbols\r\nlet mockup () = \r\n\t{ City=\"FONTAINEBLEAU\"\r\n\t  Country=\"FR\"\r\n\t  Days=\r\n\t\t[ { Date = DateTime.Now\r\n\t\t\tWeather = Cloudy\r\n\t\t\tMinTemp = 270m\u003cK\u003e\r\n\t\t\tMaxTemp = 277m\u003cK\u003e\r\n\t\t\tHumidity=98\r\n\t\t\tWind = \r\n\t\t\t\t{ Speed = 15m\u003cm/s\u003e\r\n\t\t\t\t  Degree = 270m } }\r\n\r\n\t\t  { Date = DateTime.Now.AddDays 1.\r\n\t\t\tWeather = Sunny\r\n\t\t\tMinTemp = 300m\u003cK\u003e\r\n\t\t\tMaxTemp = 310m\u003cK\u003e\r\n\t\t\tHumidity=10\r\n\t\t\tWind = \r\n\t\t\t\t{ Speed = 2m\u003cm/s\u003e\r\n\t\t\t\t  Degree = 20m } } \r\n\t\t\t\t  \r\n\t\t  { Date = DateTime.Now.AddDays 2.\r\n\t\t\tWeather = Rainy\r\n\t\t\tMinTemp = 280m\u003cK\u003e\r\n\t\t\tMaxTemp = 285m\u003cK\u003e\r\n\t\t\tHumidity=10\r\n\t\t\tWind = \r\n\t\t\t\t{ Speed = 50m\u003cm/s\u003e\r\n\t\t\t\t  Degree = 180m } } \r\n\t\t\t\t  \r\n\t\t  { Date = DateTime.Now.AddDays 3.\r\n\t\t\tWeather = PartialCloudy\r\n\t\t\tMinTemp = 250m\u003cK\u003e\r\n\t\t\tMaxTemp = 260m\u003cK\u003e\r\n\t\t\tHumidity=100\r\n\t\t\tWind = \r\n\t\t\t\t{ Speed = 28m\u003cm/s\u003e\r\n\t\t\t\t  Degree = 280m } } \r\n\t\t\t\t  \r\n\t\t  { Date = DateTime.Now.AddDays 4.\r\n\t\t\tWeather = Snowy\r\n\t\t\tMinTemp = 250m\u003cK\u003e\r\n\t\t\tMaxTemp = 257m\u003cK\u003e\r\n\t\t\tHumidity=80\r\n\t\t\tWind = \r\n\t\t\t\t{ Speed = 15m\u003cm/s\u003e\r\n\t\t\t\t  Degree = 200m } } ] }, Cmd.none\r\n```\r\n\r\nYou can now debug the application and use the Fabulous Live update feature describe here : https://fsprojects.github.io/Fabulous/tools.html\r\n\r\nNow that we have the UI part, lets track position updates!\r\n\r\n## Using GPS sensor through GeoLocator Xamarin plugins\r\nThe weather app tracks position and display forecast according to your location.\r\n\r\nThanks to the awesome plugins [GeoLocator](https://github.com/jamesmontemagno/GeolocatorPlugin) (very well documented), I transpose the csharp code to a fsharp one.\r\n\r\nHere is the fsharp implementation (https://jamesmontemagno.github.io/GeolocatorPlugin/CurrentLocation.html) of the gps locator:\r\n\r\nPorcelain and plumbing function to map features to our Domain (Position type)\r\n\r\n```fsharp\r\nlet locator = CrossGeolocator.Current\r\nlocator.DesiredAccuracy \u003c- 100.\r\n\r\nlet ofPosition (p:Abstractions.Position) = { Longitude=p.Longitude; Latitude=p.Latitude } \r\nlet ofPositionArg (p:Abstractions.PositionEventArgs) = p.Position |\u003e ofPosition\r\n\r\nlet startTracking timeout = \r\n\tif locator.IsListening then Async.ret true\r\n\telse\r\n\t\tlocator.StartListeningAsync(timeout, 0.) \r\n\t\t|\u003e Async.AwaitTask \r\n\r\nlet lastPosition () = \r\n\tlocator.GetLastKnownLocationAsync()\r\n\t|\u003e Async.AwaitTask\r\n\t|\u003e Async.map (Option.ofObj \u003e\u003e Option.map ofPosition)\r\n\r\nlet currentPosition () = \r\n\tlocator.GetPositionAsync()\r\n\t|\u003e Async.AwaitTask\r\n\t|\u003e Async.map ofPosition\r\n\r\nlet trackPosition () = \r\n\tlocator.PositionChanged \r\n\t|\u003e Async.AwaitEvent\r\n\t|\u003e Async.map (ofPositionArg \u003e\u003e PositionChanged)\r\n```\r\n\r\nWhen the application starts, I would like to use the last or current position as quick as possible and notify the app to update the weather : \r\n\r\n- Start sensor\r\n- Get the last known position or the current if it was not cached\r\n- Notify a new Position\r\n\r\nHere is the fsharp code to do that.\r\n\r\n```fsharp\r\n    let init () = \r\n        let cmd = \r\n            5. |\u003e TimeSpan.FromSeconds \r\n            |\u003e startTracking \r\n            |\u003e Async.bind (fun isListening -\u003e\r\n                if isListening then\r\n                    lastPosition ()\r\n                    |\u003e Async.bind(Option.map Async.ret \u003e\u003e Option.defaultWith currentPosition)\r\n                    |\u003e Async.map (PositionChanged \u003e\u003e Some)\r\n                else Async.ret None)\r\n            |\u003e Cmd.ofAsyncMsgOption\r\n        initModel, cmd \r\n```\r\n\r\nBecause we are using sensors, we have to prompt authorization to the user for ACCESS_FINE_LOCATION and ACCESS_COARSE_LOCATION.\r\nYou can configure each target project. This part is well described into the plugin documentation : https://jamesmontemagno.github.io/GeolocatorPlugin/GettingStarted.html\r\n\r\nNow that we can track the position of the user, we can now use a weather service\r\n\r\n## Weather service\r\nFor this app, I use https://openweathermap.org/ api with my own free account, maybe the api key will not be available after the fsharpadvent.\r\nYou can subscribe a free account and copy paste the APPID here:\r\n\r\n```fsharp\r\nlet ApiKey = \"760628d3c2b9ede15fb9f57de25cd3ee\"\r\n```\r\n\r\nThanks to the fsharp interactive you can hack any api you want.\r\nIn our case I use FSharp.Data to get the data through a REST service.\r\n\r\nA script is available : weather.fsx\r\n\r\n```fsharp\r\n#r \"../packages/FSharp.Data.3.0.0/lib/net45/FSharp.Data.dll\"\r\n#load \"Async.fs\"\r\n#load \"Domain.fs\"\r\n#load \"WeatherApi.fs\"\r\n\r\nopen Domain\r\n\r\n{ Latitude=48.8713344; Longitude= 2.3462986 } \r\n|\u003e WeatherApi.get \r\n|\u003e Async.RunSynchronously\r\n```\r\n\r\nI start firstly by putting in place the result of a crafted request inside the forecast.json file.\r\nI use FSharp.Data and defined a JsonProvider\r\nThe WeatherApi.fs file contains the implementation of the OpenWeatherMap REST service\r\n\r\nThis function build the url with our Position : \r\n```fsharp\r\nlet url apiKey latitude longitude = \r\n    let uri = \r\n        UriBuilder(\r\n            Scheme=\"https\", \r\n            Host=\"api.openweathermap.org\",\r\n            Path=\"data/2.5/forecast\",\r\n            Query=sprintf \"lat=%f\u0026lon=%f\u0026APPID=%s\" latitude longitude apiKey)\r\n    uri.Uri\r\n```\r\n\r\nYou can quickly check if it works inside the fsharp interactive\r\n```fsharp\r\nurl \"760628d3c2b9ede15fb9f57de25cd3ee\" 48.8713344 2.3462986\r\n|\u003e string\r\n|\u003e Http.RequestString\r\n```\r\n\r\nIt will return the json.\r\n\r\nYou can copy the json to a file and continuing you hack with the JsonProvider of FSharp.Data\r\n\r\n```fsharp\r\ntype ForecastApi = JsonProvider\u003c \"forecast.json\" \u003e\r\n```\r\n\r\nNow we have to convert type coming from Type Provider to our Domain :\r\nOpenWeatherMap supply 8 measures per day. \r\nI choose to fold measures by taking a pessimistic approach.\r\nThe API gives data into SI unit system, we will keep this approach by enforcing typing when we have to convert it.\r\n\r\n```fsharp\r\nlet convert (forecast:ForecastApi.Root) = \r\n    let forecasts = \r\n        forecast.List\r\n        |\u003e Seq.groupBy(fun x -\u003e x.DtTxt.Date)\r\n        |\u003e Seq.map(fun (date, sources) -\u003e \r\n            { Date = date\r\n              Weather = \r\n                sources |\u003e Seq.collect(fun w -\u003e w.Weather |\u003e Seq.map (fun x -\u003e x.Icon |\u003e Weather.ofIcon)) \r\n                |\u003e Seq.fold Weather.worst Weather.Sunny \r\n              MinTemp = sources |\u003e Seq.map (fun w -\u003e w.Main.TempMin) |\u003e Seq.min |\u003e Temperature.kelvin\r\n              MaxTemp = sources |\u003e Seq.map (fun w -\u003e w.Main.TempMax) |\u003e Seq.max |\u003e Temperature.kelvin\r\n              Humidity = sources |\u003e Seq.map (fun w -\u003e w.Main.Humidity) |\u003e Seq.max\r\n              Wind = \r\n                { Degree = sources |\u003e Seq.map (fun w -\u003e w.Wind.Deg) |\u003e Seq.max \r\n                  Speed = sources |\u003e Seq.map (fun w -\u003e w.Wind.Speed) |\u003e Seq.max |\u003e Speed.mps } } )\r\n        |\u003e Seq.toList\r\n    \r\n    { City = forecast.City.Name\r\n      Country = forecast.City.Country\r\n      Days = forecasts }\r\n```\r\n\r\nFinally, the clue that take a position in parameters and returns the forecast!\r\n\r\n```fsharp\r\nlet get position = \r\n    let u = url ApiKey position.Latitude position.Longitude\r\n    u.ToString () \r\n    |\u003e Http.AsyncRequestString \r\n    |\u003e Async.map (ForecastApi.Parse \u003e\u003e convert)\r\n```\r\n\r\nYou can test this function inside the weather.fsx script\r\n\r\nNow we have view and services, we need a clue to bind command to service / model.\r\n\r\n## Update function\r\n\r\nThe role of the update function is to process messages by generating commands and/or an updated model.\r\nIn our case, the update replace completely the previous model.\r\n\r\nWhen a position changed, we have to call the service asynchronously and send a command when the Weather is received\r\n\r\nIn our case, I would like to have a function that transform continuous to discrete time case (PositionChanged message) and the async process but you can build and compose all your communication here.\r\n\r\nHere is the interesting part for our app : \r\n```fsharp\r\nlet update msg model =\r\n\tmatch msg with\r\n\t| PositionChanged p -\u003e\r\n\t\tmodel, \r\n\t\tWeatherApi.get p |\u003e Async.map WeatherReceived \r\n\t\t|\u003e Async.Catch\r\n\t\t|\u003e Async.map (function \r\n\t\t\t| Choice1Of2 x -\u003e Some x \r\n\t\t\t| Choice2Of2 _ -\u003e \r\n\t\t\t\t//Add logging here to handle api limitations, network issue, ...\r\n\t\t\t\tNone)\r\n\t\t|\u003e Cmd.ofAsyncMsgOption\r\n\t| WeatherReceived update -\u003e\r\n\t\tupdate, trackPosition () |\u003e Cmd.ofAsyncMsg\r\n```\r\n\r\nNow we have all we need to test our app into an Android Emulator (KiKat)!\r\n\r\nIn order to use the app in AdHoc mode (by copying the apk and installing to our phone) we have to publish it\r\n\r\n## Archive / Test for real!\r\nAt this step, the app runs well inside the emulator and you are ready to test for real on your phone!\r\n\r\nIn our case, you have to right click on WeatherApp.Android project and click Archive.\r\nThe process is separated into 2 steps: \r\n- Build the apk\r\n- Sign the apk with our key (Visual studio will guide you to create a key storage)\r\n\r\nWhen it is done copy the signed apk to our phone and launch it.\r\n\r\nIt works !\r\n\r\n  As a conclusion, we have created an app with views in full fsharp with the powerfull for fsharp for the templating part thanks to Fabulous helper, then created all the behavior and communication with the elmish architecture / Fabulous plumbing very quickly.\r\n  If you are pretty new to mobile dev, keep in mind that this activity requires a lot of patience but Fabulous experience speed up the full process by embedding mature concept.\r\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcboudereau%2Ffabulous-weather","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcboudereau%2Ffabulous-weather","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcboudereau%2Ffabulous-weather/lists"}