0 Comments

Title image showing a rev counter gauge

Build targets are not only great to differentiate between a Debug and Release builds. You can also use them for targeting different environments or configurations of your app. Now I always like the idea of getting the best performance for apps that I put into my user's hands - in other words; I fancy to enable LLVM

Unfortunately when creating a new Target with Visual Studio 2019 (as of writing 16.5.4) the option to enable LLVM is disabled.

Showing disabled LLVM option in Visual Studio - and a screaming emoji

The issue is under consideration by the team, and for the time being, there is no way to enable LLVM via the UI Wizard in Visual Studio. Now one way to solve this is to clone your solution on to a machine running macOS and then enabling it in Visual Studio for Mac. But under Windows, the only option is to open up the csproj file and enable LLVM manually:

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <!-- Stuff -->
  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Gnabber|iPhone'">
    <OutputPath>bin\iPhone\Gnabber\</OutputPath>
    <DefineConstants>__IOS__;__MOBILE__;__UNIFIED__;</DefineConstants>
    <Optimize>true</Optimize>
    <PlatformTarget>AnyCPU</PlatformTarget>
    <UseVSHostingProcess>false</UseVSHostingProcess>
    <LangVersion>7.3</LangVersion>
    <ErrorReport>prompt</ErrorReport>
    <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
    <!-- Add the line bellow -->
    <MtouchUseLlvm>true</MtouchUseLlvm>
  </PropertyGroup>
  <!-- More Stuff -->
</Project>

Thanks, Victor Garcia Aprea for pointing this out to me and I hope this can be of some help to anyone out there stumbling over the same problem. If you want to check

You can find a small sample Project with a custom build target Gnabber up on GitHub.

HTH

1 Comments

ShellLoginApp

Since the release of Xamarin Forms 4.5, Shell now supports modal navigation. Since one of my highest ranking blog posts is how to create a login page with Xamarin Forms. I thought it was time to revisit the topic and look at how to implement a login page using the Shell.

So what is so special about a login page? Well, to state the obvious, the user should only be able to exit it after entering a correct login. Further, the user should not be able to leave the login page, i.e. navigate back to a previous page. And finally once successfully authenticated, the user should not find the login page when navigating back.

So let's see how we can capture the user on a page and then ensure that this page is no longer on the navigation stack while using Shell. So let's get going with the UI flow of a possible login experience. Our app has the following screen flow:

PageFlow

All of our pages have to be registered with the Shell. Note that the first ContentPage in the Shell.xaml file is the one getting displayed after start-up. So our Shell is structured accordingly:

<!-- Loading/Start Page -->
<ShellItem Route="loading">
    <ShellContent ContentTemplate="{DataTemplate local:LoadingPage}" />
</ShellItem>

<!-- Login and Registration Page -->
<ShellContent Route="login"
              ContentTemplate="{DataTemplate local:LoginPage}">
</ShellContent>

<!-- Main Page -->
<!-- We will get to this later -->

Our loading screen mainly simulates checking if the user has valid credentials. If the app was not a total fake on the business logic side. It might be using a Token-based flow; this is where one would check if the app still has a valid token and can go directly to the main screen or the user has to log in.

LoadingPage

Beautiful load animation, right? 😉 And here is the slim logic in the View Model :

// Called by the views OnAppearing method
public async void Init()
{
    var isAuthenticated = await this.identityService.VerifyRegistration();
    if (isAuthenticated)
    {
        await this.routingService.NavigateTo("///main");
    }
    else
    {
        await this.routingService.NavigateTo("///login");
    }
}

Note we only tell the Shell to navigate to the login screen. When using Shell, you define the kind of navigation on the target page. So for the login page, we would set it like this:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             ...
             Shell.PresentationMode="ModalAnimated"
             ...>
    ...
</ContentPage>

And whenever you navigate modally, it is always a good idea to override the back button navigation in the code behind of the target page as follows:

protected override bool OnBackButtonPressed()
{
    return true;
}

Why? Well because otherwise all your Android users could just simply press the Android back button and weasel their way out of your carefully crafted login process. Now let's add a registration page. Here we define the standard push navigation:

?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             ...
             Shell.PresentationMode="Animated"
             ...>
    <Shell.BackButtonBehavior>
        <BackButtonBehavior Command="{Binding ExecuteBack}"
                            TextOverride="Back" />
    </Shell.BackButtonBehavior>
    ...
</ContentPage>

Before we can navigate to the registration page, we have to register the page with the Shell. We can do this in the code behind of the AppShell.xaml.cs file:

Routing.RegisterRoute("registration", typeof(RegistrationPage));

Now we can navigate from the login page to the registration page by as follows:

Shell.Current.GoToAsync("//login/registration");

The code above implements the default navigation behaviour, i.e. a back button on the top left or by using the back button on Android. So as soon as the user has logged in, we display the main page of the app. Which is again defined in the AppShell.xaml as follows:

<!-- Main Page -->
<FlyoutItem Route="main"
            FlyoutDisplayOptions="AsMultipleItems" IsTabStop="False">
    <ShellContent Route="home"
                  IsTabStop="False"
                  ContentTemplate="{DataTemplate local:MainPage}"
                  Title="Home" />
</FlyoutItem>

Since it is not part of the login page, the Shell automatically removes the login page form the navigation stack.

So now that we are in the app. Sometimes you will want to present the user with the option to logout. Shell gives us an easy way to define a flyout menu. In Non-Shell apps, this is usually done with the MasterDetailPage. So a nice place to add our logout is as a new entry in the flyout right? Instead of defining a flyout item, it will be better to use a menu item. In general think of flyout items as areas of your app and menu items as actions in the menu. The logout is less an area and more an action. So for our logout, we define a menu item like this:

<MenuItem Text="Logout"
          Command="{Binding ExecuteLogout}" />

The Command and Binding context get defined in the code behind, i.e. the App.xaml.cs:

public AppShell()
{
    InitializeComponent();
	// ... routes and stuff
    BindingContext = this;
}

public ICommand ExecuteLogout => new Command(async () => await GoToAsync("main/login"));

Depending on the requirements of your app, you might want to force the user to log in at different times. This could be during start-up, resume or on a rainy Tuesday. Whatever the requirement, you can simply invoke the navigation similar to above, and the user will be navigated to the login screen. Once successfully logged in, the user is returned to the page on which the login page was opened.

Conclusion

With the new modal navigation mode of Shell, the implementation of a Login screen can be done quite nicely and with a lot less worrying about the state of the navigation stack. One of the main differences between using a NavigationPage compared to the Shell is how you configure the different parts within the AppShell.xaml. The Xamarin team has mentioned that more goodness should come to Shell, so be sure to stay tuned and watch out for news on the Xamarin Blog regarding new updates and features.

You can find the entire sample on GitHub. And check out David Ortinau's sample which inspired this post.

HTH

0 Comments

Icons of Xamarin & Automapper in a mobile phone

You might have already heard of AutoMapper, the library that helps you to copy Properties form Object to another written by Jimmy Bogard. Whenever you are creating a larger Xamarin Forms application, you usually end up with different models representing similar data but for different areas of your app. For example, you will get a minimalist Data Transfer Object (DTO) from your backend, which you might copy into another app-internal model or directly to the View Model representing the data displayed on your view. And this is where AutoMapper will help you out and prevent you from writing all that copy code.

I must confess - while it always felt a bit like overkill at the beginning. At the end of the project, I was still happy to have made the decision at the beginning. So let's see how we get started.

Getting Setup

The setup is quite straight forward actually add the NuGet package of AutoMapper to your Xamarin Forms project. That is until you start compiling for iOS, then it will all blow up due to some reflection issue. Since iOS is compiled Ahead Of Time (AOT), you can't do any runtime operations such as reflections. Now AutoMapper does not use reflection when running on iOS. Still, due to some weird compilation thingy issue - I haven't understood the point in detail - the compiler ends up trying to add reflection which will not work on iOS. So to make things run under iOS, we have to add the following line to our iOS csproj file:

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  ...
  <ItemGroup>
    ...
    <PackageReference Include="System.Reflection.Emit" Version="4.7.0">
      <ExcludeAssets>all</ExcludeAssets>
      <IncludeAssets>none</IncludeAssets>
    </PackageReference>
  </ItemGroup>
  ...
</Project>

Then there is still the Linker under iOS that tries to remove the System.Convert assembly. Which is required by AutoMapper. But luckily we can help the linker out here by adding an XML file:

<linker>
  <assembly fullname="mscorlib">
    <type fullname="System.Convert" preserve="All" />
  </assembly>
</linker>

And setting the build property to LinkDescription:

Showing Visual Studio Properties Pane

Configuration and usage

Okay so everything is set up now we still have to tell AutoMapper what which objects we would like to copy from A to B and back again. In the small app I prepared for this blogpost we will copy the note DTO object to it's View Model counterpart. So to get a configured AutoMapper instance, we would write something like this during the start-up of the app:

public static IMapper CreateMapper()
{
    var mapperConfiguration = new MapperConfiguration(cfg =>
    {
        cfg.CreateMap<Note, NoteViewModel>();
        cfg.CreateMap<NoteViewModel, Note>();
    });

    return mapperConfiguration.CreateMapper();
}jjjjjjj

Now we could convert the DTO to a View Model by invoking the AutoMapper instance as follows:

var viewModel = _mapper.Map<MainViewModel>(note);

But what I often end up doing is populating the View Model after creation or when the Page/View it is being used in is getting created. For example, an Init method on the View Model might be invoked during the OnAppearing of the View. Then we could call a service in the View Model which would return a DTO of that object. In this scenario, we will want to tell AutoMapper to map the DTO directly on the View Model itself:

public async void Init()
{
    var note = (await _noteService.GetNotes()).First();
    _mapper.Map(note, this);
}

If you had a List of View Models, i.e. a CollectionView or ListView, we would use LINQ in combination with AutoMapper to quickly convert from DTO to View Model:

Notes = (await _noteService.GetNotes())
            .Select(_mapper.Map<MainViewModel>)
            .ToList();

Having AutoMapper in place you whenever a new DTO has to be presented in your app, you would add a new configuration and then be able to use the mapping. So the more mapping you require, the quicker using AutoMapper will pay itself off.

But there is one more thing I really like when using AutoMapper in my projects, and that is testing.

Testing your mappings

"Testing? Are you serious?" - well yes, I actually am. You can check your configuration with a simple test case:

[Fact]
public void AppBoostrapper_ValidateMapping_AssertCorrectness()
{
    var mapper = AppBootstrapper.CreateMapper();
    mapper.ConfigurationProvider.AssertConfigurationIsValid();
}

This test will tell you if AutoMapper has all the information necessary to copy your data from one object to the other. So if we start adding Commands to the View Model, the mapping will fail with the information that it can not map the command into our DTO. And we obviously do not want to send an ICommand data field back to the server, so we ignore it:

cfg.CreateMap<Note, NoteViewModel>()
    .ForMember(n => n.ExecuteReset, opt => opt.Ignore())
    .ForMember(n => n.ExecuteStore, opt => opt.Ignore());
cfg.CreateMap<NoteViewModel, Note>();

But what when we add a data field WriterMood to the DTO and forget to add it to the View Model? Correct, the test will fail and inform us that we have forgotten to add the field.

Screenshot of failed AutoMapper config test

And that test has saved me from so many forgotten data fields - ahem what I meant to say it saved a friend of mine... 😅

Looking back

I will surely use AutoMapper in my future Xamarin Forms project that have their share of DTOs, internal Models and View Models since it is not only convenient to copy the properties. But it also saves me from forgetting to add properties to objects.

Be sure to check out the official documentation of AutoMapper since this post barely scratches the surface on what you can configure with AutoMapper.

As always, you can find a complete little sample application on GitHub.

HTH

0 Comments

00_TitleImage

Ever had to develop an app where the position of something was of interest. An address of a user, store, restaurant or some other point of interest? The straight forward version might be to provide the user with a simple form.

Address entry form

While the form is easy to implement and reliable, it can be cumbersome. Plus what if you want to navigate to the location? Or why not use the position of the device? Tap on a map to get the address? Well, all of these things can be achieved quite easily with a few helpful libraries provided by the Xamarin team.

Geocoding like a pro

Whenever you need to convert between coordinates and addresses, Geocoding is your best friend. You can show up with either an address or position, and you will get back the opposite:

var location = (await Geocoding.GetLocationsAsync($"{street}, {city}, {country}")).FirstOrDefault();

if (location == null) return;

Position = new Position(location.Latitude, location.Longitude);

So if we had a form where the user can enter the address and we wanted to navigate to that position, we could convert the address to geo coordinates and then, using Xamarin Essentials, open the maps app on the platform and navigate to that position.

var location = new Location(Position.Latitude, Position.Longitude);
var options = new MapLaunchOptions { NavigationMode = NavigationMode.Driving };
await Xamarin.Essentials.Map.OpenAsync(location, options);

Quite impressive for a bunch of entries and a few lines of code right?

Getting an address with one tap

But typing in addresses can be cumbersome. Why not just select the position on the map and get the address from that position? Xamarin Forms has supported the maps control for quite some time. Once you have added the NuGet package, it will allow you to present the native map platform, i.e. Google Maps on Android.

<?xml version="1.0" encoding="utf-8" ?>
             ...
             xmlns:maps="clr-namespace:Xamarin.Forms.Maps;assembly=Xamarin.Forms.Maps"
             ... >

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="2*" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <maps:Map x:Name="MapControl" />
        ...
    </Grid>

</ContentPage>


Note there are some setup steps required depending on the platform you intend to target. So be sure to check out the official docs from Microsoft on those details. Here we will continue to focus on the fun stuff

The map has an event MapClicked which fires when the user taps on the map with the geo-position.

Observable.FromEventPattern<MapClickedEventArgs>(
    mc => MapControl.MapClicked += mc, 
    mc => MapControl.MapClicked -= mc)
    .Subscribe(ev => ViewModel.ExecuteSetAddress.Execute(ev.EventArgs.Position));

If you are not familiar with Rx.Net, here is the equivalent code with an event handler:

protected override void OnAppearing()
{
   // ...

    MapControl.MapClicked += HandleMapClicked;
}

private void HandleMapClicked(object sender, MapClickedEventArgs e)
{
    var postion = e.Position;
    // ...
}

To give the user some feedback, we could even present the taped position with a pin:

private Unit SetPin(Position position)
{
    Pin pin = new Pin
    {
        Label = "The Place",
        Address = $"{ViewModel.Street}, {ViewModel.City}, {ViewModel.Country}",
        Type = PinType.Place,
        Position = position
    };

    var latDegrees = MapControl.VisibleRegion?.LatitudeDegrees ?? 0.01;
    var longDegrees = MapControl.VisibleRegion?.LongitudeDegrees ?? 0.01;
    MapControl.MoveToRegion(new MapSpan(position, latDegrees, longDegrees));
    MapControl?.Pins?.Clear();
    MapControl?.Pins?.Add(pin);
    return Unit.Default;
}

This code will always place the pin at the new location. If the user taps the pin, it will display the information provided to the pin.

Side note: Managing to present that info is quite a challenge given this code, 9 out of 10 times I only managed to place the pin at a new position. But perhaps that's just me

Using the geocoder, we can now get the address for those coordinates:

private async Task SetAddress(Position p)
{
    var addrs = (await Geocoding.GetPlacemarksAsync(new Location(p.Latitude, p.Longitude))).FirstOrDefault();
    Street = $"{addrs.Thoroughfare} {addrs.SubThoroughfare}";
    City = $"{addrs.PostalCode} {addrs.Locality}";
    Country = addrs.CountryName;
    // ...
}

But now you have it, going from Coordinates to an address and back the other way. Easy right? Well, there is still one more thing; the default coordinates of the map point to Rome. And I get it. Rome has a lot to offer, but if your user is not currently in Rome, it might be quite a bit of pinching and swiping until the map will be useful. So long story short, why not use the user's position?

Marco? Polo!

Getting the users position is a piece of cake again, thanks to Xamarin Essentials. That is once you have required all the permissions as described in the link. With just a few lines we can get the address and set the position:

try
{
    var location = await Geolocation.GetLastKnownLocationAsync()
	var position = new Position(location.Latitude, location.Longitude);
	Position = position;
	// ... set address and stuff
}
// many exception handlers according to docs here


The user will get prompted the first time this code runs if they want to share their position. Just as a side note if you do not want to smother the user right of the batt with permission dialogues, you might want to put some thought into when this code should first be run.

So when the view appears, we can try to get the users position and tell the map control what part of the world to display:

Geolocation.GetLastKnownLocationAsync()
            .ToObservable()
            .Catch(Observable.Return(new Location()))
            .SubscribeOn(RxApp.MainThreadScheduler)
            .Subscribe(async location => { 
                var position = new Position(location.Latitude, location.Longitude);
                Position = position;
                await SetAddress(position);
            });

I hope you could see that it does not take much to go from tiresome form to a "fun" maps control.

GifRecordingSmall

And all by using standard libraries offered to you by Microsoft. You can find the complete sample on GitHub.

HTH

0 Comments

TitleImage

A while back, I used the F# type providers to create a conversion table. That post gave me the idea if it were possible to write an app that gets its data from a website. Perhaps you have also received the request for an app. Nothing expensive and actually all the data that should be displayed is already present on the web, i.e. this website right over here. So the question is: Could such an app be created without having to write a single line of backend code?

In this blog post, I will try to create an app for one of my favourite Xamarin conferences - the Xamarin Expert Day. So let's see if we can create our Fabulous Xamarin Expert Day App.

Getting the data

Type providers are a delightful feature of F#. During compilation type providers generate the data models represented in a data source. I have written a blogpost before on the topic of parsing HTML. This time we will use another toolset that comes with the F# Data NuGet package.

Since we want to get the information about the Xamarin Experts Day conference, we can try parsing the website directly. So we could use the following line to do this:

HtmlDocument.Load "https://expertday.forxamarin.com/"

Unfortunately, we live in the modern ages of JavaScript. I don't want to go on a tangent here, but just state the fact that the Xamarin Experts Day website seems to be loading the information about the talks and the tracks after the initial HTML has been loaded. Luckily when loading the page in a browser, we get an HTML version which contains all of the information we are looking for. So instead of loading the data directly from the website, we can load the data from a file. Budget projects have their limitations... 🙃

HtmlDocument.Parse("ExpertXamarin.html")

When we look at the HTML of the website (in the browser) we can see that the speakers are listed under the following HTML structure:

<li data-speakerid="df2bc5ca-5a6b-48a9-87ac-71c817d7b240" class="sz-speaker sz-speaker--compact ">
<div class="sz-speaker__photo">
  <a href="#" onclick="return ...');">
    <img src="...894db1.jpg">
  </a>
</div>
<h3 class="sz-speaker__name">
  <a href="#" onclick="return ...');">Mark Allibone</a>
</h3>
<h4 class="sz-speaker__tagline">Lead Mobile Developer Rey Automation, Microsoft MVP</h4>
</li>

So we know that we can get the image, name, tagline and id of every speaker. So let's create a record to store that information:

type Speaker = {Id:string; Name:string; Photo:string; Tagline:string}


Creating the record is not strictly necessary. But it does make working with the data a bit easier later on. Another plus is that we could capsule the type provider code in a .Net Standard library and then share it with non F# .Net code. No, you can't access type provider data types directly from C# and while some features in C# get inspired by F#. From what I have heard, I would not hold my breath, hoping to see type providers in C# anytime soon...

With the record in place, all that is left to be done is extracting the data from the HTML. Good thing that F# Data comes along with the HTML CSS selector. The HTML CSS selector allows filtering after ids, classes and tag types. So if we wanted to get the speakers name, we can filter the component and then extract the value as follows:

// .. other parsing methods
let private getName (htmlNode:HtmlNode) =
    htmlNode.CssSelect("h3.sz-speaker__name > a") |> Seq.map (fun h -> h.DirectInnerText()) |> Seq.head

let getSpeakers (html:string) =
    HtmlDocument.Parse(html)
        .CssSelect("li.sz-speaker")
        |> Seq.map (fun s -> {Id = (getId s); Name = (getName s); Photo = (getPhoto s); Tagline = (getTagline s)})

Similarly, the rest of the data can be accessed to fill the other fields in our record. Same goes for the tracks, again we would first create a record where we store the name, time, room and speaker id of every track. We will be able to link a track to a speaker with the id:

type Track = {Room:string; Time:string; Title:string; SpeakerId:string option}

let getTracks (html:string) =
    HtmlDocument.Parse(html)
        .CssSelect("div.sz-session__card")
        |> Seq.map(fun s -> {Room = (getRoom s); Time = (getTime s); Title = (getTitle s); SpeakerId = (getSpeakerId s) })

Now that we have all the data in place, it is time to get cracking on the app.

Fabulous Xamarin Experts App

Before we start writing our UI code, there is still that shortcut we took above with loading the information out of a file. While this works great when using a script in the mobile world, this means we have to pack that HTML doc into the app. There are two approaches: either put it into the Assets folder on Android and in the Resources folder (you can also use XCAssets...) on iOS or make an Embedded Resource in the .Net Standard library. While the first option would be what Apple and Google intended you to use when adding docs, you want to ship with your app. You will have to jump through some hoops to access the document. So let's again save some time and just pack the file as an Embedded Resource in our .Net Standard project. Embedded Resources are packed into your apps binary. This results in an awkward fashion of accessing the data. While described in the official docs here, this is how it is implemented in the Xamarin Experts Day Conference App (we need a shorter name...):

let loadFile filename =
    let assembly = IntrospectionExtensions.GetTypeInfo(typedefof<Model>).Assembly;
    let stream = assembly.GetManifestResourceStream(filename);
    use streamReader = new StreamReader(stream)
    streamReader.ReadToEnd()

With that out of the way. Let's create a list of all the talks with the title, time and room. When selecting a track, we will display the Information of the presentation along with the speaker info.

So the list we can put together like this:

let showTrackCell track =
    View.ViewCell( view =
        View.StackLayout(children = [
            View.Label (text = track.Title, 
                        fontSize = FontSize 22.)
            View.Label (text = track.Time + " in " + track.Room, 
                        fontSize = FontSize 14.,
                        fontAttributes = FontAttributes.Italic)
            ]))

let view (model: Model) dispatch =

    View.ContentPage(
        content = match model.SelectedTrack with 
                    | Some track -> showTrackInfo track model dispatch
                    | None -> View.ListView(
                                    rowHeight = 80,
                                    hasUnevenRows = true,
                                    margin = Thickness(8.,0.,0.,0.),
                                    items = (model.Tracks |> List.map showTrackCell),
                                    selectionMode = ListViewSelectionMode.Single,
                                    itemSelected = (fun args -> dispatch (TrackSelected args))
                                    )
        )

And the "detail view" would be done like this:

let showTrackInfo track (model:Model) dispatch =
    let speaker = match track.SpeakerId with
                  | Some speakerId -> model.Speakers |> Seq.tryPick(fun s -> if s.Id = speakerId then Some s else None)
                  | None -> None

    let addSpeakerInfo (speaker:Speaker) =
        View.StackLayout(margin = Thickness(0.,32.,0.,0.), children = [
                View.Label (text = "Speaker", fontSize = FontSize 22. )
                View.Image (source = (Image.Path speaker.Photo))
                View.Label (text = "Presenter: " + speaker.Name)
                View.Label (text = "Tagline: " + speaker.Tagline)
            ])
        
    let speakerViewElements = match (speaker |> Option.map addSpeakerInfo) with
                              | Some speakerInfo -> speakerInfo
                              | None -> View.Label(text = "Brought to you by the Organizers");

    View.Grid (margin = Thickness(8.,8.,8.,16.),
                rowdefs = [Star; Auto],
                children = [
                    View.StackLayout(children = [
                        View.Label (text = track.Title, fontSize = FontSize 22.)
                        View.Label (text = "In: " + track.Room, fontSize = FontSize 14.)
                        View.Label (text = "At: " + track.Time, fontSize = FontSize 14., margin = Thickness(0.,-4.,0.,0.))
                        speakerViewElements
                        ])
                    (View.Button (text = "Back", command = (fun () -> dispatch (TrackSelected None)))).Row(1)
                ])

You might have noticed that the talk description is missing. The website has a JavaScript function retrieve that additional information. I think it would be possible to replicate the JavaScript call to the backend and then parse through the answer

JSON/HTML answer. But INSERT-LAME-STATEMENT-WHY-I-AM-NOT-LAZY-HERE.

The app still feels a bit ruff I think I might have to follow up in another blog post and make it pretty 😎

Conclusion

In this little experiment, we set out to see if it would be possible to write a mobile app that displays the same information as already present on a website. And while there were some bumps in the road - I am looking at you JavaScript. It was indeed possible to write an app for the Xamarin Experts Day that runs on Android and iOS.

Though I really should get started on my next post and make the app pretty 😇

You can check out the entire app on GitHub.

This post is part of theF# advent calendar. Be sure to check out the other posts.

HTH