mirror of
https://github.com/LukeHagar/jims-blog.git
synced 2025-12-06 12:37:48 +00:00
56 lines
24 KiB
HTML
56 lines
24 KiB
HTML
<!doctype html><html><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><meta http-equiv=x-ua-compatible content="ie=edge"><link rel=icon href=/fav.png type=image/png><link rel=preconnect href=https://fonts.googleapis.com><link rel=preconnect href=https://fonts.gstatic.com crossorigin><link rel=preload as=style href="https://fonts.googleapis.com/css2?family=Alata&family=Lora&family=Muli:ital,wght@0,400;0,500;0,600;0,700;1,400;1,500;1,600;1,700&family=Roboto&family=Muli:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap"><link rel=stylesheet href="https://fonts.googleapis.com/css2?family=Alata&family=Lora&family=Muli:ital,wght@0,400;0,500;0,600;0,700;1,400;1,500;1,600;1,700&family=Roboto&family=Muli:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap" media=print onload='this.media="all"'><noscript><link href="https://fonts.googleapis.com/css2?family=Alata&family=Lora&family=Muli:ital,wght@0,400;0,500;0,600;0,700;1,400;1,500;1,600;1,700&family=Roboto&family=Muli:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap" rel=stylesheet></noscript><link rel=stylesheet href=/css/font.css media=all><meta property="og:title" content="Building mobile apps in F# using Xamarin.Forms and Elmish"><meta property="og:description" content="FSharp and Xamarin Xamarin is well known for allowing you to build mobile apps in C#, but you can use F# as well. F# is fully supported by the compiler and toolchains in both Visual Studio 2017 and Visual Studio for Mac. It’s also pretty much supported by all the Xamarin tools including Xamarin.Forms (except for one bug that should be fixed soon).
|
|
This is great for F# fans like me, but one thing that has been missing for a while has been architecture recomendations."><meta property="og:type" content="article"><meta property="og:url" content="https://jimbobbennett.dev/blogs/building-mobile-apps-in-f-using-xamarin-forms-and-elmish/"><meta property="og:image" content="https://jimbobbennett.dev/blogs/building-mobile-apps-in-f-using-xamarin-forms-and-elmish/banner.png"><meta property="article:section" content="blogs"><meta property="article:published_time" content="2018-05-06T15:00:40+00:00"><meta property="article:modified_time" content="2018-05-06T15:00:40+00:00"><meta property="og:site_name" content="JimBobBennett"><meta name=twitter:card content="summary_large_image"><meta name=twitter:image content="https://jimbobbennett.dev/blogs/building-mobile-apps-in-f-using-xamarin-forms-and-elmish/banner.png"><meta name=twitter:title content="Building mobile apps in F# using Xamarin.Forms and Elmish"><meta name=twitter:description content="FSharp and Xamarin Xamarin is well known for allowing you to build mobile apps in C#, but you can use F# as well. F# is fully supported by the compiler and toolchains in both Visual Studio 2017 and Visual Studio for Mac. It’s also pretty much supported by all the Xamarin tools including Xamarin.Forms (except for one bug that should be fixed soon).
|
|
This is great for F# fans like me, but one thing that has been missing for a while has been architecture recomendations."><meta name=twitter:site content="@jimbobbennett"><meta name=twitter:creator content="@jimbobbennett"><link rel=stylesheet href=https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css integrity=sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3 crossorigin=anonymous><link rel=stylesheet href=/css/header.css media=all><link rel=stylesheet href=/css/footer.css media=all><link rel=stylesheet href=/css/theme.css media=all><link rel="shortcut icon" type=image/png href=/fav.png><link rel="shortcut icon" sizes=192x192 href=/fav.png><link rel=apple-touch-icon href=/fav.png><link rel=alternate type=application/rss+xml href=https://jimbobbennett.dev/index.xml title=JimBobBennett><script type=text/javascript>(function(e,t,n,s,o,i,a){e[n]=e[n]||function(){(e[n].q=e[n].q||[]).push(arguments)},i=t.createElement(s),i.async=1,i.src="https://www.clarity.ms/tag/"+o,a=t.getElementsByTagName(s)[0],a.parentNode.insertBefore(i,a)})(window,document,"clarity","script","dctc2ydykv")</script><style>:root{--text-color:#343a40;--text-secondary-color:#6c757d;--background-color:#000;--secondary-background-color:#64ffda1a;--primary-color:#007bff;--secondary-color:#f8f9fa;--text-color-dark:#e4e6eb;--text-secondary-color-dark:#b0b3b8;--background-color-dark:#000000;--secondary-background-color-dark:#212529;--primary-color-dark:#ffffff;--secondary-color-dark:#212529}body{background-color:#000;font-size:1rem;font-weight:400;line-height:1.5;text-align:left}</style><meta name=description content><link rel=stylesheet href=/css/index.css><link rel=stylesheet href=/css/single.css><link rel=stylesheet href=/css/projects.css media=all><script defer src=/fontawesome-5/all-5.15.4.js></script><title>Building mobile apps in F# using Xamarin.Forms and Elmish | JimBobBennett</title></head><body class=light onload=loading()><header><nav class="pt-3 navbar navbar-expand-lg"><div class="container-fluid mx-xs-2 mx-sm-5 mx-md-5 mx-lg-5"><a class="navbar-brand primary-font text-wrap" href=/><img src=/fav.png width=30 height=30 class="d-inline-block align-top">
|
|
JimBobBennett</a>
|
|
<button class=navbar-toggler type=button data-bs-toggle=collapse data-bs-target=#navbarContent aria-controls=navbarContent aria-expanded=false aria-label="Toggle navigation"><svg aria-hidden="true" height="24" viewBox="0 0 16 16" width="24" data-view-component="true"><path fill-rule="evenodd" d="M1 2.75A.75.75.0 011.75 2h12.5a.75.75.0 110 1.5H1.75A.75.75.0 011 2.75zm0 5A.75.75.0 011.75 7h12.5a.75.75.0 110 1.5H1.75A.75.75.0 011 7.75zM1.75 12a.75.75.0 100 1.5h12.5a.75.75.0 100-1.5H1.75z"/></svg></button><div class="collapse navbar-collapse text-wrap primary-font" id=navbarContent><ul class="navbar-nav ms-auto text-center"><li class="nav-item navbar-text"><a class=nav-link href=/ aria-label=home>Home</a></li><li class="nav-item navbar-text"><a class=nav-link href=/#about aria-label=about>About</a></li><li class="nav-item navbar-text"><a class=nav-link href=/#projects aria-label=projects>Recent Highlights</a></li><li class="nav-item navbar-text"><a class=nav-link href=/blogs title="Blog posts">Blog</a></li><li class="nav-item navbar-text"><a class=nav-link href=/videos title=Videos>Videos</a></li><li class="nav-item navbar-text"><a class=nav-link href=/livestreams title=Livestreams>Livestreams</a></li><li class="nav-item navbar-text"><a class=nav-link href=/conferences title=Conferences>Conferences</a></li><li class="nav-item navbar-text"><a class=nav-link href=/resume title=Resume>Resume</a></li></ul></div></div></nav></header><div id=content><section id=projects><div class="container pt-5" id=list-page><div class="row justify-content-center px-3 px-md-5"><h1 class="text-left pb-2 content">Building mobile apps in F# using Xamarin.Forms and Elmish</h1><div class="text-left content">Jim Bennett
|
|
<small>|</small>
|
|
May 6, 2018</div></div></div></section><section id=single><div class=container><div class="row justify-content-center"><div class="col-sm-12 col-md-12 col-lg-9"><div class=pr-lg-4><article class="page-content p-2"><h1 id=fsharp-and-xamarin>FSharp and Xamarin</h1><p>Xamarin is well known for allowing you to build mobile apps in C#, but you can use F# as well. F# is fully supported by the compiler and toolchains in both Visual Studio 2017 and Visual Studio for Mac. It’s also pretty much supported by all the Xamarin tools including Xamarin.Forms (<a href=https://github.com/xamarin/Xamarin.Forms/issues/2425>except for one bug</a> that should be fixed soon).</p><p>This is great for F# fans like me, but one thing that has been missing for a while has been architecture recomendations. Mobile apps, like other UI heavy apps are very object oriented. Buttons are objects, labels are objects and they fit very nicely into the classic ‘OO’ space. This has meant that traditionally UI apps are also built using objects - it’s objects all the way down and mutable state everywhere. This doesn’t match well with functional programming paradigms which try to move away from objects and mutability.</p><p>To go functional when it comes to UI, one trend is towards Elm. This is an architecture that comes from Erlang and has become popular in the F# community via <a href=https://github.com/elmish/elmish>Elmish for Fable</a> for web sites. This architecture abstracts the object nature of the UI away and replaced by a purely functional model. The app runs with a function to create a view, a function to initialise an immutable model, and an update function to handle messages from either the UI or other parts of the system. The update function takes the current model and a message, and returns a new instance of the model containing a copy of the original model updated based off the message recieved. Once a new model is generated the UI is updated.</p><div class=image-div style=max-width:410px><p><img src=04-flow.png alt="Overview diagram of Elm"></p></div><p>This paradigm has been ported to F# by <a href=https://twitter.com/dsyme>Don Syme</a> as <a href=https://github.com/fsprojects/Elmish.XamarinForms>Elmish.XamarinForms</a>, an open source project also available on <a href=https://www.nuget.org/packages/Elmish.XamarinForms>NuGet</a>. This project provides two possible implementations of Elmish for Xamarin.Forms - known as half-elmish and full-elmish. In this post I’ll look at the half-elmish implementation as this is more suited to Xamarin developers who want to move to F# (full-elmish is more suited to F# developers wanting to move to Xamarin).</p><h3 id=half-elmish>Half-Elmish</h3><p>Half-elmish mixes traditional Xamarin.Forms development with the elmish architecture. At it’s most basic, you define your UI using XAML, setting bindings on the UI elements as if you were using MVVM. You then implement an imutable model, an update method that handles messages that you define and returns an updated model, and a view method that configures the bindings for your UI including property bindings and commands. When a message is handled the bindings are re-evaluated and the UI updates using the normal Xamarin.Forms data binding.</p><h4 id=getting-started>Getting started</h4><p>Start by creating a new blank Xamarin.Forms F# app using either Visual Studio 2017 or Visual Studio for Mac called <strong>HelloElmish</strong>. The latest versions of both will allow you to create apps with a .NET standard core project. Add the <code>Elmish.XamarinForms</code> NuGet package to all the projects in your app.</p><p>We’ll create a fairly boring app, one that tracks the number of clicks of a button. Not exciting, but enough to illustrate the basics to get you started. Open the <code>MainPage.xaml</code> file, remove the contents of the <code>ContentPage</code> and add the following:</p><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-xml data-lang=xml><span style=display:flex><span><span style=color:#f92672><StackLayout></span>
|
|
</span></span><span style=display:flex><span> <span style=color:#f92672><Label</span> <span style=color:#a6e22e>Text=</span><span style=color:#e6db74>"{Binding Path=[Count]}"</span><span style=color:#f92672>/></span>
|
|
</span></span><span style=display:flex><span> <span style=color:#f92672><Button</span> <span style=color:#a6e22e>Text=</span><span style=color:#e6db74>"Increment"</span>
|
|
</span></span><span style=display:flex><span> <span style=color:#a6e22e>Command=</span><span style=color:#e6db74>"{Binding Path=[Increment]}"</span><span style=color:#f92672>/></span>
|
|
</span></span><span style=display:flex><span><span style=color:#f92672></StackLayout></span>
|
|
</span></span></code></pre></div><blockquote><p>Notice how the bindings are set using an array-indexer style syntax. This is part of the Elmish implementation of binding, so if you want to know more head to GitHub and dig through the source, but for now just accept that this is how we bind.</p></blockquote><p>Next, lets implement the application logic. Open the <code>App.xaml.cs</code> file. We need to declare a model to track the button click count, an <code>enum</code> to define the messages our app supports, as well as three functions - <code>init</code>, <code>update</code> and <code>view</code>.</p><p>The model is fairly simple, so declare this before the <code>App</code> class declaration:</p><pre tabindex=0><code>type Model =
|
|
{
|
|
Count : int
|
|
}
|
|
</code></pre><p>Next up, add an enum type for the messages that the update function will handle:</p><pre tabindex=0><code>type Msg =
|
|
| Increment
|
|
</code></pre><p>Now we need to implement the <code>init</code> method. This is called when your app starts up to create the initial model, so add this function to the <code>App</code> class:</p><pre tabindex=0><code>let init () = { Count = 0 }
|
|
</code></pre><p>This creates a new model with the count set to 0. Next is to add a function to handle a message to increment the counter. This takes a message and a model, so add this to the <code>App</code> class:</p><pre tabindex=0><code>let update msg model =
|
|
match msg with
|
|
| Increment -> { Count = model.Count + 1 }
|
|
</code></pre><p>This function matches the message with the <code>Increment</code> message, and if it matches returns a new model with the <code>Count</code> set to the count of the previous model plus one. It may seem overkill to match when we only have one message type, but we’ll add another message type later. Notice how it is always a new model that is returned - the <code>Model</code> type is immutable so we always have to create a new copy.</p><p>After the <code>update</code> function we need to add the <code>view</code> function to the <code>App</code> type. This is used to create bindings between the UI, the model and the messages. This function returns a tuple of a Xamarin.Forms page and a list of bindings:</p><pre tabindex=0><code>let view () =
|
|
MainPage(),
|
|
[ "Count" |> Binding.oneWay (fun m -> m.Count.ToString())
|
|
"Increment" |> Binding.msg Increment
|
|
]
|
|
</code></pre><p>You’ll need to open the following modules at the top of the file for this to compile:</p><pre tabindex=0><code>open Elmish.XamarinForms
|
|
open Elmish.XamarinForms.StaticViews
|
|
</code></pre><p>This view method defines two bindings. One is a one way binding for the <code>"Count"</code> property that binds the value to a function that returns the <code>Count</code> from the model converted to a string. The next is a binding from the <code>"Increment"</code> command to the <code>Increment</code> message.</p><p>When the app is launched this <code>view</code> function is run and the page is created. The values of the bindings are evaluated and set. The <code>oneWay</code> binding sets the value on the Label based on the return value of the function, and the <code>msg</code> binding binds the button command property to a command created for you by the elmish implementation. Executing this command calls the <code>update</code> function passing in the <code>Increment</code> message and the current model.</p><p>When the button is clicked, this command is executed, calling the <code>update</code> function. The new model that comes back from this function call is then passed through the bindings again, and if the values returned from the bindings is different from the previous value the UI widget gets updated. In our case, the <code>update</code> method increments the count, so the value of the one way binding would increment from 0 to 1, and the UI would be updated to show 1.</p><p>The final thing to do is wire everything up to make the app kick off the elmish functions. Add the following to the <code>App</code> class:</p><pre tabindex=0><code>let runner =
|
|
Program.mkSimple init update view
|
|
|> Program.withStaticView
|
|
|> Program.run
|
|
|
|
do base.MainPage <- runner.InitialMainPage
|
|
</code></pre><p>In addition, remove the setting of the <code>MainPage</code> when inheriting from <code>App</code>:</p><pre tabindex=0><code>type App() =
|
|
inherit Application()
|
|
</code></pre><p>This code creates a <code>runner</code> that creates an elmish program using our <code>init</code>, <code>update</code> and <code>view</code> functions. It uses a static view that tells elmish that the page is created once and updated via binding. The <strong>full-elmish</strong> implementation doesn’t use XAML pages, instead it creates the UI on the fly and the whole UI is rebuilt after each update (with a lot of logic inside the elmish code to reuse stuff for performance). Full-elmish is outside the scope of this post.</p><p>This runner has an initial main page property that is set as the <code>MainPage</code> for your <code>App</code> class. The elmish architecture supports navigation and multiple pages, and I’ll cover this in a later blog post.</p><p>Build everything and run the app. You’ll be able to click the button and see the count increment.</p><div class=image-div style=max-width:322px><p><img src=2018-05-05_15-44-50-2.gif alt="Animated Gif showing the value being incremented"></p></div><h4 id=getting-more-advanced>Getting more advanced</h4><p>Lets make this a bit more advanced by adding a decrement button. Start by adding a new button to the <code>StackLayout</code>:</p><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-xml data-lang=xml><span style=display:flex><span>...
|
|
</span></span><span style=display:flex><span><span style=color:#f92672><Button</span> <span style=color:#a6e22e>Text=</span><span style=color:#e6db74>"Decrement"</span>
|
|
</span></span><span style=display:flex><span> <span style=color:#a6e22e>Command=</span><span style=color:#e6db74>"{Binding Path=[Decrement]}"</span><span style=color:#f92672>/></span>
|
|
</span></span><span style=display:flex><span>...
|
|
</span></span></code></pre></div><p>Next add a message type for this:</p><pre tabindex=0><code>type Msg =
|
|
| Increment
|
|
| Decrement
|
|
</code></pre><p>Then add code to the <code>update</code> function to handle this message:</p><pre tabindex=0><code>let update msg model =
|
|
match msg with
|
|
| Increment -> { Count = model.Count + 1 }
|
|
| Decrement -> { Count = model.Count - 1 }
|
|
</code></pre><p>Then finally wire up the message in the <code>view</code> function:</p><pre tabindex=0><code>let view () =
|
|
MainPage(),
|
|
[ "Count" |> Binding.oneWay (fun m -> m.Count.ToString())
|
|
"Increment" |> Binding.msg Increment
|
|
"Decrement" |> Binding.msg Decrement
|
|
]
|
|
</code></pre><p>Run the app and you’ll be able to increment and decrement the value.</p><div class=image-div style=max-width:322px><p><img src=2018-05-05_15-47-58.gif alt="Animated Gif showing the value being incremented and decremented"></p></div><p>The code for this is on <a href=https://github.com/jimbobbennett/HelloElmish>GitHub</a>.</p><h3 id=learn-more>Learn more</h3><p>We’ve seen a very simple example of how to use F# to build Xamarin.Forms apps using the elmish architecture. For a more complicated example, check out my <a href=https://github.com/jimbobbennett/TicTacToe>Tic Tac Toe game on GitHub</a> or any of the <a href=https://github.com/fsprojects/Elmish.XamarinForms/tree/master/Samples>Elmish.XamarinForms samples</a>.</p><p>Also check out my video from <a href=http://fsharpconf.com>F#Conf</a> on using F# to build Xamarin apps below or on <a href="https://channel9.msdn.com/Events/FSharp-Events/fsharpConf-2018/05/?WT.mc_id=fsharpconf-channel9-jabenn">Channel9</a>, and don’t forget there is plenty of official <a href="https://www.microsoft.com/net/learn/languages/fsharp/?WT.mc_id=fsharp-blog-jabenn">Xamarin F# documentation</a> available.</p><iframe src=https://channel9.msdn.com/Events/FSharp-Events/fsharpConf-2018/05/player width=960 height=540 allowfullscreen frameborder=0></iframe></article></div></div><div class="col-sm-12 col-md-12 col-lg-3"><div class=sticky-sidebar><aside class=toc><h5>Table Of Contents</h5><div class=toc-content><nav id=TableOfContents><ul><li><ul><li><a href=#half-elmish>Half-Elmish</a></li><li><a href=#learn-more>Learn more</a></li></ul></li></ul></nav></div></aside><aside class=tags><h5>Tags</h5><ul class="tags-ul list-unstyled list-inline"><li class=list-inline-item><a href=https://jimbobbennett.dev/tags/xamarin target=_blank>xamarin</a></li><li class=list-inline-item><a href=https://jimbobbennett.dev/tags/xamarin.forms target=_blank>xamarin.forms</a></li><li class=list-inline-item><a href=https://jimbobbennett.dev/tags/technology target=_blank>technology</a></li><li class=list-inline-item><a href=https://jimbobbennett.dev/tags/f target=_blank>f#</a></li><li class=list-inline-item><a href=https://jimbobbennett.dev/tags/fsharp target=_blank>fsharp</a></li><li class=list-inline-item><a href=https://jimbobbennett.dev/tags/elmish target=_blank>elmish</a></li></ul></aside></div></div></div><div class=row><div class="col-sm-12 col-md-12 col-lg-9 p-4"><div id=disqus_thread></div><script>var disqus_config=function(){this.page.url="https://jimbobbennett.dev/blogs/building-mobile-apps-in-f-using-xamarin-forms-and-elmish/",this.page.identifier="1e69ed4ef086e3b0aafa2c5a5c290c66"};(function(){if(window.location.hostname=="localhost")return;var e=document,t=e.createElement("script");t.src="https://jimbobbennett.disqus.com/embed.js",t.setAttribute("data-timestamp",+new Date),(e.head||e.body).appendChild(t)})()</script><noscript>Please enable JavaScript to view the <a href=https://disqus.com/?ref_noscript>comments powered by Disqus.</a></noscript></div></div></div><button class="p-2 px-3" onclick=topFunction() id=topScroll>
|
|
<i class="fas fa-angle-up"></i></button></section><script>var topScroll=document.getElementById("topScroll");window.onscroll=function(){scrollFunction()};function scrollFunction(){document.body.scrollTop>20||document.documentElement.scrollTop>20?topScroll.style.display="block":topScroll.style.display="none"}function topFunction(){document.body.scrollTop=0,document.documentElement.scrollTop=0}</script></div><footer><div class="container py-4"><div class="row justify-content-center"><div class="col-md-4 text-center">© 2023 All Rights Reserved</div></div></div></div></footer><script src=https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.min.js integrity=sha384-QJHtvGhmr9XOIpI6YVutG+2QOK9T+ZnN4kzFN1RtK3zEFEIsxhlmWl5/YESvpZ13 crossorigin=anonymous></script>
|
|
<script>document.body.className.includes("light")&&(document.body.classList.add("dark"),localStorage.setItem("pref-theme","dark"))</script><script>let loadingIcons;function loading(){myVar=setTimeout(showPage,100)}function showPage(){try{document.getElementById("loading-icons").style.display="block"}catch{}}</script><script>function createCopyButton(e,t){const n=document.createElement("button");n.className="copy-code-button",n.type="button",n.innerText="Copy",n.addEventListener("click",()=>copyCodeToClipboard(n,e,t)),addCopyButtonToDom(n,e)}async function copyCodeToClipboard(e,t,n){const s=t.querySelector("pre > code").innerText;try{n.writeText(s)}finally{codeWasCopied(e)}}function codeWasCopied(e){e.blur(),e.innerText="Copied!",setTimeout(function(){e.innerText="Copy"},2e3)}function addCopyButtonToDom(e,t){t.insertBefore(e,t.firstChild);const n=document.createElement("div");n.className="highlight-wrapper",t.parentNode.insertBefore(n,t),n.appendChild(t)}if(navigator&&navigator.clipboard)document.querySelectorAll(".highlight").forEach(e=>createCopyButton(e,navigator.clipboard));else{var script=document.createElement("script");script.src="https://cdnjs.cloudflare.com/ajax/libs/clipboard-polyfill/2.7.0/clipboard-polyfill.promise.js",script.integrity="sha256-waClS2re9NUbXRsryKoof+F9qc1gjjIhc2eT7ZbIv94=",script.crossOrigin="anonymous",script.onload=function(){addCopyButtons(clipboard)},document.querySelectorAll(".highlight").forEach(e=>createCopyButton(e,script)),document.body.appendChild(script)}</script></body></html> |